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('releases a definitively rejected claim so a retry calls the provider again', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-error')
    let reject = true
    const provider = new MockInvoiceProvider(async () => {
      if (reject) throw new Error('provider exploded')
      return createInvoiceResult(spec.idempotencyKey)
    })

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

    expect(first).toEqual({
      ok: false,
      error: { code: 'PROVIDER_REJECTED', issued: 'no', message: 'provider exploded' },
    })
    expect(second).toEqual({ ok: true, result: createInvoiceResult(spec.idempotencyKey) })
    expect(provider.issueCallCount.current).toBe(2)
    expect(await countInvoiceDocuments(db)).toBe(1)
  })

  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 with a fresh token that fences the prior claimant', 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, claim_token, provider_called_at, created_at
      )
      VALUES (${spec.idempotencyKey}, '', '', '', 'old-token', NULL, NOW() - INTERVAL '10 minutes')
    `)

    const result = await issueInvoice(db, provider, { apiKey: 'secret' }, spec)
    const staleStamp = await db.execute(sql`
      UPDATE invoice_document
      SET provider_called_at = NOW()
      WHERE idempotency_key = ${spec.idempotencyKey}
        AND claim_token = 'old-token'
      RETURNING idempotency_key
    `)

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

  it('deletes a pending claim only when the release token owns it', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-token-release')

    await db.execute(sql`
      INSERT INTO invoice_document (
        idempotency_key, document_id, document_number, document_url, claim_token
      )
      VALUES (${spec.idempotencyKey}, '', '', '', 'right-token')
    `)

    const wrongRelease = await db.execute(sql`
      DELETE FROM invoice_document
      WHERE idempotency_key = ${spec.idempotencyKey}
        AND document_id = ''
        AND claim_token = 'wrong-token'
      RETURNING idempotency_key
    `)
    const rightRelease = await db.execute(sql`
      DELETE FROM invoice_document
      WHERE idempotency_key = ${spec.idempotencyKey}
        AND document_id = ''
        AND claim_token = 'right-token'
      RETURNING idempotency_key
    `)

    expect(unwrapTestRows(wrongRelease)).toHaveLength(0)
    expect(unwrapTestRows(rightRelease)).toHaveLength(1)
  })

  it('requires reconciliation for a stale pending row after the provider was called', async () => {
    const db = await createTestDb()
    const spec = createDocumentSpec('idem-invoice-stale-provider-called')
    const provider = new MockInvoiceProvider()

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

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

    expect(result).toEqual({
      ok: false,
      error: {
        code: 'RECONCILE_REQUIRED',
        issued: 'unknown',
        message: expect.stringContaining('idempotency key'),
      },
    })
    expect(result.ok || result.error.message).toContain('remarks')
    expect(provider.issueCallCount.current).toBe(0)
  }, 10_000)
})

function unwrapTestRows(result: unknown): unknown[] {
  return (Array.isArray(result) ? result : (result as { rows?: unknown[] }).rows) ?? []
}
