/**
 * Invoice approval query helpers — invoice-approval-workflow (iaw-012).
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'

const mockAppendInvoiceActivity = vi.fn().mockResolvedValue({ id: 'activity-id' })

vi.mock('../src/activities/writers', () => ({
  appendInvoiceActivity: (...args: unknown[]) => mockAppendInvoiceActivity(...args),
}))

function makeTx(existing: { id: string; status: string; source?: string } | null) {
  const auditValues: Record<string, unknown>[] = []
  return {
    select: vi.fn(() => ({
      from: vi.fn(() => ({
        where: vi.fn(() => ({
          limit: vi.fn(() => ({
            for: vi.fn().mockResolvedValue(existing ? [existing] : []),
          })),
        })),
      })),
    })),
    execute: vi.fn().mockResolvedValue(undefined),
    insert: vi.fn(() => ({
      values: vi.fn((values: Record<string, unknown>) => {
        auditValues.push(values)
        return Promise.resolve()
      }),
    })),
    auditValues,
  }
}

describe('listPendingApprovals', () => {
  it('returns SENT queue rows with camelCase fields', async () => {
    const db = {
      select: vi.fn(() => ({
        from: vi.fn(() => ({
          leftJoin: vi.fn(() => ({
            where: vi.fn(() => ({
              orderBy: vi.fn(() => ({
                limit: vi.fn(() => ({
                  offset: vi.fn().mockResolvedValue([
                    {
                      id: 'inv-1',
                      proformaNumber: 'INV-PRO-001',
                      customerId: 'cust-1',
                      customerName: 'Acme Corp',
                      total: '4200.00',
                      currency: 'ILS',
                      sentAt: new Date('2026-06-01T10:00:00.000Z'),
                    },
                  ]),
                })),
              })),
            })),
          })),
        })),
      })),
    }
    db.select
      .mockImplementationOnce(() => ({
        from: vi.fn(() => ({
          leftJoin: vi.fn(() => ({
            where: vi.fn().mockResolvedValue([{ total: 1 }]),
          })),
        })),
      }))
      .mockImplementation(() => ({
        from: vi.fn(() => ({
          leftJoin: vi.fn(() => ({
            where: vi.fn(() => ({
              orderBy: vi.fn(() => ({
                limit: vi.fn(() => ({
                  offset: vi.fn().mockResolvedValue([
                    {
                      id: 'inv-1',
                      proformaNumber: 'INV-PRO-001',
                      customerId: 'cust-1',
                      customerName: 'Acme Corp',
                      total: '4200.00',
                      currency: 'ILS',
                      sentAt: new Date('2026-06-01T10:00:00.000Z'),
                    },
                  ]),
                })),
              })),
            })),
          })),
        })),
      }))

    const { listPendingApprovals } = await import('../src/queries/invoice-approvals')
    const result = await listPendingApprovals(db as never, 'tenant-1', {
      sort: 'oldest',
      page: 1,
      perPage: 50,
    })

    expect(result.items).toHaveLength(1)
    expect(result.items[0]?.proformaNumber).toBe('INV-PRO-001')
    expect(result.items[0]?.customerName).toBe('Acme Corp')
    expect(result.items[0]?.sentAt).toBe('2026-06-01T10:00:00.000Z')
  })
})

describe('approveInvoiceEnhanced', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  function makeApproveTx(existing: { id: string; status: string; source?: string } | null) {
    const tx = makeTx(existing)
    let selectCall = 0
    tx.select = vi.fn(() => {
      selectCall++
      if (selectCall === 1) {
        return {
          from: vi.fn(() => ({
            where: vi.fn(() => ({
              limit: vi.fn(() => ({
                for: vi.fn().mockResolvedValue(existing ? [existing] : []),
              })),
            })),
          })),
        }
      }
      return {
        from: vi.fn(() => ({
          where: vi.fn(() => ({
            limit: vi.fn().mockResolvedValue([{ name: 'Dan Cohen' }]),
          })),
        })),
      }
    })
    return tx
  }

  it('writes on-behalf approval activity when approvedOnBehalfOf is customer', async () => {
    const tx = makeApproveTx({ id: 'inv-1', status: 'SENT' })
    const db = {
      transaction: vi.fn(async (fn: (txArg: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { approveInvoiceEnhanced } = await import('../src/queries/invoice-approvals')
    const result = await approveInvoiceEnhanced(db as never, 'tenant-1', 'inv-1', 'user-1', {
      approvedOnBehalfOf: 'customer',
      note: 'Customer confirmed via phone call',
    })

    expect(result.status).toBe('APPROVED')
    expect(mockAppendInvoiceActivity).toHaveBeenCalledWith(
      tx,
      expect.objectContaining({
        tenantId: 'tenant-1',
        invoiceId: 'inv-1',
        actorId: 'user-1',
        eventType: 'approved',
        note: 'Approved on behalf of customer by Dan Cohen\n"Customer confirmed via phone call"',
      }),
    )
  })

  it('writes staff approval activity without on-behalf wording by default', async () => {
    const tx = makeApproveTx({ id: 'inv-1', status: 'SENT' })
    const db = {
      transaction: vi.fn(async (fn: (txArg: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { approveInvoiceEnhanced } = await import('../src/queries/invoice-approvals')
    await approveInvoiceEnhanced(db as never, 'tenant-1', 'inv-1', 'user-1', {
      note: 'Confirmed by phone',
    })

    expect(mockAppendInvoiceActivity).toHaveBeenCalledWith(
      tx,
      expect.objectContaining({
        eventType: 'approved',
        note: 'Approved by Dan Cohen\n"Confirmed by phone"',
      }),
    )
  })

  it('throws when the invoice is not in the tenant', async () => {
    const tx = makeTx(null)
    const db = {
      transaction: vi.fn(async (fn: (txArg: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { approveInvoiceEnhanced } = await import('../src/queries/invoice-approvals')
    await expect(
      approveInvoiceEnhanced(db as never, 'tenant-1', 'missing-id', 'user-1', {}),
    ).rejects.toThrow('Invoice not found')
  })

  it('rejects approving credit notes through the customer approval workflow', async () => {
    const tx = makeApproveTx({ id: 'cn-1', status: 'SENT', source: 'credit_note' })
    const db = {
      transaction: vi.fn(async (fn: (txArg: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { approveInvoiceEnhanced } = await import('../src/queries/invoice-approvals')
    await expect(
      approveInvoiceEnhanced(db as never, 'tenant-1', 'cn-1', 'user-1', {}),
    ).rejects.toThrow('Cannot approve a credit note')
  })
})

describe('rejectInvoiceEnhanced', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('writes rejection activity with the rejection reason', async () => {
    const tx = makeTx({ id: 'inv-1', status: 'SENT' })
    const db = {
      transaction: vi.fn(async (fn: (txArg: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { rejectInvoiceEnhanced } = await import('../src/queries/invoice-approvals')
    const result = await rejectInvoiceEnhanced(db as never, 'tenant-1', 'inv-1', 'user-1', {
      reason: 'Totals do not match the signed quote',
      notifyCustomer: true,
    })

    expect(result.status).toBe('REJECTED')
    expect(mockAppendInvoiceActivity).toHaveBeenCalledWith(
      tx,
      expect.objectContaining({
        eventType: 'rejected',
        note: 'Totals do not match the signed quote',
      }),
    )
  })

  it('rejects sending credit notes into the rejection workflow', async () => {
    const tx = makeTx({ id: 'cn-1', status: 'SENT', source: 'credit_note' })
    const db = {
      transaction: vi.fn(async (fn: (txArg: typeof tx) => Promise<unknown>) => fn(tx)),
    }

    const { rejectInvoiceEnhanced } = await import('../src/queries/invoice-approvals')
    await expect(
      rejectInvoiceEnhanced(db as never, 'tenant-1', 'cn-1', 'user-1', {
        reason: 'Credit notes do not use customer rejection',
      }),
    ).rejects.toThrow('Cannot reject a credit note')
  })
})

describe('rejectInvoiceEnhancedSchema', () => {
  it('rejects reasons shorter than 5 characters', async () => {
    const { rejectInvoiceEnhancedSchema } = await import('../src/queries/invoice-approvals')
    const parsed = rejectInvoiceEnhancedSchema.safeParse({ reason: 'nope' })
    expect(parsed.success).toBe(false)
  })
})

describe('bulkApproveSchema', () => {
  it('allows up to 200 invoice ids', async () => {
    const { bulkApproveSchema } = await import('../src/queries/invoice-approvals')
    const ids = Array.from({ length: 200 }, (_, i) =>
      `00000000-0000-4000-8000-${String(i).padStart(12, '0')}`,
    )
    const parsed = bulkApproveSchema.safeParse({ ids })
    expect(parsed.success).toBe(true)
  })

  it('rejects more than 200 invoice ids', async () => {
    const { bulkApproveSchema } = await import('../src/queries/invoice-approvals')
    const ids = Array.from({ length: 201 }, (_, i) =>
      `00000000-0000-4000-8000-${String(i).padStart(12, '0')}`,
    )
    const parsed = bulkApproveSchema.safeParse({ ids })
    expect(parsed.success).toBe(false)
  })
})

describe('bulkApproveInvoices', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('counts approved, skipped, and error results for mixed batches', async () => {
    const SENT_ID = '00000000-0000-4000-8000-000000000001'
    const APPROVED_ID = '00000000-0000-4000-8000-000000000002'
    const MISSING_ID = '00000000-0000-4000-8000-000000000003'

    const states: Record<string, { id: string; status: string } | null> = {
      [SENT_ID]: { id: SENT_ID, status: 'SENT' },
      [APPROVED_ID]: { id: APPROVED_ID, status: 'APPROVED' },
      [MISSING_ID]: null,
    }

    let txCallIndex = 0
    const idOrder = [SENT_ID, APPROVED_ID, MISSING_ID]

    function makeTxForId(id: string) {
      const existing = states[id] ?? null
      let selectCall = 0
      return {
        select: vi.fn(() => {
          selectCall++
          if (selectCall === 1) {
            return {
              from: vi.fn(() => ({
                where: vi.fn(() => ({
                  limit: vi.fn(() => ({
                    for: vi.fn().mockResolvedValue(existing ? [existing] : []),
                  })),
                })),
              })),
            }
          }
          return {
            from: vi.fn(() => ({
              where: vi.fn(() => ({
                limit: vi.fn().mockResolvedValue([{ name: 'Staff User' }]),
              })),
            })),
          }
        }),
        execute: vi.fn().mockResolvedValue(undefined),
        insert: vi.fn(() => ({ values: vi.fn().mockResolvedValue(undefined) })),
      }
    }

    const db = {
      transaction: vi.fn(async (fn: (tx: ReturnType<typeof makeTxForId>) => Promise<unknown>) => {
        const id = idOrder[txCallIndex++]!
        return fn(makeTxForId(id))
      }),
    }

    const { bulkApproveInvoices } = await import('../src/queries/invoice-approvals')
    const result = await bulkApproveInvoices(
      db as never,
      'tenant-1',
      'user-1',
      [SENT_ID, APPROVED_ID, MISSING_ID],
    )

    expect(result).toEqual({
      approved: 1,
      skipped: 1,
      errors: [{ id: MISSING_ID, reason: 'Invoice not found' }],
    })
  })
})
