/**
 * bulkUpdateInvoiceStatus regression tests — S9-001 money-integrity fix.
 *
 * Verifies bulk status changes delegate to guarded single-invoice helpers and
 * skip invalid transitions instead of raw status UPDATE.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'

const sendInvoice = vi.fn()
const voidInvoice = vi.fn()
const recordInvoicePayment = vi.fn()

class MockConflictError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'ConflictError'
  }
}

vi.mock('../../src/queries/invoices', () => ({
  sendInvoice,
  voidInvoice,
  ConflictError: MockConflictError,
}))

vi.mock('../../src/queries/invoice-payments', () => ({
  recordInvoicePayment,
}))

function invoiceSelectDb(row: { status: string; total: string; amountPaid: string } | null) {
  return {
    select: vi.fn().mockReturnValue({
      from: vi.fn().mockReturnValue({
        where: vi.fn().mockReturnValue({
          limit: vi.fn().mockResolvedValue(row ? [row] : []),
        }),
      }),
    }),
  }
}

describe('bulkUpdateInvoiceStatus', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    sendInvoice.mockResolvedValue({ id: 'inv-1', status: 'SENT' })
    voidInvoice.mockResolvedValue({ id: 'inv-1', status: 'VOID' })
    recordInvoicePayment.mockResolvedValue({
      payments: [],
      amountPaid: 1000,
      balance: 0,
      total: 1000,
      overpaymentAmount: 0,
    })
  })

  it('skips bulk PAID on a DRAFT invoice without calling recordInvoicePayment', async () => {
    const db = invoiceSelectDb({ status: 'DRAFT', total: '1000.00', amountPaid: '0' })
    const { bulkUpdateInvoiceStatus } = await import('../../src/queries/bulk-operations')

    const result = await bulkUpdateInvoiceStatus(
      db as never,
      'tenant-1',
      'actor-1',
      ['draft-id'],
      'PAID',
    )

    expect(result.processed).toBe(0)
    expect(result.skipped).toBe(1)
    expect(result.errors).toEqual([])
    expect(recordInvoicePayment).not.toHaveBeenCalled()
  })

  it('records remaining balance via recordInvoicePayment for TAX_ISSUED invoices', async () => {
    const db = invoiceSelectDb({
      status: 'TAX_ISSUED',
      total: '1000.00',
      amountPaid: '250.00',
    })
    const { bulkUpdateInvoiceStatus } = await import('../../src/queries/bulk-operations')

    const result = await bulkUpdateInvoiceStatus(
      db as never,
      'tenant-1',
      'actor-1',
      ['tax-issued-id'],
      'PAID',
    )

    expect(result.processed).toBe(1)
    expect(result.skipped).toBe(0)
    expect(recordInvoicePayment).toHaveBeenCalledWith(
      db,
      'tenant-1',
      'tax-issued-id',
      'actor-1',
      expect.objectContaining({
        amount: 750,
        source: 'manual',
      }),
    )
  })

  it('delegates SENT to sendInvoice with country and issue date', async () => {
    const db = { select: vi.fn() }
    const { bulkUpdateInvoiceStatus } = await import('../../src/queries/bulk-operations')

    const result = await bulkUpdateInvoiceStatus(
      db as never,
      'tenant-1',
      'actor-1',
      ['draft-id'],
      'SENT',
      { countryCode: 'IL', issueDate: '2026-06-10' },
    )

    expect(result.processed).toBe(1)
    expect(sendInvoice).toHaveBeenCalledWith(
      db,
      'tenant-1',
      'draft-id',
      'actor-1',
      'IL',
      '2026-06-10',
    )
  })

  it('delegates VOID to voidInvoice with required reason', async () => {
    const db = { select: vi.fn() }
    const { bulkUpdateInvoiceStatus } = await import('../../src/queries/bulk-operations')

    const result = await bulkUpdateInvoiceStatus(
      db as never,
      'tenant-1',
      'actor-1',
      ['sent-id'],
      'VOID',
      { voidReason: 'Duplicate invoice' },
    )

    expect(result.processed).toBe(1)
    expect(voidInvoice).toHaveBeenCalledWith(
      db,
      'tenant-1',
      'sent-id',
      'actor-1',
      'Duplicate invoice',
    )
  })

  it('skips void when single-path guard rejects TAX_ISSUED invoice', async () => {
    voidInvoice.mockRejectedValue(
      new MockConflictError(
        'Cannot void a TAX_ISSUED or later invoice — use a credit note instead',
      ),
    )
    const db = { select: vi.fn() }
    const { bulkUpdateInvoiceStatus } = await import('../../src/queries/bulk-operations')

    const result = await bulkUpdateInvoiceStatus(
      db as never,
      'tenant-1',
      'actor-1',
      ['tax-issued-id'],
      'VOID',
      { voidReason: 'Attempt void' },
    )

    expect(result.processed).toBe(0)
    expect(result.skipped).toBe(1)
    expect(result.errors).toEqual([])
  })
})

describe('bulkInvoiceStatusSchema', () => {
  it('requires voidReason when status is VOID', async () => {
    const { bulkInvoiceStatusSchema } = await import('../../src/queries/bulk-operations')
    const parsed = bulkInvoiceStatusSchema.safeParse({
      ids: ['550e8400-e29b-41d4-a716-446655440000'],
      status: 'VOID',
    })
    expect(parsed.success).toBe(false)
  })
})
