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 { isOrderNotChargeableError, isOrderValidationError } from '../errors.js'
import { markPaid } from '../mark-paid.js'
import { pushSchema } from '../migrate.js'
import { order, ordersSchema, refundIntent } from '../schema.js'
import type { NewOrder } from '../types.js'
import { executeRefund } from './execute-refund.js'
import { reconcileStuckRefunds } from './reconcile-stuck-refunds.js'
import type { RefundPort } from './types.js'

const VARIANT_A = '11111111-1111-4111-8111-111111111111'
const BUYER_ID = '33333333-3333-4333-8333-333333333333'
const CHARGE_REF = 'ch_test_reconcile'
const ADMIN = { isAdmin: true as const }

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

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

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

function refundLine(orderLineId: string, amount: bigint) {
  return { orderLineId, qty: 1, amount }
}

describe('reconcileStuckRefunds', () => {
  it('throw-then-reconcile retries with identical refundKey', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    let capturedKey = ''

    const throwPort: RefundPort = async () => {
      throw new Error('transient')
    }

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 100n)], throwPort, ADMIN),
    ).rejects.toThrow('transient')

    const [intent] = await db
      .select()
      .from(refundIntent)
      .where(eq(refundIntent.orderId, created.id))
    expect(intent?.status).toBe('pending')

    const port2: RefundPort = async (req) => {
      capturedKey = req.refundKey
      return {
        kind: 'refunded',
        refundKey: req.refundKey,
        chargeKey: req.chargeKey,
        providerRef: 're_reconciled',
        amount: req.amount,
        currency: 'USD',
      }
    }

    const result = await reconcileStuckRefunds(db, port2)
    expect(result.settled).toBe(1)
    expect(capturedKey).toBe(`refund:${intent!.id}`)

    const intents = await db.select().from(refundIntent).where(eq(refundIntent.orderId, created.id))
    expect(intents).toHaveLength(1)
    expect(intents[0]?.status).toBe('executed')

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

  it('idempotent settle on already-executed intent is no-op', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const port: RefundPort = async (req) => ({
      kind: 'refunded',
      refundKey: req.refundKey,
      chargeKey: req.chargeKey,
      providerRef: 're_done',
      amount: req.amount,
      currency: 'USD',
    })

    await executeRefund(db, created.id, [refundLine(lineId, 100n)], port, ADMIN)

    const result = await reconcileStuckRefunds(db, port)
    expect(result.settled).toBe(0)
    expect(result.scanned).toBe(0)
  })

  it('async-pending stays pending', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const pendingPort: RefundPort = async () => ({ kind: 'pending' })

    await executeRefund(db, created.id, [refundLine(lineId, 60n)], pendingPort, ADMIN)

    const result = await reconcileStuckRefunds(db, pendingPort)
    expect(result.stillPending).toBe(1)
    expect(result.settled).toBe(0)
  })

  it('one bad intent does not block others', async () => {
    const db = await freshDb()
    const order1 = await paidOrder(db)
    const order2 = await paidOrder(db)
    const line1 = order1.lines[0]!.id
    const line2 = order2.lines[0]!.id
    const pendingPort: RefundPort = async () => ({ kind: 'pending' })

    await executeRefund(db, order1.id, [refundLine(line1, 30n)], pendingPort, ADMIN)
    await executeRefund(db, order2.id, [refundLine(line2, 40n)], pendingPort, ADMIN)

    let call = 0
    const port: RefundPort = async (req) => {
      call++
      if (call === 1) {
        throw new Error('first fails')
      }
      return {
        kind: 'refunded',
        refundKey: req.refundKey,
        chargeKey: req.chargeKey,
        providerRef: 're_second',
        amount: req.amount,
        currency: 'USD',
      }
    }

    const result = await reconcileStuckRefunds(db, port, { limit: 10 })
    expect(result.scanned).toBe(2)
    expect(result.settled).toBe(1)
  })

  it('port throw is returned as a failure, never swallowed', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const pendingPort: RefundPort = async () => ({ kind: 'pending' })

    await executeRefund(db, created.id, [refundLine(lineId, 50n)], pendingPort, ADMIN)

    const outage: RefundPort = async () => {
      throw new Error('provider down')
    }

    const result = await reconcileStuckRefunds(db, outage)
    expect(result.scanned).toBe(1)
    expect(result.settled).toBe(0)
    expect(result.stillPending).toBe(0)
    expect(result.failures).toHaveLength(1)
    expect(result.failures[0]?.orderId).toBe(created.id)
    expect((result.failures[0]?.error as Error).message).toBe('provider down')

    const [intent] = await db
      .select()
      .from(refundIntent)
      .where(eq(refundIntent.orderId, created.id))
    expect(result.failures[0]?.intentId).toBe(intent!.id)
    expect(intent?.status).toBe('pending')
  })

  it('unsafe intent amount never reaches the port as a truncated Number', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const unsafe = BigInt(Number.MAX_SAFE_INTEGER) + 1n

    await db.insert(refundIntent).values({
      id: crypto.randomUUID(),
      orderId: created.id,
      seq: 1,
      amount: unsafe,
      status: 'pending',
      refundKey: 'refund:unsafe-amount-fixture',
    })

    const calls: number[] = []
    const port: RefundPort = async (req) => {
      calls.push(req.amount)
      return {
        kind: 'refunded',
        refundKey: req.refundKey,
        chargeKey: req.chargeKey,
        providerRef: 're_never',
        amount: req.amount,
        currency: 'USD',
      }
    }

    const result = await reconcileStuckRefunds(db, port)
    expect(calls).toHaveLength(0)
    expect(result.settled).toBe(0)
    expect(result.failures).toHaveLength(1)
    expect(isOrderValidationError(result.failures[0]?.error)).toBe(true)
  })

  it('pending intent on an order without chargeRef is a failure, not a silent skip', async () => {
    const db = await freshDb()
    const created = await db.transaction((tx) => createOrder(tx, orderTotal100()))

    await db.insert(refundIntent).values({
      id: crypto.randomUUID(),
      orderId: created.id,
      seq: 1,
      amount: 10n,
      status: 'pending',
      refundKey: 'refund:no-charge-ref-fixture',
    })

    const calls: string[] = []
    const port: RefundPort = async (req) => {
      calls.push(req.refundKey)
      return { kind: 'pending' }
    }

    const result = await reconcileStuckRefunds(db, port)
    expect(calls).toHaveLength(0)
    expect(result.failures).toHaveLength(1)
    expect(isOrderNotChargeableError(result.failures[0]?.error)).toBe(true)
  })

  it('honors limit and olderThan', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const pendingPort: RefundPort = async () => ({ kind: 'pending' })

    await executeRefund(db, created.id, [refundLine(lineId, 10n)], pendingPort, ADMIN)
    await executeRefund(db, created.id, [refundLine(lineId, 10n)], pendingPort, ADMIN)
    await executeRefund(db, created.id, [refundLine(lineId, 10n)], pendingPort, ADMIN)

    const limited = await reconcileStuckRefunds(db, pendingPort, { limit: 2 })
    expect(limited.scanned).toBe(2)

    const skipped = await reconcileStuckRefunds(db, pendingPort, {
      olderThan: new Date(0),
      limit: 10,
    })
    expect(skipped.scanned).toBe(0)
  })
})
