import { sql } from 'drizzle-orm'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { createOrder } from '@platform-modules/commerce-orders'
import type { NewOrder, Order, ProductKind } from '@platform-modules/commerce-orders'
import type { TransactionalDatabase } from '@platform-modules/db'
import { fulfillOrder } from './fulfill.js'
import { startPg } from './pg-harness.js'
import type { FulfillmentDbSchema } from './schema.js'
import { createMemoryFulfillmentPorts } from './testing.js'

const BUYER_ID = '33333333-3333-4333-8333-333333333333'
const VARIANT_PHYSICAL = '11111111-1111-4111-8111-111111111111'
const VARIANT_DIGITAL = '22222222-2222-4222-8222-222222222222'
const VARIANT_VOUCHER = '44444444-4444-4444-8444-444444444444'
const DIGITAL_BLOB_KEY = 'blob/digital/smoke-track.zip'

function mixedKindOrderInput(): NewOrder {
  return {
    idempotencyKey: crypto.randomUUID(),
    buyerRef: { userId: BUYER_ID },
    currency: 'USD',
    priceMode: 'exclusive',
    subtotal: 400n,
    tax: 0n,
    discount: 0n,
    total: 400n,
    lines: [
      {
        variantId: VARIANT_PHYSICAL,
        kind: 'physical',
        qty: 1,
        unitPrice: 100n,
        lineTotal: 100n,
        currency: 'USD',
        vendorId: null,
      },
      {
        variantId: VARIANT_DIGITAL,
        kind: 'digital',
        qty: 1,
        unitPrice: 100n,
        lineTotal: 100n,
        currency: 'USD',
        vendorId: null,
      },
      {
        variantId: VARIANT_VOUCHER,
        kind: 'voucher',
        qty: 2,
        unitPrice: 100n,
        lineTotal: 200n,
        currency: 'USD',
        vendorId: 'vendor-a',
      },
    ],
    splits: [{ vendorId: null, amount: 400n, funder: 'platform' }],
  }
}

function digitalOnlyOrderInput(): NewOrder {
  return {
    idempotencyKey: crypto.randomUUID(),
    buyerRef: { userId: BUYER_ID },
    currency: 'USD',
    priceMode: 'exclusive',
    subtotal: 100n,
    tax: 0n,
    discount: 0n,
    total: 100n,
    lines: [
      {
        variantId: VARIANT_DIGITAL,
        kind: 'digital',
        qty: 1,
        unitPrice: 100n,
        lineTotal: 100n,
        currency: 'USD',
        vendorId: null,
      },
    ],
    splits: [{ vendorId: null, amount: 100n, funder: 'platform' }],
  }
}

function voucherOnlyOrderInput(): NewOrder {
  return {
    idempotencyKey: crypto.randomUUID(),
    buyerRef: { userId: BUYER_ID },
    currency: 'USD',
    priceMode: 'exclusive',
    subtotal: 200n,
    tax: 0n,
    discount: 0n,
    total: 200n,
    lines: [
      {
        variantId: VARIANT_VOUCHER,
        kind: 'voucher',
        qty: 2,
        unitPrice: 100n,
        lineTotal: 200n,
        currency: 'USD',
        vendorId: 'vendor-a',
      },
    ],
    splits: [{ vendorId: null, amount: 200n, funder: 'platform' }],
  }
}

function firstCount(res: unknown): number {
  const rows = (Array.isArray(res) ? res : (res as { rows?: Array<{ count: number }> }).rows) ?? []
  return Number(rows[0]?.count ?? 0)
}

describe('fulfillOrder (real Postgres)', () => {
  let db: TransactionalDatabase<FulfillmentDbSchema>
  let stop: (() => Promise<void>) | undefined
  let order: Order

  beforeAll(async () => {
    const pgResult = await startPg()
    db = pgResult.db
    stop = pgResult.stop

    order = await db.transaction((tx) => createOrder(tx, mixedKindOrderInput()))
  }, 120_000)

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

  function createPorts() {
    return createMemoryFulfillmentPorts({
      db,
      resolveBlobKey: async (line) =>
        line.variantId === VARIANT_DIGITAL ? DIGITAL_BLOB_KEY : `blob:${line.variantId}`,
    })
  }

  async function countGrants(orderId: string): Promise<number> {
    const res = await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM access_grant WHERE order_id = ${orderId}::uuid
    `)
    return firstCount(res)
  }

  async function countVouchers(orderId: string): Promise<number> {
    const res = await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM voucher WHERE order_id = ${orderId}::uuid
    `)
    return firstCount(res)
  }

  async function countUnredeemedVouchers(orderId: string): Promise<number> {
    const res = await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM voucher
      WHERE order_id = ${orderId}::uuid
        AND state = 'UNREDEEMED'
    `)
    return firstCount(res)
  }

  async function countShipments(orderId: string): Promise<number> {
    const res = await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM shipment WHERE order_id = ${orderId}::uuid
    `)
    return firstCount(res)
  }

  it('mixed-kind fulfillOrder creates grant=1, vouchers=2, shipment=1 with overall fulfilled', async () => {
    const ports = createPorts()
    const result = await fulfillOrder(ports, order)

    expect(result.overall).toBe('fulfilled')
    expect(result.lines).toHaveLength(3)
    expect(result.lines.every((line) => line.kind !== 'unfulfillable')).toBe(true)
    expect(await countGrants(order.id)).toBe(1)
    expect(await countVouchers(order.id)).toBe(2)
    expect(await countShipments(order.id)).toBe(1)
    expect(ports.notifications).toHaveLength(2)
  })

  it('replayed fulfillOrder does not duplicate non-idempotent shipment rows', async () => {
    const ports = createPorts()
    const replay = await fulfillOrder(ports, order)

    expect(replay.overall).toBe('fulfilled')
    expect(await countGrants(order.id)).toBe(1)
    expect(await countVouchers(order.id)).toBe(2)
    expect(await countShipments(order.id)).toBe(1)
    expect(ports.notifications).toHaveLength(0)
  })

  it('corrupt line kind is unfulfillable while other lines still fulfill', async () => {
    const corruptOrder = await db.transaction((tx) => createOrder(tx, mixedKindOrderInput()))
    const corruptLine = corruptOrder.lines[0]!
    const corruptOrderView: Order = {
      ...corruptOrder,
      lines: corruptOrder.lines.map((line) =>
        line.id === corruptLine.id
          ? { ...line, kind: 'bogus' as ProductKind }
          : line,
      ),
    }

    const ports = createPorts()
    const result = await fulfillOrder(ports, corruptOrderView)

    expect(result.overall).toBe('partial')
    const corruptOutcome = result.lines.find((line) => line.lineId === corruptLine.id)
    expect(corruptOutcome?.kind).toBe('unfulfillable')
    expect(result.lines.filter((line) => line.kind !== 'unfulfillable')).toHaveLength(2)
    expect(await countGrants(corruptOrder.id)).toBe(1)
    expect(await countVouchers(corruptOrder.id)).toBe(2)
    expect(await countShipments(corruptOrder.id)).toBe(0)
  })

  it('secaudit-fulfillment-notify-failure-isolated', async () => {
    const digitalOrder = await db.transaction((tx) => createOrder(tx, digitalOnlyOrderInput()))
    const digitalLine = digitalOrder.lines[0]!
    let notifyAttempts = 0

    const throwingPorts = createMemoryFulfillmentPorts({
      db,
      resolveBlobKey: async (line) =>
        line.variantId === VARIANT_DIGITAL ? DIGITAL_BLOB_KEY : `blob:${line.variantId}`,
      notify: async () => {
        notifyAttempts += 1
        throw new Error('notify down')
      },
    })

    const first = await fulfillOrder(throwingPorts, digitalOrder)

    expect(first.overall).toBe('fulfilled')
    expect(first.lines).toHaveLength(1)
    expect(first.lines.filter((line) => line.lineId === digitalLine.id)).toHaveLength(1)
    expect(first.lines[0]?.kind).toBe('digital')
    expect(await countGrants(digitalOrder.id)).toBe(1)
    expect(notifyAttempts).toBe(1)

    const markerRes = await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM order_step
      WHERE order_id = ${digitalOrder.id}::uuid
        AND step_id = ${`notify:${digitalLine.id}`}
    `)
    expect(firstCount(markerRes)).toBe(0)

    let retryNotifyAttempts = 0
    const retryPorts = createMemoryFulfillmentPorts({
      db,
      resolveBlobKey: async (line) =>
        line.variantId === VARIANT_DIGITAL ? DIGITAL_BLOB_KEY : `blob:${line.variantId}`,
      notify: async () => {
        retryNotifyAttempts += 1
      },
    })

    await fulfillOrder(retryPorts, digitalOrder)
    expect(retryNotifyAttempts).toBe(1)
    expect(retryPorts.notifications).toHaveLength(1)

    const markerAfterRetry = await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM order_step
      WHERE order_id = ${digitalOrder.id}::uuid
        AND step_id = ${`notify:${digitalLine.id}`}
    `)
    expect(firstCount(markerAfterRetry)).toBe(1)

    const dedupPorts = createMemoryFulfillmentPorts({
      db,
      resolveBlobKey: async (line) =>
        line.variantId === VARIANT_DIGITAL ? DIGITAL_BLOB_KEY : `blob:${line.variantId}`,
      notify: async () => {
        throw new Error('notify must not run on dedup replay')
      },
    })

    await fulfillOrder(dedupPorts, digitalOrder)
    expect(dedupPorts.notifications).toHaveLength(0)
  })

  it('secaudit-fulfillment-notify-failure-isolated-voucher', async () => {
    const voucherOrder = await db.transaction((tx) => createOrder(tx, voucherOnlyOrderInput()))
    const voucherLine = voucherOrder.lines[0]!
    const voucherQty = voucherLine.qty
    let notifyAttempts = 0

    const resilientPorts = createMemoryFulfillmentPorts({
      db,
      resolveBlobKey: async (line) =>
        line.variantId === VARIANT_DIGITAL ? DIGITAL_BLOB_KEY : `blob:${line.variantId}`,
      notify: async (notification) => {
        notifyAttempts += 1
        if (notifyAttempts === 1) {
          throw new Error('notify down')
        }
        expect(notification.kind).toBe('voucher-issued')
      },
    })

    const first = await fulfillOrder(resilientPorts, voucherOrder)

    expect(first.overall).toBe('fulfilled')
    expect(first.lines).toHaveLength(1)
    expect(first.lines.filter((line) => line.lineId === voucherLine.id)).toHaveLength(1)
    expect(first.lines[0]?.kind).toBe('voucher')
    expect(await countVouchers(voucherOrder.id)).toBe(voucherQty)
    expect(await countUnredeemedVouchers(voucherOrder.id)).toBe(voucherQty)
    expect(notifyAttempts).toBe(1)

    const markerRes = await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM order_step
      WHERE order_id = ${voucherOrder.id}::uuid
        AND step_id = ${`notify:${voucherLine.id}`}
    `)
    expect(firstCount(markerRes)).toBe(0)

    await fulfillOrder(resilientPorts, voucherOrder)
    expect(notifyAttempts).toBe(2)
    expect(resilientPorts.notifications.filter((n) => n.kind === 'voucher-issued')).toHaveLength(2)

    const markerAfterRetry = await db.execute(sql`
      SELECT COUNT(*)::int AS count
      FROM order_step
      WHERE order_id = ${voucherOrder.id}::uuid
        AND step_id = ${`notify:${voucherLine.id}`}
    `)
    expect(firstCount(markerAfterRetry)).toBe(1)

    const dedupPorts = createMemoryFulfillmentPorts({
      db,
      resolveBlobKey: async (line) =>
        line.variantId === VARIANT_DIGITAL ? DIGITAL_BLOB_KEY : `blob:${line.variantId}`,
      notify: async () => {
        throw new Error('notify must not run on dedup replay')
      },
    })

    await fulfillOrder(dedupPorts, voucherOrder)
    expect(dedupPorts.notifications).toHaveLength(0)
  })
})
