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,
  isOrderNotFoundError,
  isOrderValidationError,
  isRefundExceedsPaidError,
} from './errors.js'
import { getOrderById } from './get-order-by-id.js'
import { listOrders } from './list-orders.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 {
  claimRefundIntentInTx,
  executeRefund,
  settleRefundIntentInTx,
} from './returns/execute-refund.js'
import { reconcileStuckRefunds } from './returns/reconcile-stuck-refunds.js'
import type { RefundPort } from './returns/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 BUYER_B = '44444444-4444-4444-8444-444444444444'
const VENDOR_A = 'vendor-a'
const VENDOR_B = 'vendor-b'
const CHARGE_REF = 'ch_secaudit'
const OTHER_CHARGE = 'ch_secaudit_other'
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 refundLine(orderLineId: string, amount: bigint) {
  return { orderLineId, qty: 1, amount }
}

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

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
}

function wrapWithTxDepth(db: Awaited<ReturnType<typeof freshDb>>) {
  let depth = 0
  const baseTransaction = db.transaction.bind(db)
  const wrapped = Object.assign(db, {
    transaction<T>(
      fn: (tx: Parameters<Parameters<typeof baseTransaction>[0]>[0]) => Promise<T>,
    ): Promise<T> {
      depth += 1
      return baseTransaction(fn).finally(() => {
        depth -= 1
      })
    },
    txDepth: () => depth,
  })
  return wrapped as typeof db & { txDepth: () => number }
}

describe('Gate 4 — secaudit-orders-* conformance', () => {
  it('secaudit-orders-refund-outside-tx', async () => {
    const db = wrapWithTxDepth(await freshDb())
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    let portCalledInsideTx = false

    const port: RefundPort = async (req) => {
      if (db.txDepth() > 0) {
        portCalledInsideTx = true
      }
      return {
        kind: 'refunded',
        refundKey: req.refundKey,
        chargeKey: req.chargeKey,
        providerRef: 're_outside',
        amount: req.amount,
        currency: 'USD',
      }
    }

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

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

  it('secaudit-orders-refund-single-winner', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const orderId = created.id

    const claimed = await db.transaction((tx) => claimRefundIntentInTx(tx, orderId, 50n))

    const first = await db.transaction((tx) =>
      settleRefundIntentInTx(tx, claimed.intentId, orderId, 're_first'),
    )
    const second = await db.transaction((tx) =>
      settleRefundIntentInTx(tx, claimed.intentId, orderId, 're_second'),
    )

    expect(first).toBe(true)
    expect(second).toBe(false)

    const intents = await db.select().from(refundIntent).where(eq(refundIntent.orderId, orderId))
    expect(intents.filter((i) => i.status === 'executed')).toHaveLength(1)
  })

  it('secaudit-orders-refund-idem-key', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    let capturedKey = ''

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

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

    const [intent] = await db
      .select()
      .from(refundIntent)
      .where(eq(refundIntent.orderId, created.id))
    expect(intent?.refundKey).toBe(`refund:${intent?.id}`)
    expect(capturedKey).toBe(`refund:${intent?.id}`)
    expect(capturedKey).not.toMatch(/seq/)
  })

  it('secaudit-orders-refund-retry', async () => {
    const db = await freshDb()
    const created = await paidOrder(db)
    const lineId = created.lines[0]!.id
    const keys: string[] = []

    await expect(
      executeRefund(db, created.id, [refundLine(lineId, 60n)], async () => {
        throw new Error('ambiguous network')
      }, ADMIN),
    ).rejects.toThrow('ambiguous network')

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

    const retryPort: RefundPort = async (req) => {
      keys.push(req.refundKey)
      return {
        kind: 'refunded',
        refundKey: req.refundKey,
        chargeKey: req.chargeKey,
        providerRef: 're_retry',
        amount: req.amount,
        currency: 'USD',
      }
    }

    const result = await reconcileStuckRefunds(db, retryPort)
    expect(result.settled).toBe(1)
    expect(keys).toEqual([`refund:${pending!.id}`])
    expect(await countIntents(db, created.id)).toBe(1)

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

  it('secaudit-orders-over-refund-422', 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.httpStatus === 422)
  })

  // secaudit-orders-over-refund-race: real-PG concurrent partial refund — see
  // execute-refund.concurrency.test.ts ('locked-intent cap: concurrent partial refunds').

  it('secaudit-orders-settle-idempotent', async () => {
    const db = await 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))
    await expect(
      db.transaction((tx) => markPaid(tx, created.id, CHARGE_REF)),
    ).resolves.toBeUndefined()

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

  it('secaudit-orders-charge-conflict', async () => {
    const db = await 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))

    await expect(
      db.transaction((tx) => markPaid(tx, created.id, OTHER_CHARGE)),
    ).rejects.toSatisfy((e) => isOrderChargeConflictError(e) && e.httpStatus === 409)
  })

  it('secaudit-orders-ownership-recheck', async () => {
    const db = await freshDb()
    const created = await paidOrder(db, multiVendorOrder())
    const foreignLine = created.lines.find((l) => l.vendorId === VENDOR_B)!.id

    const readAsVendorA = await getOrderById(db, created.id, { vendorId: VENDOR_A })
    expect(readAsVendorA).not.toBeNull()

    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('secaudit-orders-authz-before-refund', async () => {
    const phantom = '55555555-5555-4555-8555-555555555555'
    const db = await freshDb()
    const created = await paidOrder(db, multiVendorOrder())
    const ownLine = created.lines.find((l) => l.vendorId === VENDOR_A)!.id
    const foreignLine = created.lines.find((l) => l.vendorId === VENDOR_B)!.id
    const dbPlatform = await freshDb()
    const platformPaid = await paidOrder(dbPlatform)
    const platformLine = platformPaid.lines[0]!.id
    await expect(
      executeRefund(
        dbPlatform,
        platformPaid.id,
        [refundLine(platformLine, 10n)],
        refundedPort(),
        { vendorId: VENDOR_A },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)
    expect(await countIntents(dbPlatform, platformPaid.id)).toBe(0)

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

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

    await executeRefund(
      db,
      created.id,
      [refundLine(ownLine, 10n)],
      refundedPort(),
      { vendorId: VENDOR_A },
    )
    expect(await countIntents(db, created.id)).toBe(1)

    const dbAdmin = await freshDb()
    const createdAdmin = await paidOrder(dbAdmin)
    const adminLine = createdAdmin.lines[0]!.id
    await executeRefund(dbAdmin, createdAdmin.id, [refundLine(adminLine, 10n)], refundedPort(), ADMIN)
    expect(await countIntents(dbAdmin, createdAdmin.id)).toBe(1)

    const dbEmpty = await freshDb()
    const createdEmpty = await paidOrder(dbEmpty)
    await expect(
      executeRefund(dbEmpty, createdEmpty.id, [], refundedPort(), ADMIN),
    ).rejects.toSatisfy((e) => isOrderValidationError(e) && e.field === 'lines')
    expect(await countIntents(dbEmpty, createdEmpty.id)).toBe(0)

    const dbPhantom = await freshDb()
    const createdPhantom = await paidOrder(dbPhantom, multiVendorOrder())
    await expect(
      executeRefund(dbPhantom, createdPhantom.id, [refundLine(phantom, 10n)], refundedPort(), ADMIN),
    ).rejects.toSatisfy(isOrderNotFoundError)
    await expect(
      executeRefund(
        dbPhantom,
        createdPhantom.id,
        [refundLine(phantom, 10n)],
        refundedPort(),
        { vendorId: VENDOR_A },
      ),
    ).rejects.toSatisfy(isOrderNotFoundError)
    expect(await countIntents(dbPhantom, createdPhantom.id)).toBe(0)
  })

  it('secaudit-orders-no-enum-oracle', async () => {
    const db = await freshDb()
    const created = await db.transaction((tx) => createOrder(tx, orderTotal100()))

    const notOwned = await getOrderById(db, created.id, { userId: BUYER_B })
    expect(notOwned).toBeNull()

    const missing = await getOrderById(
      db,
      'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
      { userId: BUYER_B },
    )
    expect(missing).toBeNull()
  })

  it('secaudit-orders-list-scoped', async () => {
    const db = await freshDb()
    const vendorOrder = await db.transaction((tx) =>
      createOrder(tx, multiVendorOrder()),
    )
    await db.transaction((tx) => createOrder(tx, orderTotal100({ buyerRef: { userId: BUYER_B } })))

    const vendorPage = await listOrders(db, { vendorId: VENDOR_A })
    expect(vendorPage.items).toHaveLength(1)
    expect(vendorPage.items[0]?.id).toBe(vendorOrder.id)

    const buyerPage = await listOrders(db, { userId: BUYER_ID })
    // Positive cardinality first so the scope assertions below cannot pass
    // vacuously on an empty page: BUYER_ID owns exactly the vendorOrder; the
    // BUYER_B order must NOT leak into BUYER_ID's scope.
    expect(buyerPage.items).toHaveLength(1)
    expect(buyerPage.items[0]?.id).toBe(vendorOrder.id)
    expect(buyerPage.items.every((o) => 'userId' in o.buyerRef && o.buyerRef.userId === BUYER_ID)).toBe(
      true,
    )
    expect(buyerPage.items.some((o) => o.buyerRef && 'userId' in o.buyerRef && o.buyerRef.userId === BUYER_B)).toBe(
      false,
    )
  })
})
