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,
  isOrderNotFoundError,
  isOrderValidationError,
  isRefundExceedsPaidError,
} from '../errors.js'
import { markPaid } from '../mark-paid.js'
import { markUnfulfillable } from '../mark-unfulfillable.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 type { RefundPort } from './types.js'

const VARIANT_A = '11111111-1111-4111-8111-111111111111'
const VARIANT_B = '22222222-2222-4222-8222-222222222222'
const BUYER_ID = '33333333-3333-4333-8333-333333333333'
const VENDOR_A = 'vendor-a'
const VENDOR_B = 'vendor-b'
const CHARGE_REF = 'ch_test_refund'
const ADMIN = { isAdmin: true as const }

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

function orderTotal100(over: Partial<NewOrder> = {}): 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' }],
    ...over,
  }
}

function multiVendorOrder(): 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: 50n,
        lineTotal: 50n,
        currency: 'USD',
        vendorId: VENDOR_A,
      },
      {
        variantId: VARIANT_B,
        kind: 'physical',
        qty: 1,
        unitPrice: 50n,
        lineTotal: 50n,
        currency: 'USD',
        vendorId: VENDOR_B,
      },
    ],
    splits: [
      { vendorId: VENDOR_A, amount: 50n, funder: 'vendor' },
      { vendorId: VENDOR_B, amount: 50n, funder: 'vendor' },
    ],
  }
}

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

function refundedPort(providerRef = 're_test_ok'): RefundPort {
  return async (req) => ({
    kind: 'refunded',
    refundKey: req.refundKey,
    chargeKey: req.chargeKey,
    providerRef,
    amount: req.amount,
    currency: 'USD',
  })
}

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

async function countIntents(db: Awaited<ReturnType<typeof freshDb>>, orderId: string) {
  const rows = await db.select().from(refundIntent).where(eq(refundIntent.orderId, orderId))
  return rows.length
}

describe('executeRefund', () => {
  it('happy path: full refund executes intent and sets order refunded', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

    const res = await executeRefund(
      db,
      created.id,
      [refundLine(lineId, 100n)],
      refundedPort(),
      ADMIN,
    )

    expect(res.kind).toBe('refunded')

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

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

  it('partial refund sets order partially_refunded', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

    await executeRefund(db, created.id, [refundLine(lineId, 40n)], refundedPort(), ADMIN)

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

  it('over-refund: second 60 on a 100 order throws RefundExceedsPaidError', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

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

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 60n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy((e) => isRefundExceedsPaidError(e) && e.context.orderId === created.id)
  })

  it('refund allowed on unfulfillable order', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    await db.transaction((tx) => markUnfulfillable(tx, created.id, 'no-stock'))
    const lineId = created.lines[0]!.id

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

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

  it('RefundPort pending leaves intent pending and counts in cap', 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 intents = await db.select().from(refundIntent).where(eq(refundIntent.orderId, created.id))
    expect(intents[0]?.status).toBe('pending')

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 60n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(isRefundExceedsPaidError)
  })

  it('RefundPort throw leaves intent pending, rethrows, blocks retry via cap', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const throwingPort: RefundPort = async () => {
      throw new Error('network timeout')
    }

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 60n)], throwingPort, ADMIN),
    ).rejects.toThrow('network timeout')

    const intents = await db.select().from(refundIntent).where(eq(refundIntent.orderId, created.id))
    expect(intents).toHaveLength(1)
    expect(intents[0]?.status).toBe('pending')
    expect(intents[0]?.refundKey).toBe(`refund:${intents[0]?.id}`)

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 60n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(isRefundExceedsPaidError)
  })

  it('rejects refund on unpaid order', async () => {
    const db = await freshDb()
    const created = await db.transaction((tx) => createOrder(tx, orderTotal100()))
    const lineId = created.lines[0]!.id

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 10n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(isOrderNotChargeableError)
  })

  it('buyer cannot self-refund (404 before intent)', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

    await expect(
      executeRefund(
        db,
        created.id,
        [refundLine(lineId, 10n)],
        refundedPort(),
        { userId: BUYER_ID },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)

    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('unrelated actor gets 404 before intent', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

    await expect(
      executeRefund(
        db,
        created.id,
        [refundLine(lineId, 10n)],
        refundedPort(),
        { userId: '99999999-9999-4999-8999-999999999999' },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)

    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('vendor cannot refund foreign line on multi-vendor order', async () => {
    const db = await freshDb()
    const created = await paidOrder(db, multiVendorOrder())
    const foreignLine = created.lines.find((l) => l.vendorId === VENDOR_B)!.id

    await expect(
      executeRefund(
        db,
        created.id,
        [refundLine(foreignLine, 50n)],
        refundedPort(),
        { vendorId: VENDOR_A },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)

    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('vendor can refund only own line', async () => {
    const db = await freshDb()
    const created = await paidOrder(db, multiVendorOrder())
    const ownLine = created.lines.find((l) => l.vendorId === VENDOR_A)!.id

    await executeRefund(
      db,
      created.id,
      [refundLine(ownLine, 50n)],
      refundedPort(),
      { vendorId: VENDOR_A },
    )

    expect(await countIntents(db, created.id)).toBe(1)
  })

  it('admin can refund', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

    await executeRefund(db, created.id, [refundLine(lineId, 10n)], refundedPort(), ADMIN)
    expect(await countIntents(db, created.id)).toBe(1)
  })

  it('empty lines → OrderValidationError with zero intents', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)

    await expect(executeRefund(db, created.id, [], refundedPort(), ADMIN)).rejects.toSatisfy(
      (e) => isOrderValidationError(e) && (e as { field: string }).field === 'lines',
    )

    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('phantom orderLineId → 404 for admin and vendor with zero intents', async () => {
    const db = await freshDb()
    const created = await paidOrder(db, multiVendorOrder())
    const phantom = '44444444-4444-4444-8444-444444444444'

    await expect(
      executeRefund(db, created.id, [refundLine(phantom, 10n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(isOrderNotFoundError)
    expect(await countIntents(db, created.id)).toBe(0)

    await expect(
      executeRefund(
        db,
        created.id,
        [refundLine(phantom, 10n)],
        refundedPort(),
        { vendorId: VENDOR_A },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)
    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('vendor cannot refund platform line', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id

    await expect(
      executeRefund(
        db,
        created.id,
        [refundLine(lineId, 10n)],
        refundedPort(),
        { vendorId: VENDOR_A },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)

    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('amount above MAX_SAFE_INTEGER → OrderValidationError with zero intents', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const tooLarge = BigInt(Number.MAX_SAFE_INTEGER) + 1n

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, tooLarge)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(
      (e) => isOrderValidationError(e) && (e as { field: string }).field === 'amount',
    )

    expect(await countIntents(db, created.id)).toBe(0)
  })

  it('secaudit-orders-refund-amount-positive', async () => {
    // P0 (Gate-4): a non-positive line amount poisons the M8 over-refund cap.
    // The cap SUMs refund_intent.amount, so a claimed -60 intent lets a later
    // +60 over-refund past `paid` (proven: 150 refunded on a 100 order).
    //
    // Oracle note: assert the ERROR TYPE, not just rejection/zero-rows. With the
    // DB CHECK (amount > 0) present, removing the PRE-tx1 code guard makes the
    // negative INSERT throw a Postgres constraint error and roll back to zero
    // rows — so a loose rejects.toThrow() or a countIntents===0 check ALONE still
    // passes with the guard gone. Requiring OrderValidationError('amount') (a
    // pre-tx1 code throw, never a pg error) is what reddens on guard removal.

    // (a) single negative-amount line → OrderValidationError('amount'), zero rows.
    const dbA = await freshDb()
    const createdA = await paidOrder(dbA)
    const lineA = createdA.lines[0]!.id

    await expect(
      executeRefund(dbA, createdA.id, [refundLine(lineA, -1n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(
      (e) => isOrderValidationError(e) && (e as { field: string }).field === 'amount',
    )
    expect(await countIntents(dbA, createdA.id)).toBe(0)

    // Σ-positive but per-line malformed: [100n, -40n] sums to 60n (passes the Σ
    // safe-int assert) yet carries a negative line — the per-line guard must
    // still reject it before tx1, leaving zero rows.
    await expect(
      executeRefund(
        dbA,
        createdA.id,
        [refundLine(lineA, 100n), refundLine(lineA, -40n)],
        refundedPort(),
        ADMIN,
      ),
    ).rejects.toSatisfy(
      (e) => isOrderValidationError(e) && (e as { field: string }).field === 'amount',
    )
    expect(await countIntents(dbA, createdA.id)).toBe(0)

    // (b) poison sequence: a rejected -60 must NOT have consumed cap. Then a legit
    // 90 succeeds, and a follow-up 60 correctly trips RefundExceedsPaidError
    // (90+60 > 100) — the cap holds, Σ executed ≤ total.
    const dbB = await freshDb()
    const createdB = await paidOrder(dbB)
    const lineB = createdB.lines[0]!.id

    await expect(
      executeRefund(dbB, createdB.id, [refundLine(lineB, -60n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(
      (e) => isOrderValidationError(e) && (e as { field: string }).field === 'amount',
    )
    expect(await countIntents(dbB, createdB.id)).toBe(0)

    await executeRefund(dbB, createdB.id, [refundLine(lineB, 90n)], refundedPort(), ADMIN)

    await expect(
      executeRefund(dbB, createdB.id, [refundLine(lineB, 60n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(isRefundExceedsPaidError)

    const executed = await dbB
      .select()
      .from(refundIntent)
      .where(eq(refundIntent.orderId, createdB.id))
    const sumExecuted = executed
      .filter((i) => i.status === 'executed')
      .reduce((acc, i) => acc + i.amount, 0n)
    expect(sumExecuted).toBeLessThanOrEqual(100n)
  })

  it('seq after gapped failed intent uses MAX(seq)+1', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const now = new Date()

    await db.insert(refundIntent).values({
      id: '55555555-5555-4555-8555-555555555555',
      orderId: created.id,
      seq: 1,
      amount: 10n,
      status: 'failed',
      refundKey: 'refund:55555555-5555-4555-8555-555555555555',
      createdAt: now,
      updatedAt: now,
    })

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

    const intents = await db
      .select()
      .from(refundIntent)
      .where(eq(refundIntent.orderId, created.id))
      .orderBy(refundIntent.seq)

    const active = intents.filter((i) => i.status !== 'failed')
    expect(active[0]?.seq).toBe(2)
  })
})
