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

const mockGetTenantFieldRules = vi.fn()
const mockListInvoices = vi.fn()
const mockGetInvoiceWithLines = vi.fn()

vi.mock('../src/middleware/guards', () => ({
  requirePermission: () => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('../src/middleware/require-module-enabled', () => ({
  requireModuleEnabled: () => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('../src/middleware/bump-financials-version', () => ({
  bumpFinancialsVersionOnWrite: async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('@zync/db/queries', async () => {
  const actual = await vi.importActual<Record<string, unknown>>('@zync/db/queries')
  return {
    ...actual,
    getTenantFieldRules: (...args: unknown[]) => mockGetTenantFieldRules(...args),
    listInvoices: (...args: unknown[]) => mockListInvoices(...args),
    getInvoiceWithLines: (...args: unknown[]) => mockGetInvoiceWithLines(...args),
  }
})

import { invoiceRoutes } from '../src/routes/invoices/index'

function appFor() {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      role: 'CONTRACTOR',
      permissions: ['invoices:read'],
    })
    c.set('db', {})
    await next()
  })
  app.route('/api/invoices', invoiceRoutes)
  return app
}

describe('invoice field permission enforcement', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetTenantFieldRules.mockResolvedValue([
      { entityType: 'invoice', fieldName: 'total', role: 'CONTRACTOR', permission: 'hidden' },
      { entityType: 'invoice', fieldName: 'notes', role: 'CONTRACTOR', permission: 'read_only' },
    ])
    mockListInvoices.mockResolvedValue({
      items: [
        { id: 'invoice-1', total: '100.00', notes: 'Internal note' },
        { id: 'invoice-2', total: '200.00', notes: 'Second note' },
      ],
      nextCursor: null,
      total: 2,
    })
    mockGetInvoiceWithLines.mockResolvedValue({
      id: 'invoice-1',
      total: '100.00',
      notes: 'Internal note',
      lines: [],
    })
  })

  it('strips hidden invoice fields from list payloads', async () => {
    const res = await appFor().request('/api/invoices', undefined, {} as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      items: [
        { id: 'invoice-1', notes: 'Internal note' },
        { id: 'invoice-2', notes: 'Second note' },
      ],
      nextCursor: null,
      total: 2,
      _meta: { readOnly: ['notes'] },
    })
  })

  it('strips hidden invoice fields from detail payloads and marks read-only ones', async () => {
    const res = await appFor().request('/api/invoices/invoice-1', undefined, {} as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      id: 'invoice-1',
      notes: 'Internal note',
      lines: [],
      _meta: { readOnly: ['notes'] },
    })
  })
})
