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

const mockGetProposalInvoiceLink = vi.fn()
const mockCreateInvoiceFromProposal = vi.fn()

const { permissionCalls } = vi.hoisted(() => ({
  permissionCalls: [] as string[],
}))

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

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: 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,
    createDb: vi.fn(() => ({})),
    getProposalInvoiceLink: (...args: unknown[]) => mockGetProposalInvoiceLink(...args),
    createInvoiceFromProposal: (...args: unknown[]) => mockCreateInvoiceFromProposal(...args),
  }
})

import { proposalRoutes } from '../src/routes/proposals'

function appForSession(session: Record<string, unknown>) {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', session)
    await next()
  })
  app.route('/api/proposals', proposalRoutes)
  return app
}

describe('proposal invoice routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('uses the spec permissions for link lookup and creation', () => {
    appForSession({ type: 'user', tid: 'tenant-1', sub: 'user-1' })

    expect(permissionCalls).toEqual(['marketing:read', 'invoices:write'])
  })

  it('returns null from GET when no linked invoice exists', async () => {
    mockGetProposalInvoiceLink.mockResolvedValue(null)

    const res = await appForSession({
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      permissions: ['marketing:read'],
    }).request('/api/proposals/proposal-1/invoice', undefined, {} as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    expect(await res.json()).toBeNull()
    expect(mockGetProposalInvoiceLink).toHaveBeenCalledWith({}, 'tenant-1', 'proposal-1')
  })

  it('accepts the spec POST body and returns invoiceId plus invoiceNumber', async () => {
    mockCreateInvoiceFromProposal.mockResolvedValue({
      invoiceId: 'invoice-1',
      invoiceNumber: null,
    })

    const res = await appForSession({
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      permissions: ['invoices:write'],
    }).request(
      '/api/proposals/proposal-1/invoice',
      {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          issue_date: '2026-07-03',
          due_date: '2026-08-02',
          note: 'PO 42',
          lines: [
            {
              description: 'Website Redesign',
              quantity: 2,
              unit_price: 5000,
              discount_pct: 10,
            },
          ],
        }),
      },
      {} as AppEnv['Bindings'],
    )

    expect(res.status).toBe(201)
    expect(await res.json()).toEqual({
      invoiceId: 'invoice-1',
      invoiceNumber: null,
    })
    expect(mockCreateInvoiceFromProposal).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'proposal-1',
      'user-1',
      {
        issue_date: '2026-07-03',
        due_date: '2026-08-02',
        note: 'PO 42',
        lines: [
          {
            description: 'Website Redesign',
            quantity: 2,
            unit_price: 5000,
            discount_pct: 10,
          },
        ],
      },
    )
  })

  it('maps accepted-status violations to 400', async () => {
    mockCreateInvoiceFromProposal.mockRejectedValue(
      new Error('Proposal must be ACCEPTED before creating an invoice'),
    )

    const res = await appForSession({
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      permissions: ['invoices:write'],
    }).request(
      '/api/proposals/proposal-1/invoice',
      {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          issue_date: '2026-07-03',
          due_date: '2026-08-02',
          lines: [{ description: 'Line', quantity: 1, unit_price: 100 }],
        }),
      },
      {} as AppEnv['Bindings'],
    )

    expect(res.status).toBe(400)
    expect(await res.json()).toEqual({
      error: 'Proposal must be ACCEPTED before creating an invoice',
    })
  })
})
