import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Hono } from 'hono'
import type { AppEnv } from '../src/types'

const mocks = vi.hoisted(() => ({
  authMiddleware: vi.fn(async (_c: unknown, next: () => Promise<void>) => {
    await next()
  }),
  requireModuleEnabled: vi.fn(() => async (_c: unknown, next: () => Promise<void>) => {
    await next()
  }),
  requirePermission: vi.fn(() => async (_c: unknown, next: () => Promise<void>) => {
    await next()
  }),
  appendInvoiceActivity: vi.fn(),
  appendProjectActivity: vi.fn(),
  appendCustomerActivity: vi.fn(),
  appendVendorActivity: vi.fn(),
}))

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: mocks.authMiddleware,
}))

vi.mock('../src/middleware/require-module-enabled', () => ({
  requireModuleEnabled: mocks.requireModuleEnabled,
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: mocks.requirePermission,
}))

vi.mock('../src/lib/activity-feed', () => ({
  loadActivityFeed: vi.fn(),
}))

vi.mock('@zync/db/queries', () => {
  return {
    appendInvoiceActivity: mocks.appendInvoiceActivity,
    appendProjectActivity: mocks.appendProjectActivity,
    appendCustomerActivity: mocks.appendCustomerActivity,
    appendVendorActivity: mocks.appendVendorActivity,
    getInvoiceActivityNote: vi.fn(),
    softDeleteInvoiceActivity: vi.fn(),
    getProjectActivityNote: vi.fn(),
    softDeleteProjectActivity: vi.fn(),
    getCustomerActivityNote: vi.fn(),
    softDeleteCustomerActivity: vi.fn(),
    getVendorActivityNote: vi.fn(),
    softDeleteVendorActivity: vi.fn(),
  }
})

import { invoiceActivityRoutes } from '../src/routes/invoices/activity'
import { projectActivityRoutes } from '../src/routes/projects/activity'
import { customerActivityRoutes } from '../src/routes/customers/activity'
import { vendorActivityRoutes } from '../src/routes/vendor-activity'

function appFor(routePrefix: string, routes: Hono<AppEnv>) {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      permissions: ['*'],
    })
    c.set('db', {
      transaction: async <T>(fn: (tx: Record<string, never>) => Promise<T>) => fn({}),
    })
    await next()
  })
  app.route(routePrefix, routes)
  return app
}

const cases = [
  {
    name: 'invoice',
    app: () => appFor('/api/invoices', invoiceActivityRoutes),
    path: '/api/invoices/invoice-1/activity',
    appendMock: mocks.appendInvoiceActivity,
  },
  {
    name: 'project',
    app: () => appFor('/api/projects', projectActivityRoutes),
    path: '/api/projects/project-1/activity',
    appendMock: mocks.appendProjectActivity,
  },
  {
    name: 'customer',
    app: () => appFor('/api/customers', customerActivityRoutes),
    path: '/api/customers/customer-1/activity',
    appendMock: mocks.appendCustomerActivity,
  },
  {
    name: 'vendor',
    app: () => appFor('/api/vendors', vendorActivityRoutes),
    path: '/api/vendors/vendor-1/activity',
    appendMock: mocks.appendVendorActivity,
  },
] as const

describe('activity note routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    for (const testCase of cases) {
      testCase.appendMock.mockResolvedValue({ id: `${testCase.name}-activity-1` })
    }
  })

  it.each(cases)('accepts a 1000-character note for $name', async ({ app, path, appendMock }) => {
    const note = 'a'.repeat(1000)

    const res = await app().request(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ note }),
    })

    expect(res.status).toBe(201)
    expect(await res.json()).toEqual({ id: expect.any(String) })
    expect(appendMock).toHaveBeenCalledOnce()
    expect(appendMock.mock.calls[0]?.[1]).toMatchObject({
      note,
      eventType: 'note_added',
      actorType: 'user',
    })
  })

  it.each(cases)('rejects a note longer than 1000 characters for $name', async ({ app, path, appendMock }) => {
    const res = await app().request(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ note: 'a'.repeat(1001) }),
    })

    expect(res.status).toBe(400)
    expect(await res.json()).toEqual({ error: 'note must be 1-1000 characters' })
    expect(appendMock).not.toHaveBeenCalled()
  })
})
