import { eq } from 'drizzle-orm'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import * as ordersModule from '@platform-modules/commerce-orders'
import { order } from '@platform-modules/commerce-orders'
import type { TransactionalDatabase } from '@platform-modules/db'
import {
  isCheckoutValidationError,
  isFulfillmentIncompleteError,
  isOrderNotChargeableError,
  isPaymentFailedError,
} from './errors.js'
import { startPg } from './pg-harness.js'
import { checkoutSession, type CheckoutDbSchema } from './schema.js'
import { startCheckout } from './start.js'
import {
  baseCheckoutInput,
  buildCheckoutDeps,
  buildDigitalCart,
  BUYER_ID,
  OTHER_USER_ID,
  seedDigitalVariant,
  setOrderStatus,
  VARIANT_DIGITAL,
  VARIANT_STALE,
} from './test-fixtures.js'
import {
  createFakeIntentStore,
  createFakePaymentProvider,
  fakeFulfillmentPorts,
} from './testing/index.js'
import type { FulfillmentDbSchema } from '@platform-modules/commerce-fulfillment'

describe('startCheckout (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 })
    await seedDigitalVariant(db, { variantId: VARIANT_STALE, amount: 500n })
  }, 120_000)

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

  it('start-validates — empty cart throws typed validation error', async () => {
    const deps = buildCheckoutDeps(db)
    const idempotencyKey = crypto.randomUUID()
    await expect(
      startCheckout(deps, {
        cart: { id: 'c', currency: 'USD', lines: [], subtotal: 0n },
        buyerRef: { userId: BUYER_ID },
        priceMode: 'exclusive',
        currency: 'USD',
        buyerCountry: 'DE',
        idempotencyKey,
      }),
    ).rejects.toSatisfy(isCheckoutValidationError)

    const rows = await db.select().from(order).where(eq(order.idempotencyKey, idempotencyKey))
    expect(rows).toHaveLength(0)
  })

  it('start-validates — stale removed throws STALE_ITEMS', async () => {
    const deps = buildCheckoutDeps(db)
    const cart = buildDigitalCart('99999999-9999-4999-8999-999999999999', 1000n)
    await expect(
      startCheckout(deps, {
        cart,
        buyerRef: { userId: BUYER_ID },
        priceMode: 'exclusive',
        currency: 'USD',
        buyerCountry: 'DE',
        idempotencyKey: crypto.randomUUID(),
      }),
    ).rejects.toSatisfy((e: unknown) => {
      return (
        isCheckoutValidationError(e) &&
        e.reason === 'STALE_ITEMS' &&
        e.stale?.removed.includes('99999999-9999-4999-8999-999999999999') === true
      )
    })
  })

  it('start-validates — stale priced throws STALE_ITEMS with updated split', async () => {
    const deps = buildCheckoutDeps(db)
    const cart = buildDigitalCart(VARIANT_STALE, 999n)
    await expect(
      startCheckout(deps, {
        cart,
        buyerRef: { userId: BUYER_ID },
        priceMode: 'exclusive',
        currency: 'USD',
        buyerCountry: 'DE',
        idempotencyKey: crypto.randomUUID(),
      }),
    ).rejects.toSatisfy((e: unknown) => {
      return (
        isCheckoutValidationError(e) &&
        e.reason === 'STALE_ITEMS' &&
        (e.stale?.updated.some((u) => u.variantId === VARIANT_STALE) ?? false)
      )
    })
  })

  it('start-validates — currency mismatch throws typed validation error', async () => {
    const deps = buildCheckoutDeps(db)
    const cart = buildDigitalCart(VARIANT_DIGITAL, 1000n, 'EUR')
    await expect(
      startCheckout(deps, {
        cart,
        buyerRef: { userId: BUYER_ID },
        priceMode: 'exclusive',
        currency: 'USD',
        buyerCountry: 'DE',
        idempotencyKey: crypto.randomUUID(),
      }),
    ).rejects.toSatisfy((e: unknown) => {
      return isCheckoutValidationError(e) && e.reason === 'CURRENCY_MISMATCH'
    })
  })

  it('start-charge-amount-cap — total past MAX_SAFE_INTEGER rejects before order or provider call', async () => {
    const hugeVariant = crypto.randomUUID()
    const hugeAmount = 2n ** 53n
    await seedDigitalVariant(db, { variantId: hugeVariant, amount: hugeAmount })
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const idempotencyKey = crypto.randomUUID()

    await expect(
      startCheckout(deps, {
        cart: buildDigitalCart(hugeVariant, hugeAmount),
        buyerRef: { userId: BUYER_ID },
        priceMode: 'exclusive',
        currency: 'USD',
        buyerCountry: 'DE',
        idempotencyKey,
      }),
    ).rejects.toSatisfy(ordersModule.isOrderValidationError)

    expect(provider.chargeCallCount).toBe(0)
    const rows = await db.select().from(order).where(eq(order.idempotencyKey, idempotencyKey))
    expect(rows).toHaveLength(0)
  })

  it('start-creates-and-claims — happy path returns clientSecret and charging order', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const idempotencyKey = crypto.randomUUID()

    const result = await startCheckout(deps, {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey,
    })

    expect(result.clientSecret).toBe('sec_test')
    expect(provider.chargeCallCount).toBe(1)

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

  it('start-idempotent-order — same idempotency key yields one order row', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const idempotencyKey = crypto.randomUUID()
    const input = {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive' as const,
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey,
    }

    const first = await startCheckout(deps, input)
    provider.setMode({ kind: 'requires_client_action', clientSecret: 'sec_other' })
    const second = await startCheckout(deps, input)

    expect(second.orderId).toBe(first.orderId)
    const rows = await db.select().from(order).where(eq(order.idempotencyKey, idempotencyKey))
    expect(rows).toHaveLength(1)
    expect(provider.chargeCallCount).toBe(1)
  })

  it('start-resume-in-flight — resume reads session, never re-charges', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput()

    const first = await startCheckout(deps, input)
    expect(first.clientSecret).toBe('sec_test')

    const result = await startCheckout(deps, input)

    expect(result).toEqual({ orderId: first.orderId, clientSecret: 'sec_test' })
    expect(provider.chargeCallCount).toBe(1)
  })

  it('start-resume-charging-no-session — returns orderId without clientSecret', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput()

    const first = await startCheckout(deps, input)
    await db.delete(checkoutSession).where(eq(checkoutSession.orderId, first.orderId))

    const result = await startCheckout(deps, input)

    expect(result).toEqual({ orderId: first.orderId })
    expect(result.clientSecret).toBeUndefined()
    expect(provider.chargeCallCount).toBe(1)
  })

  it('start-concurrent-claim-rebranches — lost CAS resumes with session secret', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput()

    const first = await startCheckout(deps, input)
    await db.update(order).set({ status: 'pending', updatedAt: new Date() }).where(eq(order.id, first.orderId))

    const claimSpy = vi.spyOn(ordersModule, 'claimForCharge').mockImplementationOnce(async (tx, orderId) => {
      await tx
        .update(order)
        .set({ status: 'charging', updatedAt: new Date() })
        .where(eq(order.id, orderId))
      throw new ordersModule.OrderNotChargeableError(orderId)
    })

    const result = await startCheckout(deps, input)
    claimSpy.mockRestore()

    expect(result).toEqual({ orderId: first.orderId, clientSecret: 'sec_test' })
    expect(provider.chargeCallCount).toBe(1)
  })

  it('start-resume-already-paid — returns paid result without clientSecret', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput()

    const first = await startCheckout(deps, input)
    await setOrderStatus(db, first.orderId, 'paid', 'pi_paid')

    const result = await startCheckout(deps, input)

    expect(result).toEqual({ orderId: first.orderId })
    expect(result.clientSecret).toBeUndefined()
    expect(provider.chargeCallCount).toBe(1)
  })

  it('start-claim-terminal — failed order throws OrderNotChargeableError', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput()

    const first = await startCheckout(deps, input)
    await setOrderStatus(db, first.orderId, 'failed')

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

  it('start-claim-terminal — non-owner buyer throws OrderNotChargeableError', async () => {
    const provider = createFakePaymentProvider()
    const deps = buildCheckoutDeps(db, { provider })
    const input = baseCheckoutInput()

    const first = await startCheckout(deps, input)
    const owned = await ordersModule.getOrderById(db, first.orderId, { isAdmin: true })
    expect(owned).not.toBeNull()

    vi.spyOn(ordersModule, 'createOrder').mockResolvedValueOnce(owned!)

    await expect(
      startCheckout(deps, {
        ...baseCheckoutInput(),
        buyerRef: { userId: OTHER_USER_ID },
      }),
    ).rejects.toSatisfy(isOrderNotChargeableError)
    expect(provider.chargeCallCount).toBe(1)
  })

  it('start-settle-throws-pending — in-flight intent leaves order charging', async () => {
    const provider = createFakePaymentProvider()
    const intentStore = createFakeIntentStore({ forcePending: true })
    const deps = buildCheckoutDeps(db, { provider, intentStore })

    const result = await startCheckout(deps, {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey: crypto.randomUUID(),
    })

    expect(result).toEqual({ orderId: expect.any(String) })
    expect(result.clientSecret).toBeUndefined()

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

  it('start-settle-throws-provider — provider error wraps PaymentFailedError, order stays charging', async () => {
    const provider = createFakePaymentProvider({ kind: 'throw', error: new Error('declined') })
    const deps = buildCheckoutDeps(db, { provider })
    const idempotencyKey = crypto.randomUUID()

    await expect(
      startCheckout(deps, {
        cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
        buyerRef: { userId: BUYER_ID },
        priceMode: 'exclusive',
        currency: 'USD',
        buyerCountry: 'DE',
        idempotencyKey,
      }),
    ).rejects.toSatisfy(isPaymentFailedError)

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

  it('inline-settled-path — settled provider settles inline without clientSecret', async () => {
    const provider = createFakePaymentProvider({ kind: 'settled', providerRef: 'pi_inline' })
    const deps = buildCheckoutDeps(db, { provider })

    const result = await startCheckout(deps, {
      cart: buildDigitalCart(VARIANT_DIGITAL, 1000n),
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey: crypto.randomUUID(),
    })

    expect(result.clientSecret).toBeUndefined()
    const [row] = await db.select().from(order).where(eq(order.id, result.orderId))
    expect(row?.status).toBe('paid')
  })

  it('inline-settled-fulfillment-incomplete — surfaces FulfillmentIncompleteError unwrapped, not PaymentFailedError', async () => {
    // INV-1: on the inline-settled path the charge succeeds and the order is
    // already paid; an unfulfillable outcome must surface the SAME throw that
    // settleCheckout raises (a 500 heal trigger), never be masked as a 402
    // PaymentFailedError by the provider-call try.
    const provider = createFakePaymentProvider({ kind: 'settled', providerRef: 'pi_inline_incomplete' })
    const fulfillment = fakeFulfillmentPorts({
      db: db as unknown as TransactionalDatabase<FulfillmentDbSchema>,
      resolveBlobKey: async () => '',
    })
    const deps = buildCheckoutDeps(db, { provider, fulfillment })
    const idempotencyKey = crypto.randomUUID()

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

    expect(isFulfillmentIncompleteError(thrown)).toBe(true)
    expect(isPaymentFailedError(thrown)).toBe(false)

    // Charge succeeded: order is paid (heal pending), not left charging.
    const [row] = await db.select().from(order).where(eq(order.idempotencyKey, idempotencyKey))
    expect(row?.status).toBe('paid')
  })

  it('splits-vendor-attribution — vendorId comes from the CATALOG (forged cart vendorId ignored), vendor splits funder=vendor', async () => {
    const VENDOR_REAL = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
    const VENDOR_FORGED = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'
    const VARIANT_VENDORED = '66666666-6666-4666-8666-666666666666'
    const VARIANT_PLATFORM = '77777777-7777-4777-8777-777777777777'
    await seedDigitalVariant(db, {
      variantId: VARIANT_VENDORED,
      amount: 1000n,
      vendorId: VENDOR_REAL,
    })
    await seedDigitalVariant(db, { variantId: VARIANT_PLATFORM, amount: 500n })

    const deps = buildCheckoutDeps(db)
    const result = await startCheckout(deps, {
      cart: {
        id: 'cart-mv',
        currency: 'USD',
        subtotal: 1500n,
        lines: [
          {
            lineId: 'l1',
            variantId: VARIANT_VENDORED,
            qty: 1,
            price: { amount: 1000n, currency: 'USD', priceMode: 'exclusive' as const },
            // forged: the cart claims another vendor for this product's money
            vendorId: VENDOR_FORGED,
          },
          {
            lineId: 'l2',
            variantId: VARIANT_PLATFORM,
            qty: 1,
            price: { amount: 500n, currency: 'USD', priceMode: 'exclusive' as const },
            vendorId: null,
          },
        ],
      },
      buyerRef: { userId: BUYER_ID },
      priceMode: 'exclusive',
      currency: 'USD',
      buyerCountry: 'DE',
      idempotencyKey: crypto.randomUUID(),
    })

    const splits = await db
      .select()
      .from(ordersModule.vendorSplit)
      .where(eq(ordersModule.vendorSplit.orderId, result.orderId))
    expect(splits).toHaveLength(2)

    // DE 19% VAT exclusive: gross 1190 vendor line, 595 platform line.
    const vendorSplitRow = splits.find((s) => s.vendorId !== null)
    expect(vendorSplitRow?.vendorId).toBe(VENDOR_REAL)
    expect(vendorSplitRow?.funder).toBe('vendor')
    expect(vendorSplitRow?.amount).toBe(1190n)

    const platformSplitRow = splits.find((s) => s.vendorId === null)
    expect(platformSplitRow?.funder).toBe('platform')
    expect(platformSplitRow?.amount).toBe(595n)

    const lines = await db
      .select()
      .from(ordersModule.orderLine)
      .where(eq(ordersModule.orderLine.orderId, result.orderId))
    const vendoredLine = lines.find((l) => l.variantId === VARIANT_VENDORED)
    expect(vendoredLine?.vendorId).toBe(VENDOR_REAL)
  })

})
