import { eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { claimForCharge } from './claim-for-charge.js'
import { createOrder } from './create-order.js'
import {
  isOrderChargeConflictError,
  isOrderNotChargeableError,
} from './errors.js'
import { markPaid } from './mark-paid.js'
import { pushSchema } from './migrate.js'
import { order, ordersSchema } from './schema.js'
import type { NewOrder } from './types.js'

const VARIANT_A = '11111111-1111-4111-8111-111111111111'
const BUYER_ID = '33333333-3333-4333-8333-333333333333'
const CHARGE_REF = 'ch_test_abc123'
const OTHER_CHARGE_REF = 'ch_test_xyz789'

async function freshDb() {
  const db = createPgliteClient({ schema: ordersSchema })
  await pushSchema(db)
  return db
}

function baseOrder(): NewOrder {
  return {
    idempotencyKey: crypto.randomUUID(),
    buyerRef: { userId: BUYER_ID },
    currency: 'USD',
    priceMode: 'exclusive',
    subtotal: 1000n,
    tax: 100n,
    discount: 0n,
    total: 1100n,
    lines: [
      {
        variantId: VARIANT_A,
        kind: 'physical',
        qty: 2,
        unitPrice: 500n,
        lineTotal: 1000n,
        currency: 'USD',
        vendorId: null,
      },
    ],
    splits: [{ vendorId: null, amount: 1100n, funder: 'platform' }],
  }
}

async function chargingOrder(db: Awaited<ReturnType<typeof freshDb>>) {
  const created = await db.transaction((tx) => createOrder(tx, baseOrder()))
  await db.transaction((tx) => claimForCharge(tx, created.id))
  return created.id
}

describe('markPaid', () => {
  it('transitions charging to paid', async () => {
    const db = await freshDb()
    const orderId = await chargingOrder(db)

    await db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF))

    const [row] = await db.select().from(order).where(eq(order.id, orderId))
    expect(row?.status).toBe('paid')
    expect(row?.chargeRef).toBe(CHARGE_REF)
  })

  it('is idempotent when replaying the same chargeRef', async () => {
    const db = await freshDb()
    const orderId = await chargingOrder(db)

    await db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF))
    await expect(
      db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF)),
    ).resolves.toBeUndefined()
  })

  it('throws OrderChargeConflictError on a different chargeRef replay', async () => {
    const db = await freshDb()
    const orderId = await chargingOrder(db)

    await db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF))

    await expect(
      db.transaction((tx) => markPaid(tx, orderId, OTHER_CHARGE_REF)),
    ).rejects.toSatisfy((e) => isOrderChargeConflictError(e))
  })

  it('rejects markPaid on pending with OrderNotChargeableError', async () => {
    const db = await freshDb()
    const created = await db.transaction((tx) => createOrder(tx, baseOrder()))

    await expect(
      db.transaction((tx) => markPaid(tx, created.id, CHARGE_REF)),
    ).rejects.toSatisfy((e) => isOrderNotChargeableError(e))
  })

  it('is idempotent when the same chargeRef replays after a refund (div 8 — provider redelivery window)', async () => {
    const db = await freshDb()
    const orderId = await chargingOrder(db)
    await db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF))
    await db.update(order).set({ status: 'refunded' }).where(eq(order.id, orderId))

    await expect(
      db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF)),
    ).resolves.toBeUndefined()

    const [row] = await db.select().from(order).where(eq(order.id, orderId))
    expect(row?.status).toBe('refunded')
    expect(row?.chargeRef).toBe(CHARGE_REF)
  })

  it('throws OrderChargeConflictError on a different chargeRef after a refund', async () => {
    const db = await freshDb()
    const orderId = await chargingOrder(db)
    await db.transaction((tx) => markPaid(tx, orderId, CHARGE_REF))
    await db.update(order).set({ status: 'refunded' }).where(eq(order.id, orderId))

    await expect(
      db.transaction((tx) => markPaid(tx, orderId, OTHER_CHARGE_REF)),
    ).rejects.toSatisfy((e) => isOrderChargeConflictError(e))
  })
})
