import { sql } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { MockInvoiceProvider } from '@platform-modules/invoicing'
import type { MorningCredentials } from '@platform-modules/invoicing/morning'
import { issueSettlementInvoice, type SettlementInvoiceDb } from './billing-doc.js'
import { pressZoneInitSql, pressZoneSchema } from '../schema.js'

function createMorningCredentials(): MorningCredentials {
  return {
    apiUser: 'api-user@example.test',
    apiPass: 'super-secret',
    companyId: 'company-123',
  }
}

async function createTestDb() {
  const db = createPgliteClient({ schema: pressZoneSchema })

  for (const statement of pressZoneInitSql.split(';').map((part) => part.trim()).filter(Boolean)) {
    await db.execute(sql.raw(statement))
  }

  return db
}

async function countInvoiceDocuments(db: SettlementInvoiceDb): Promise<number> {
  const result = await db.execute(sql`SELECT COUNT(*)::int AS count FROM invoice_document`)
  const rows =
    (Array.isArray(result)
      ? result
      : (result as { rows?: Array<{ count?: number | string | bigint }> }).rows) ?? []

  return Number(rows[0]?.count ?? 0)
}

describe('issueSettlementInvoice', () => {
  it('maps an IL to IL settlement to a Morning invoice with a taxable line', async () => {
    const provider = new MockInvoiceProvider()
    const db = Object.assign(await createTestDb(), {
      morningCredential: createMorningCredentials(),
      morningProvider: provider,
      invoiceDate: '2025-01-01',
      invoiceCurrency: 'ILS',
    })

    const result = await issueSettlementInvoice(db, {
      supplier: { country: 'IL' },
      customer: {
        country: 'IL',
        name: 'Press Zone Customer',
        email: 'billing@example.test',
        taxId: '514000000',
      },
      supplyType: 'services',
      lineItems: [
        {
          description: 'Monthly plugin subscription',
          quantity: 1,
          unitAmountMinor: 10_000n,
        },
      ],
      idempotencyKey: 'settlement-il-il',
    })

    expect(result.ok).toBe(true)
    expect(provider.issueCallCount.current).toBe(1)
    expect(provider.calls).toHaveLength(1)
    expect(provider.calls[0]?.spec).toMatchObject({
      currency: 'ILS',
      idempotencyKey: 'settlement-il-il',
      docType: 'invoice',
      customer: {
        name: 'Press Zone Customer',
        email: 'billing@example.test',
        taxId: '514000000',
      },
    })
    expect(provider.calls[0]?.spec.lineItems).toEqual([
      {
        description: 'Monthly plugin subscription',
        quantity: 1,
        unitAmountMinor: 10_000n,
        taxTreatment: 'exclusive',
        vatRateBasisPoints: 1800,
      },
    ])
    expect(await countInvoiceDocuments(db)).toBe(1)
  })

  it('maps an export settlement to a zero-rated Morning invoice line', async () => {
    const provider = new MockInvoiceProvider()
    const db = Object.assign(await createTestDb(), {
      morningCredential: createMorningCredentials(),
      morningProvider: provider,
      invoiceDate: '2025-01-01',
      invoiceCurrency: 'ILS',
    })

    await issueSettlementInvoice(db, {
      supplier: { country: 'IL' },
      customer: {
        country: 'US',
        name: 'Export Customer',
      },
      supplyType: 'digital',
      lineItems: [
        {
          description: 'Monthly plugin subscription',
          quantity: 1,
          unitAmountMinor: 10_000n,
        },
      ],
      idempotencyKey: 'settlement-export',
    })

    expect(provider.calls[0]?.spec.lineItems).toEqual([
      {
        description: 'Monthly plugin subscription',
        quantity: 1,
        unitAmountMinor: 10_000n,
        taxTreatment: 'zero_rated',
        vatRateBasisPoints: 0,
      },
    ])
  })

  it('rejects malformed document inputs before issuing with Morning', async () => {
    const provider = new MockInvoiceProvider()
    const db = Object.assign(await createTestDb(), {
      morningCredential: createMorningCredentials(),
      morningProvider: provider,
      invoiceDate: '2025-01-01',
      invoiceCurrency: 'ILS',
    })
    const baseLineItem = {
      description: 'Monthly plugin subscription',
      quantity: 1,
      unitAmountMinor: 10_000n,
    }
    const baseInput = {
      supplier: { country: 'IL' },
      customer: {
        country: 'IL',
        name: 'Press Zone Customer',
      },
      supplyType: 'services' as const,
      lineItems: [baseLineItem],
      idempotencyKey: 'settlement-valid',
    }

    await expect(issueSettlementInvoice(db, { ...baseInput, lineItems: [] })).rejects.toThrow(
      'settlement invoice requires at least one line item',
    )
    await expect(
      issueSettlementInvoice(db, {
        ...baseInput,
        lineItems: [{ ...baseLineItem, quantity: 0 }],
      }),
    ).rejects.toThrow('settlement invoice line item quantity must be greater than 0')
    await expect(
      issueSettlementInvoice(db, {
        ...baseInput,
        lineItems: [{ ...baseLineItem, unitAmountMinor: -1n }],
      }),
    ).rejects.toThrow('settlement invoice line item amount must be a non-negative integer')
    await expect(
      issueSettlementInvoice(db, { ...baseInput, idempotencyKey: '  ' }),
    ).rejects.toThrow('settlement invoice idempotencyKey is required')
    await expect(
      issueSettlementInvoice(Object.assign(db, { invoiceCurrency: 'usd' }), baseInput),
    ).rejects.toThrow('settlement invoice currency must be an ISO-4217 code')
    await expect(
      issueSettlementInvoice(Object.assign(db, { invoiceDate: '2025-13-01' }), baseInput),
    ).rejects.toThrow('settlement invoiceDate must be a valid YYYY-MM-DD date')
    await expect(
      issueSettlementInvoice(Object.assign(db, { invoiceDate: '1976-06-30' }), baseInput),
    ).rejects.toThrow('settlement invoiceDate is implausibly old')
    await expect(
      issueSettlementInvoice(Object.assign(db, { invoiceDate: '2999-01-01' }), baseInput),
    ).rejects.toThrow('settlement invoiceDate must not be in the future')

    expect(provider.issueCallCount.current).toBe(0)
  })

  it('deduplicates by idempotencyKey and does not issue a second Morning document', async () => {
    const provider = new MockInvoiceProvider()
    const db = Object.assign(await createTestDb(), {
      morningCredential: createMorningCredentials(),
      morningProvider: provider,
      invoiceDate: '2025-01-01',
      invoiceCurrency: 'ILS',
    })

    const first = await issueSettlementInvoice(db, {
      supplier: { country: 'IL' },
      customer: {
        country: 'IL',
        name: 'Press Zone Customer',
      },
      supplyType: 'services',
      lineItems: [
        {
          description: 'Monthly plugin subscription',
          quantity: 1,
          unitAmountMinor: 10_000n,
        },
      ],
      idempotencyKey: 'settlement-dedup',
    })
    const second = await issueSettlementInvoice(db, {
      supplier: { country: 'IL' },
      customer: {
        country: 'IL',
        name: 'Press Zone Customer',
      },
      supplyType: 'services',
      lineItems: [
        {
          description: 'Monthly plugin subscription',
          quantity: 1,
          unitAmountMinor: 10_000n,
        },
      ],
      idempotencyKey: 'settlement-dedup',
    })

    expect(first).toEqual(second)
    expect(provider.issueCallCount.current).toBe(1)
    expect(await countInvoiceDocuments(db)).toBe(1)
  })
})
