import { eq } from 'drizzle-orm'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import * as fulfillmentModule from '@platform-modules/commerce-fulfillment'
import { claimForCharge, createOrder, isOrderChargeConflictError, order } from '@platform-modules/commerce-orders'
import type { TransactionalDatabase } from '@platform-modules/db'
import { buildChargeKey } from './charge-key.js'
import { isFulfillmentIncompleteError, isOrderNotChargeableError } from './errors.js'
import { startPg } from './pg-harness.js'
import type { CheckoutDbSchema } from './schema.js'
import { settleCheckout } from './settle.js'
import {
  buildCheckoutDeps,
  BUYER_ID,
  countGrantsForOrder,
  seedDigitalVariant,
  setOrderStatus,
  VARIANT_DIGITAL,
} from './test-fixtures.js'
import { fakeFulfillmentPorts } from './testing/index.js'
import { makeSettlementDispatch } from './webhook/index.js'

describe('settleCheckout (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 })
  }, 120_000)

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

  async function createChargingOrder() {
    const created = await db.transaction(async (tx) => {
      const orderRow = 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, orderRow.id)
    })
    return created
  }

  it('settle-marks-paid-and-fulfills — order paid with digital grant', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)

    await settleCheckout(deps, charging.id, 'pi_settle_1')

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

  it('settle-idempotent-replay — same providerRef replays fulfill once', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)
    const providerRef = 'pi_idem_1'

    await settleCheckout(deps, charging.id, providerRef)
    await settleCheckout(deps, charging.id, providerRef)

    expect(await countGrantsForOrder(db, charging.id)).toBe(1)
  })

  it('settle-heal-after-paid-crash — replay after partial failure heals fulfillment', async () => {
    const charging = await createChargingOrder()
    const fulfillment = fakeFulfillmentPorts({
      db: db as unknown as TransactionalDatabase<import('@platform-modules/commerce-fulfillment').FulfillmentDbSchema>,
    })
    let failOnce = true
    const fulfillSpy = vi.spyOn(fulfillmentModule, 'fulfillOrder').mockImplementation(async (...args) => {
      if (failOnce) {
        failOnce = false
        throw new Error('crash before fulfill completes')
      }
      return fulfillmentModule.fulfillOrder(...args)
    })

    const deps = buildCheckoutDeps(db, { fulfillment })
    const providerRef = 'pi_heal_1'

    await expect(settleCheckout(deps, charging.id, providerRef)).rejects.toThrow('crash')
    fulfillSpy.mockRestore()
    await settleCheckout(deps, charging.id, providerRef)

    expect(await countGrantsForOrder(db, charging.id)).toBe(1)
  })

  it('settle-fulfillment-incomplete-throws — unfulfillable outcome throws then heals on replay', async () => {
    const charging = await createChargingOrder()
    const fulfillment = fakeFulfillmentPorts({
      db: db as unknown as TransactionalDatabase<import('@platform-modules/commerce-fulfillment').FulfillmentDbSchema>,
      resolveBlobKey: async () => '',
    })
    const deps = buildCheckoutDeps(db, { fulfillment })
    const providerRef = 'pi_incomplete_1'

    await expect(settleCheckout(deps, charging.id, providerRef)).rejects.toSatisfy(
      isFulfillmentIncompleteError,
    )

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

    fulfillment.setResolveBlobKey(async (line) => `blob:${line.id}`)
    await settleCheckout(deps, charging.id, providerRef)
    expect(await countGrantsForOrder(db, charging.id)).toBe(1)
  })

  it('settle-rejects-fractional-provider-amount — typed error, order stays charging', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)

    await expect(
      settleCheckout(deps, charging.id, 'pi_frac', 1000.5),
    ).rejects.toSatisfy(isOrderNotChargeableError)

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

  it('settle-conflicting-charge — different providerRef throws conflict', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)

    await settleCheckout(deps, charging.id, 'pi_first')
    await expect(settleCheckout(deps, charging.id, 'pi_second')).rejects.toSatisfy(
      isOrderChargeConflictError,
    )
  })

  it('settle-empty-providerref-on-paid — skips CAS conflict and re-drives fulfill', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)
    const providerRef = 'pi_real_ref'

    await settleCheckout(deps, charging.id, providerRef)
    await settleCheckout(deps, charging.id, '')

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

  it('settle-empty-providerref-on-charging — leaves order charging until real ref', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)

    const unchanged = await settleCheckout(deps, charging.id, '')
    expect(unchanged.status).toBe('charging')

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

  it('dispatch-maps-settlement — settlement event settles order', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)
    const dispatch = makeSettlementDispatch(deps)

    await dispatch({
      eventId: 'evt_1',
      kind: 'settlement',
      chargeKey: buildChargeKey(charging.id),
      providerRef: 'pi_dispatch',
      amount: 1000,
      currency: 'USD',
    })

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

  it('dispatch-maps-settlement — refund event is a no-op', async () => {
    const charging = await createChargingOrder()
    const deps = buildCheckoutDeps(db)
    const dispatch = makeSettlementDispatch(deps)

    await dispatch({
      eventId: 'evt_refund',
      kind: 'refund',
      refundKey: 'ref_1',
      chargeKey: buildChargeKey(charging.id),
      providerChargeId: 'pi_charge',
      providerRef: 'pi_refund',
      amount: 100,
      currency: 'USD',
    })

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