import { eq } from 'drizzle-orm'
import { drizzle } from 'drizzle-orm/node-postgres'
import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import { ingestWebhook } from '@platform-modules/billing'
import { claimForCharge, createOrder, order } from '@platform-modules/commerce-orders'
import type { TransactionalDatabase } from '@platform-modules/db'
import { withTransactionIdentity } from '@platform-modules/db'
import { EU_VAT_SCHEDULES, resolveVatRate } from '@platform-modules/tax/rates-table'
import { buildChargeKey } from './charge-key.js'
import {
  isCheckoutValidationError,
  isOrderNotChargeableError,
  isOrderNotFoundError,
  isPaymentFailedError,
} from './errors.js'
import { startPg, pushWebhookEventsTable } from './pg-harness.js'
import { checkoutDbSchema, checkoutSession, type CheckoutDbSchema } from './schema.js'
import { getCheckoutStatus } from './status.js'
import { startCheckout } from './start.js'
import { settleCheckout } from './settle.js'
import * as settleModule from './settle.js'
import {
  baseCheckoutInput,
  buildCheckoutDeps,
  buildDigitalCart,
  BUYER_ID,
  countGrantsForOrder,
  createTrackingLedger,
  OTHER_USER_ID,
  seedDigitalVariant,
  setOrderStatus,
  VARIANT_DIGITAL,
} from './test-fixtures.js'
import {
  createFakePaymentProvider,
  createPgDedupStore,
  fakeFulfillmentPorts,
} from './testing/index.js'
import { validateCheckout } from './validate.js'
import { makeSettlementDispatch } from './webhook/index.js'
import pg from 'pg'
import type { FulfillmentDbSchema } from '@platform-modules/commerce-fulfillment'

const srcDir = join(fileURLToPath(new URL('.', import.meta.url)))

function wrapWithTxDepth(db: TransactionalDatabase<CheckoutDbSchema>) {
  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 }
}

function webhookReq(): Request {
  return new Request('https://secaudit/webhook', { method: 'POST', body: '{}' })
}

describe('Gate 4 — secaudit-checkout-* conformance (real Postgres)', () => {
  let db: TransactionalDatabase<CheckoutDbSchema>
  let pool: pg.Pool
  let dbB: TransactionalDatabase<CheckoutDbSchema>
  let poolB: pg.Pool | undefined
  let stop: (() => Promise<void>) | undefined

  beforeAll(async () => {
    const pgResult = await startPg()
    db = pgResult.db
    pool = pgResult.pool
    await pushWebhookEventsTable(pool)
    const { host, port, user, database } = pool.options
    poolB = new pg.Pool({ host, port, user, database, max: 1 })
    dbB = withTransactionIdentity(drizzle(poolB, { schema: checkoutDbSchema })) as unknown as TransactionalDatabase<CheckoutDbSchema>
    stop = pgResult.stop
    await seedDigitalVariant(db, { variantId: VARIANT_DIGITAL, amount: 1000n })
  }, 120_000)

  afterAll(async () => {
    await poolB?.end().catch(() => undefined)
    await stop?.()
  }, 30_000)

  it('secaudit-checkout-charge-outside-tx (M1)', async () => {
    const trackedDb = wrapWithTxDepth(db)
    let chargeInsideTx = false
    const provider = createFakePaymentProvider()
    const originalCharge = provider.charge.bind(provider)
    provider.charge = async (req) => {
      if (trackedDb.txDepth() > 0) {
        chargeInsideTx = true
      }
      return originalCharge(req)
    }
    const deps = buildCheckoutDeps(trackedDb, { provider })

    await startCheckout(deps, baseCheckoutInput())

    expect(chargeInsideTx).toBe(false)
    expect(provider.chargeCallCount).toBe(1)
  })

  it('secaudit-checkout-claim-single-winner (M2)', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const depsB = buildCheckoutDeps(dbB, { provider })
    const input = baseCheckoutInput(crypto.randomUUID())

    const [first, second] = await Promise.allSettled([
      startCheckout(deps, input),
      startCheckout(depsB, input),
    ])

    expect(first.status).toBe('fulfilled')
    expect(second.status).toBe('fulfilled')
    expect(provider.chargeCallCount).toBe(1)

    const rows = await db.select().from(order).where(eq(order.idempotencyKey, input.idempotencyKey))
    expect(rows).toHaveLength(1)
    expect(rows[0]?.status).toBe('charging')
  })

  it('secaudit-checkout-idem-key-derived (M3)', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput(crypto.randomUUID())

    const first = await startCheckout(deps, input)
    const chargeKey = buildChargeKey(first.orderId)
    expect(chargeKey).toBe(`charge:${encodeURIComponent(first.orderId)}`)

    const second = await startCheckout(deps, input)
    expect(second.orderId).toBe(first.orderId)
    expect(provider.chargeCallCount).toBe(1)
    expect(chargeKey).toBe(buildChargeKey(second.orderId))
  })

  it('secaudit-checkout-charge-retry (M4)', async () => {
    const provider = createFakePaymentProvider({ kind: 'throw', error: new Error('transient provider') })
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput(crypto.randomUUID())

    await expect(startCheckout(deps, input)).rejects.toSatisfy(isPaymentFailedError)
    expect(provider.chargeCallCount).toBe(1)

    const [row] = await db.select().from(order).where(eq(order.idempotencyKey, input.idempotencyKey))
    expect(row?.status).toBe('charging')

    await db.insert(checkoutSession).values({
      orderId: row!.id,
      clientSecret: 'sec_retry',
      createdAt: new Date(),
      updatedAt: new Date(),
    })

    const resume = await startCheckout(deps, input)
    expect(resume.clientSecret).toBe('sec_retry')
    expect(provider.chargeCallCount).toBe(1)

    const dedup = createPgDedupStore(pool)
    const { ledger } = createTrackingLedger()
    const dispatch = makeSettlementDispatch(deps)
    const chargeKey = buildChargeKey(resume.orderId)
    const event = {
      eventId: 'evt-m4-retry',
      kind: 'settlement' as const,
      chargeKey,
      providerRef: 'pi_retry_settle',
      amount: Number(row!.total),
      currency: 'USD',
    }

    const res = await ingestWebhook(webhookReq(), {
      provider: {
        provider: 'fake',
        emitsInvoiceOnCharge: false,
        charge: async () => {
          throw new Error('not used')
        },
        refund: async () => ({ kind: 'pending' as const }),
        reconcileRefund: async () => ({ kind: 'pending_or_unknown' as const }),
        parseWebhook: async () => event,
      },
      dedupStore: dedup,
      dispatch,
      ledger,
      db,
    })
    expect(res.status).toBe(200)
    expect(await countGrantsForOrder(db, resume.orderId)).toBe(1)
  })

  it('secaudit-checkout-settle-idempotent (M5)', async () => {
    const charging = await db.transaction(async (tx) => {
      const created = await 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' }],
      })
      return claimForCharge(tx, created.id)
    })

    const deps = buildCheckoutDeps(db)
    const providerRef = 'pi_idem_secaudit'
    await settleCheckout(deps, charging.id, providerRef)
    await settleCheckout(deps, charging.id, providerRef)

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

  it('secaudit-checkout-webhook-dedup-concurrent (M5 wiring)', async () => {
    const charging = await db.transaction(async (tx) => {
      const created = await 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' }],
      })
      return claimForCharge(tx, created.id)
    })

    const deps = buildCheckoutDeps(db)
    const settleSpy = vi.spyOn(settleModule, 'settleCheckout')
    const dispatch = makeSettlementDispatch(deps)

    const dedup = createPgDedupStore(pool)
    const { ledger } = createTrackingLedger()
    const event = {
      eventId: 'evt-concurrent-dedup',
      kind: 'settlement' as const,
      chargeKey: buildChargeKey(charging.id),
      providerRef: 'pi_concurrent',
      amount: 1000,
      currency: 'USD',
    }
    const provider = {
      provider: 'fake',
      emitsInvoiceOnCharge: false,
      charge: async () => {
        throw new Error('not used')
      },
      refund: async () => ({ kind: 'pending' as const }),
      reconcileRefund: async () => ({ kind: 'pending_or_unknown' as const }),
      parseWebhook: async () => event,
    }

    const [resA, resB] = await Promise.all([
      ingestWebhook(webhookReq(), {
        provider,
        dedupStore: dedup,
        dispatch,
        ledger,
        db,
      }),
      ingestWebhook(webhookReq(), {
        provider,
        dedupStore: createPgDedupStore(poolB!),
        dispatch,
        ledger,
        db: dbB,
      }),
    ])

    expect(resA.status).toBe(200)
    expect(resB.status).toBe(200)
    expect(settleSpy).toHaveBeenCalledTimes(1)
    expect(await countGrantsForOrder(db, charging.id)).toBe(1)
    settleSpy.mockRestore()
  })

  it('secaudit-checkout-settle-replay-after-partial (M6 + INV-1)', async () => {
    const charging = await db.transaction(async (tx) => {
      const created = await 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' }],
      })
      return claimForCharge(tx, created.id)
    })

    const fulfillment = fakeFulfillmentPorts({
      db: db as unknown as TransactionalDatabase<FulfillmentDbSchema>,
      resolveBlobKey: async () => '',
    })
    const deps = buildCheckoutDeps(db, { fulfillment })
    const dedup = createPgDedupStore(pool)
    const { ledger } = createTrackingLedger()
    const dispatch = makeSettlementDispatch(deps)
    const eventId = 'evt-partial-heal'
    const event = {
      eventId,
      kind: 'settlement' as const,
      chargeKey: buildChargeKey(charging.id),
      providerRef: 'pi_partial_heal',
      amount: 1000,
      currency: 'USD',
    }
    const provider = {
      provider: 'fake',
      emitsInvoiceOnCharge: false,
      charge: async () => {
        throw new Error('not used')
      },
      refund: async () => ({ kind: 'pending' as const }),
      reconcileRefund: async () => ({ kind: 'pending_or_unknown' as const }),
      parseWebhook: async () => event,
    }

    const first = await ingestWebhook(webhookReq(), {
      provider,
      dedupStore: dedup,
      dispatch,
      ledger,
      db,
    })
    expect(first.status).toBe(500)
    expect(dedup.marked).toHaveLength(0)
    expect(await countGrantsForOrder(db, charging.id)).toBe(0)

    const [paidRow] = await db.select().from(order).where(eq(order.id, charging.id))
    expect(paidRow?.status).toBe('paid')

    await pool.query(
      `UPDATE webhook_events SET claimed_at = now() - INTERVAL '6 minutes' WHERE event_id = $1`,
      [eventId],
    )

    fulfillment.setResolveBlobKey(async (line) => `blob:${line.id}`)
    const second = await ingestWebhook(webhookReq(), {
      provider,
      dedupStore: dedup,
      dispatch,
      ledger,
      db,
    })
    expect(second.status).toBe(200)
    expect(dedup.marked).toContain(eventId)
    expect(await countGrantsForOrder(db, charging.id)).toBe(1)
  })

  it('secaudit-checkout-money-format (M7)', () => {
    const vatRate = resolveVatRate(EU_VAT_SCHEDULES.DE!, '2026-06-20')
    const cart = buildDigitalCart(VARIANT_DIGITAL, 1234n)
    const catalog = new Map([
      [
        VARIANT_DIGITAL,
        {
          available: true,
          kind: 'digital' as const,
          price: cart.lines[0]!.price,
        },
      ],
    ])

    const result = validateCheckout({
      cart,
      catalog,
      currency: 'USD',
      priceMode: 'exclusive',
      vatRate,
    })
    expect(result.ok).toBe(true)
    if (!result.ok) return

    for (const line of result.lines) {
      expect(typeof line.unitNet).toBe('bigint')
      expect(typeof line.vat).toBe('bigint')
      expect(typeof line.lineTotal).toBe('bigint')
      expect(Number.isInteger(Number(line.lineTotal))).toBe(true)
    }

    const sources = readdirSync(srcDir).filter(
      (name) => name.endsWith('.ts') && !name.endsWith('.test.ts') && name !== 'pg-harness.ts',
    )
    for (const file of sources) {
      const text = readFileSync(join(srcDir, file), 'utf8')
      expect(text.includes('parseFloat')).toBe(false)
      expect(/\/\s*100\b/.test(text)).toBe(false)
    }
  })

  it('secaudit-checkout-typed-status (M8)', async () => {
    const deps = buildCheckoutDeps(db)

    const empty = await startCheckout(deps, {
      cart: { id: 'c', currency: 'USD', lines: [], subtotal: 0n },
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey: crypto.randomUUID(),
    }).catch((e) => e)
    expect(isCheckoutValidationError(empty) && empty.httpStatus).toBe(400)

    const stale = await startCheckout(deps, {
      cart: buildDigitalCart('99999999-9999-4999-8999-999999999999', 1000n),
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey: crypto.randomUUID(),
    }).catch((e) => e)
    expect(
      isCheckoutValidationError(stale) && stale.reason === 'STALE_ITEMS' && stale.httpStatus,
    ).toBe(409)

    const currency = await startCheckout(deps, {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n, 'EUR'),
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey: crypto.randomUUID(),
    }).catch((e) => e)
    expect(
      isCheckoutValidationError(currency) &&
        currency.reason === 'CURRENCY_MISMATCH' &&
        currency.httpStatus,
    ).toBe(422)

    const terminalInput = baseCheckoutInput(crypto.randomUUID())
    const terminalProvider = createFakePaymentProvider()
    const terminalDeps = buildCheckoutDeps(db, { provider: terminalProvider })
    const started = await startCheckout(terminalDeps, terminalInput)
    await setOrderStatus(db, started.orderId, 'failed')

    const claimLost = await startCheckout(terminalDeps, terminalInput).catch((e) => e)
    expect(isOrderNotChargeableError(claimLost) && claimLost.httpStatus).toBe(409)
  })

  it('secaudit-checkout-buyer-branch-authz (A1)', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const idempotencyKey = crypto.randomUUID()
    await db.transaction((tx) =>
      createOrder(tx, {
        idempotencyKey,
        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' }],
      }),
    )

    const intruder = await startCheckout(deps, {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
      buyerRef: { userId: OTHER_USER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey,
    }).catch((e) => e)

    expect((intruder as { name?: string }).name).toBe('OrderIdempotencyConflictError')
    expect(provider.chargeCallCount).toBe(0)
  })

  it('secaudit-checkout-ownership-recheck (A2)', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput(crypto.randomUUID())

    const started = await startCheckout(deps, input)
    const otherStatus = await getCheckoutStatus(deps, started.orderId, {
      userId: OTHER_USER_ID,
    }).catch((e) => e)
    expect(isOrderNotFoundError(otherStatus)).toBe(true)

    const ownerResume = await startCheckout(deps, input)
    expect(ownerResume).toEqual({ orderId: started.orderId, clientSecret: 'sec_test' })
  })

  it('secaudit-checkout-authz-before-charge (A4)', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const idempotencyKey = crypto.randomUUID()
    await db.transaction((tx) =>
      createOrder(tx, {
        idempotencyKey,
        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 startCheckout(deps, {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
      buyerRef: { userId: OTHER_USER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey,
    }).catch(() => undefined)

    expect(provider.chargeCallCount).toBe(0)
  })

  it('secaudit-checkout-no-enum-oracle (A5)', async () => {
    const deps = buildCheckoutDeps(db)
    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' }],
      }),
    )

    const missingId = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
    const nonOwner = await getCheckoutStatus(deps, created.id, { userId: OTHER_USER_ID }).catch(
      (e) => e,
    )
    const missing = await getCheckoutStatus(deps, missingId, { userId: BUYER_ID }).catch((e) => e)

    expect(isOrderNotFoundError(nonOwner)).toBe(true)
    expect(isOrderNotFoundError(missing)).toBe(true)
    expect(nonOwner.httpStatus).toBe(404)
    expect(missing.httpStatus).toBe(404)
    expect(nonOwner.code).toBe(missing.code)
    expect(nonOwner.name).toBe(missing.name)
  })
})
