import { beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('jsonwebtoken', () => ({ default: { verify: vi.fn() } }))
vi.mock('../../lib/redis', () => ({
  redis: { on: vi.fn(), get: vi.fn(), set: vi.fn(), del: vi.fn(), quit: vi.fn() },
}))
vi.mock('../../lib/prisma', () => ({
  prisma: {
    adminUser: { findUnique: vi.fn() },
  },
}))
vi.mock('../../modules/admin/admin.service', () => ({
  getBilling: vi.fn(),
  getInvoicePdf: vi.fn(),
  getBillingAccountDetail: vi.fn(),
  getCompanyInvoices: vi.fn(),
  updateBillingAccount: vi.fn(),
  setDunningPaused: vi.fn(),
  createBillingInvoice: vi.fn(),
  finalizeBillingInvoice: vi.fn(),
  payBillingInvoice: vi.fn(),
  retryBillingInvoicePayment: vi.fn(),
  voidBillingInvoice: vi.fn(),
  markBillingInvoiceUncollectible: vi.fn(),
  issueBillingCreditNote: vi.fn(),
  issueBillingRefund: vi.fn(),
}))

import request from 'supertest'
import jwt from 'jsonwebtoken'
import { createApp } from '../../app'
import { prisma } from '../../lib/prisma'
import * as adminService from '../../modules/admin/admin.service'

const app = createApp()

function admin(role = 'FINANCE') {
  return { id: 'admin_1', email: 'finance@example.test', role, isActive: true, totpEnabled: true }
}

beforeEach(() => {
  vi.clearAllMocks()
  process.env.JWT_SECRET = 'test-secret'
  vi.mocked(jwt.verify).mockReturnValue({ sub: 'admin_1', type: 'admin' } as never)
  vi.mocked(prisma.adminUser.findUnique).mockResolvedValue(admin() as never)
})

describe('admin billing API contracts', () => {
  it('coerces list filters and requires finance access', async () => {
    vi.mocked(adminService.getBilling).mockResolvedValue({ data: [], total: 0, stats: {} } as never)

    const res = await request(app)
      .get('/api/v1/admin/billing?q=atlas&status=PAST_DUE&plan=PRO&page=2&pageSize=25')
      .set('Authorization', 'Bearer admin-token')

    expect(res.status).toBe(200)
    expect(adminService.getBilling).toHaveBeenCalledWith({
      q: 'atlas',
      status: 'PAST_DUE',
      plan: 'PRO',
      page: 2,
      pageSize: 25,
    })
  })

  it('rejects oversized billing pagination before service execution', async () => {
    const res = await request(app)
      .get('/api/v1/admin/billing?pageSize=500')
      .set('Authorization', 'Bearer admin-token')

    expect(res.status).toBe(400)
    expect(adminService.getBilling).not.toHaveBeenCalled()
  })

  it('streams invoice PDFs with content headers from the service boundary', async () => {
    vi.mocked(adminService.getInvoicePdf).mockResolvedValue({
      invoiceNumber: 'INV-2026-000001',
      pdfBuffer: Buffer.from('%PDF fake'),
    } as never)

    const res = await request(app)
      .get('/api/v1/admin/billing/invoices/invoice_1/pdf')
      .set('Authorization', 'Bearer admin-token')

    expect(res.status).toBe(200)
    expect(res.header['content-type']).toContain('application/pdf')
    expect(res.header['content-disposition']).toContain('INV-2026-000001.pdf')
    expect(adminService.getInvoicePdf).toHaveBeenCalledWith('invoice_1')
  })

  it('passes parsed account updates with admin identity and request ip', async () => {
    vi.mocked(adminService.updateBillingAccount).mockResolvedValue({ id: 'billing_account_1' } as never)

    const res = await request(app)
      .patch('/api/v1/admin/billing/accounts/billing_account_1')
      .set('Authorization', 'Bearer admin-token')
      .send({ billingEmail: 'billing@example.test', invoiceTerms: 'NET_30', netTermsDays: 30 })

    expect(res.status).toBe(200)
    expect(adminService.updateBillingAccount).toHaveBeenCalledWith(
      'billing_account_1',
      { billingEmail: 'billing@example.test', invoiceTerms: 'NET_30', netTermsDays: 30 },
      'admin_1',
      expect.any(String),
    )
  })

  it('rejects malformed draft invoice payloads before invoice creation', async () => {
    const res = await request(app)
      .post('/api/v1/admin/billing/accounts/billing_account_1/invoices')
      .set('Authorization', 'Bearer admin-token')
      .send({ invoiceType: 'MANUAL', lineItems: [] })

    expect(res.status).toBe(400)
    expect(adminService.createBillingInvoice).not.toHaveBeenCalled()
  })

  it('requires finance role for billing mutations', async () => {
    vi.mocked(prisma.adminUser.findUnique).mockResolvedValue(admin('VIEWER') as never)

    const res = await request(app)
      .post('/api/v1/admin/billing/invoices/invoice_1/finalize')
      .set('Authorization', 'Bearer admin-token')

    expect(res.status).toBe(403)
    expect(adminService.finalizeBillingInvoice).not.toHaveBeenCalled()
  })
})
