import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { eq, inArray } from 'drizzle-orm'
import { createOrder, order } from '@platform-modules/commerce-orders'
import type { TransactionalDatabase } from '@platform-modules/db'
import { startPg } from './pg-harness.js'
import type { CheckoutDbSchema } from './schema.js'
import { reconcileStuckCharges } from './reconcile/index.js'
import {
  ageOrder,
  buildCheckoutDeps,
  BUYER_ID,
  seedDigitalVariant,
  setOrderStatus,
  VARIANT_DIGITAL,
} from './test-fixtures.js'

describe('reconcileStuckCharges (real Postgres)', () => {
  let db: TransactionalDatabase<CheckoutDbSchema>
  let stop: (() => Promise<void>) | undefined

  beforeAll(async () => {
    const pg = await startPg()
    db = pg.db
    stop = pg.stop
    await seedDigitalVariant(db, { variantId: VARIANT_DIGITAL, amount: 1000n })
  }, 120_000)

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

  async function createChargingOrder() {
    const created = await db.transaction((tx) =>
      createOrder(tx, {
        idempotencyKey: crypto.randomUUID(),
        buyerRef: { userId: BUYER_ID },
        currency: 'USD',
        priceMode: 'exclusive',
        subtotal: 1000n,
        tax: 0n,
        discount: 0n,
        total: 1000n,
        lines: [
          {
            variantId: VARIANT_DIGITAL,
            kind: 'digital',
            qty: 1,
            unitPrice: 1000n,
            lineTotal: 1000n,
            currency: 'USD',
          },
        ],
        splits: [{ vendorId: null, amount: 1000n, funder: 'platform' }],
      }),
    )
    await setOrderStatus(db, created.id, 'charging')
    return created
  }

  it('reconcile-lists-stuck — returns only aged charging orders', async () => {
    const aged = await createChargingOrder()
    const fresh = await createChargingOrder()
    await ageOrder(db, aged.id, 20 * 60 * 1000)

    const deps = buildCheckoutDeps(db)
    const { stuck } = await reconcileStuckCharges(deps, { olderThanMs: 15 * 60 * 1000 })

    expect(stuck.map((o) => o.id)).toContain(aged.id)
    expect(stuck.map((o) => o.id)).not.toContain(fresh.id)
  })

  it('reconcile-bounded-oldest-first — returns oldest N aged charging orders, pure read', async () => {
    const minutesAgo = [35, 30, 25, 20] as const
    const created: { id: string; updatedAt: Date }[] = []

    for (const minutes of minutesAgo) {
      const row = await createChargingOrder()
      const updatedAt = new Date(Date.now() - minutes * 60 * 1000)
      await db.update(order).set({ updatedAt }).where(eq(order.id, row.id))
      created.push({ id: row.id, updatedAt })
    }

    const expectedOldestIds = created
      .slice()
      .sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime())
      .slice(0, 3)
      .map((row) => row.id)
    const excludedId = created
      .slice()
      .sort((a, b) => a.updatedAt.getTime() - b.updatedAt.getTime())[3]!.id

    const deps = buildCheckoutDeps(db)
    const { stuck } = await reconcileStuckCharges(deps, { limit: 3 })

    expect(stuck).toHaveLength(3)
    expect(new Set(stuck.map((o) => o.id))).toEqual(new Set(expectedOldestIds))
    expect(stuck.map((o) => o.id)).not.toContain(excludedId)

    const statuses = await db
      .select({ id: order.id, status: order.status })
      .from(order)
      .where(
        inArray(
          order.id,
          created.map((row) => row.id),
        ),
      )
    expect(statuses).toHaveLength(4)
    for (const row of statuses) {
      expect(row.status).toBe('charging')
    }
  })
})
