import { sql } from 'drizzle-orm'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import type { TransactionalDatabase } from '@platform-modules/db'
import { isLabelPurchaseError } from '../errors.js'
import { startPg } from '../pg-harness.js'
import type { FulfillmentSchema } from '../schema.js'
import type { Address } from '../types.js'
import { createMemoryCarrierAdapter } from '../testing.js'
import { createShipment } from './shipment.js'
import { buyShippingLabel } from './label.js'

const ORDER_ID = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'

const ADDRESS: Address = {
  line1: '7 Label Ln',
  city: 'Freightville',
  postalCode: '10001',
  country: 'US',
}

describe('buyShippingLabel', () => {
  let db: TransactionalDatabase<FulfillmentSchema>
  let stop: (() => Promise<void>) | undefined

  beforeAll(async () => {
    const pgResult = await startPg()
    db = pgResult.db
    stop = pgResult.stop
  }, 120_000)

  afterAll(async () => {
    await stop?.()
  }, 30_000)

  async function seedPendingShipment() {
    return db.transaction((tx) =>
      createShipment(tx, {
        orderId: ORDER_ID,
        lineIds: ['line-physical-2'],
        address: ADDRESS,
      }),
    )
  }

  it('calls carrier.buyLabel once on first purchase', async () => {
    const shipment = await seedPendingShipment()
    const carrier = createMemoryCarrierAdapter()

    const label = await buyShippingLabel(db, shipment.id, carrier)

    expect(carrier.buyLabelCallCount).toBe(1)
    expect(label.trackingNumber).toBeTruthy()
    expect(label.id).toBe(`label-label:${shipment.id}`)
  })

  it('retry with the same idempotency key does not double-buy (M4)', async () => {
    const shipment = await seedPendingShipment()
    const carrier = createMemoryCarrierAdapter()

    await buyShippingLabel(db, shipment.id, carrier)
    await buyShippingLabel(db, shipment.id, carrier)

    expect(carrier.buyLabelCallCount).toBe(1)
  })

  it('carrier failure throws LabelPurchaseError and leaves shipment pending', async () => {
    const shipment = await seedPendingShipment()
    const carrier = createMemoryCarrierAdapter({
      buyLabel: async () => {
        throw new Error('carrier unavailable')
      },
    })

    await expect(buyShippingLabel(db, shipment.id, carrier)).rejects.toSatisfy(
      (e: unknown) => isLabelPurchaseError(e),
    )

    const pending = await db.execute(sql`
      SELECT status, label_id, tracking_number
      FROM shipment
      WHERE id = ${shipment.id}::uuid
    `)

    const rows = (Array.isArray(pending) ? pending : (pending as { rows?: unknown[] }).rows) ?? []
    const row = rows[0] as { status: string; label_id: string | null; tracking_number: string | null }
    expect(row.status).toBe('pending')
    expect(row.label_id).toBeNull()
    expect(row.tracking_number).toBeNull()
  })
})
