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

const mockBuildCustomerStatement = vi.fn()
const mockRenderStatementPdf = vi.fn()
const mockValidateStatementPdf = vi.fn()

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

vi.mock('../src/lib/statement-pdf', () => ({
  renderStatementPdf: (...args: unknown[]) => mockRenderStatementPdf(...args),
  validateStatementPdf: (...args: unknown[]) => mockValidateStatementPdf(...args),
}))

vi.mock('@zync/auth', () => ({ verifyJwt: vi.fn() }))

vi.mock('@zync/db/queries', () => ({
  buildCustomerStatement: (...args: unknown[]) => mockBuildCustomerStatement(...args),
  getCustomerBillingEmail: vi.fn(),
}))

vi.mock('@zync/notifications', () => ({ sendEmail: vi.fn() }))

import { customerStatementRoute } from '../src/routes/customers/statement'

const statement: CustomerStatement = {
  customerId: 'customer-1',
  customerName: 'Acme Ltd',
  from: '2026-01-01',
  to: '2026-01-31',
  groups: [],
  aging: { d0_30: 0, d31_60: 0, d61_90: 0, d90plus: 0 },
}

const env = { JWT_SECRET: 'secret' } as AppEnv['Bindings']

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

describe('customer statement PDF route', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockBuildCustomerStatement.mockResolvedValue(statement)
    mockValidateStatementPdf.mockImplementation(() => undefined)
  })

  it('returns validated PDF bytes with download headers', async () => {
    const bytes = new TextEncoder().encode('%PDF-1.7\nbody\n%%EOF\n')
    mockRenderStatementPdf.mockResolvedValue(bytes)

    const response = await statementApp().request(
      '/api/customers/customer-1/statement/pdf?from=2026-01-01&to=2026-01-31',
      {},
      env,
    )

    expect(response.status).toBe(200)
    expect(response.headers.get('Content-Type')).toBe('application/pdf')
    expect(new Uint8Array(await response.arrayBuffer())).toEqual(bytes)
    expect(mockValidateStatementPdf).toHaveBeenCalledWith(bytes)
  })

  it('returns a stable typed 502 and records diagnostics when validation fails', async () => {
    const error = Object.assign(new Error('invalid bytes'), {
      code: 'STATEMENT_PDF_INVALID_BYTES',
      upstreamStatus: 200,
    })
    mockRenderStatementPdf.mockResolvedValue(new TextEncoder().encode('%PDF-1.7\ninvalid\n%%EOF\n'))
    mockValidateStatementPdf.mockImplementationOnce(() => {
      throw error
    })
    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined)

    const response = await statementApp().request(
      '/api/customers/customer-1/statement/pdf?from=2026-01-01&to=2026-01-31',
      {},
      env,
    )

    expect(response.status).toBe(502)
    expect(await response.json()).toEqual({
      error: 'PDF generation failed',
      code: 'STATEMENT_PDF_GENERATION_FAILED',
    })
    expect(errorSpy).toHaveBeenCalledWith('statement_pdf_generation_failed', expect.objectContaining({
      customerId: 'customer-1',
      tenantId: 'tenant-1',
      reason: 'STATEMENT_PDF_INVALID_BYTES',
      upstreamStatus: 200,
    }))
    expect(errorSpy.mock.calls.flat().join(' ')).not.toContain('invalid bytes')
    errorSpy.mockRestore()
  })
})
