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

const mockReverseInvoicePayment = vi.fn()
const mockGetInvoiceBalance = vi.fn()
const mockRecordInvoicePayment = vi.fn()

let mockSessionRole = 'OWNER'

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (c: { set: (key: string, value: unknown) => void }, next: () => Promise<void>) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      role: mockSessionRole,
      permissions: ['invoices:write'],
    })
    await next()
  },
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: () => 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')
  class MockReceiptIssuedError extends Error {}
  return {
    ...actual,
    createDb: () => ({}),
    getInvoiceBalance: (...args: unknown[]) => mockGetInvoiceBalance(...args),
    recordInvoicePayment: (...args: unknown[]) => mockRecordInvoicePayment(...args),
    reverseInvoicePayment: (...args: unknown[]) => mockReverseInvoicePayment(...args),
    ReceiptIssuedError: MockReceiptIssuedError,
  }
})

import { invoicePaymentsRoutes } from '../src/routes/invoices/payments'

function appForRoutes() {
  const app = new Hono<AppEnv>()
  app.route('/api/invoices', invoicePaymentsRoutes)
  return app
}

describe('invoice payment routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockSessionRole = 'OWNER'
    mockGetInvoiceBalance.mockResolvedValue({
      payments: [],
      amountPaid: 0,
      balance: 100,
      total: 100,
      overpaymentAmount: 0,
    })
    mockRecordInvoicePayment.mockResolvedValue({
      payments: [],
      amountPaid: 130,
      balance: 0,
      total: 100,
      overpaymentAmount: 30,
    })
    mockReverseInvoicePayment.mockResolvedValue({
      payments: [],
      amountPaid: 0,
      balance: 100,
      total: 100,
      overpaymentAmount: 0,
    })
  })

  it('forwards the reversal reason to the query layer', async () => {
    const res = await appForRoutes().request('/api/invoices/inv-1/payments/pay-1', {
      method: 'DELETE',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ reason: 'mis-keyed amount' }),
    })

    expect(res.status).toBe(200)
    expect(mockReverseInvoicePayment).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'inv-1',
      'pay-1',
      'user-1',
      { reason: 'mis-keyed amount' },
    )
  })

  it('rejects reversal for non-owner non-admin sessions', async () => {
    mockSessionRole = 'MEMBER'

    const res = await appForRoutes().request('/api/invoices/inv-1/payments/pay-1', {
      method: 'DELETE',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ reason: 'mis-keyed amount' }),
    })

    expect(res.status).toBe(403)
    expect(mockReverseInvoicePayment).not.toHaveBeenCalled()
  })
})
