import { sql } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { MockInvoiceProvider, issueInvoice, type DocumentSpec, type InvoiceDocumentResult } from './index.js'
import {
  countInvoiceDocuments,
  createDocumentSpec,
  createInvoiceResult,
  createTestDb,
} from './test-fixture.js'

describe('@platform-modules/invoicing', () => {
  it('issues via the mock provider and replays the stored result for the same idempotencyKey', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-success')
    const expected = createInvoiceResult(spec.idempotencyKey)
    const provider = new MockInvoiceProvider(
      async (_credential: unknown, callSpec: DocumentSpec): Promise<InvoiceDocumentResult> =>
        createInvoiceResult(callSpec.idempotencyKey),
    )

    const first = await issueInvoice(db, provider, { apiKey: 'secret' }, spec)
    const second = await issueInvoice(db, provider, { apiKey: 'secret' }, spec)

    expect(first).toEqual({ ok: true, result: expected })
    expect(second).toEqual(first)
    expect(provider.issueCallCount.current).toBe(1)
    expect(await countInvoiceDocuments(db)).toBe(1)
  })

  it('returns ok:false and does not write an idempotency record when the provider throws', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-error')
    const provider = new MockInvoiceProvider(async () => {
      throw new Error('provider exploded')
    })

    const result = await issueInvoice(db, provider, { apiKey: 'secret' }, spec)

    expect(result).toEqual({
      ok: false,
      error: { code: 'PROVIDER_REJECTED', message: 'provider exploded' },
    })
    expect(provider.issueCallCount.current).toBe(1)
    expect(await countInvoiceDocuments(db)).toBe(0)
  })

  it('calls the provider exactly once when two concurrent issueInvoice calls race the same idempotencyKey', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-race')
    const expected = createInvoiceResult(spec.idempotencyKey)
    const provider = new MockInvoiceProvider(
      async (_credential: unknown, callSpec: DocumentSpec): Promise<InvoiceDocumentResult> => {
        await new Promise((resolve) => setTimeout(resolve, 10))
        return createInvoiceResult(callSpec.idempotencyKey)
      },
    )

    const [first, second] = await Promise.all([
      issueInvoice(db, provider, { apiKey: 'secret' }, spec),
      issueInvoice(db, provider, { apiKey: 'secret' }, spec),
    ])

    expect(first).toEqual({ ok: true, result: expected })
    expect(second).toEqual(first)
    expect(provider.issueCallCount.current).toBe(1)
    expect(await countInvoiceDocuments(db)).toBe(1)
  })

  it('reclaims a stale pending row (crashed claimant) instead of wedging the idempotencyKey forever', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-stale-claim')
    const expected = createInvoiceResult(spec.idempotencyKey)
    const provider = new MockInvoiceProvider(
      async (_credential: unknown, callSpec: DocumentSpec): Promise<InvoiceDocumentResult> =>
        createInvoiceResult(callSpec.idempotencyKey),
    )

    await db.execute(sql`
      INSERT INTO invoice_document (idempotency_key, document_id, document_number, document_url, created_at)
      VALUES (${spec.idempotencyKey}, '', '', '', NOW() - INTERVAL '10 minutes')
    `)

    const result = await issueInvoice(db, provider, { apiKey: 'secret' }, spec)

    expect(result).toEqual({ ok: true, result: expected })
    expect(provider.issueCallCount.current).toBe(1)
    expect(await countInvoiceDocuments(db)).toBe(1)
  })
})
