import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createFakeSupabase } from './stubs/fake-supabase'
import { ACTOR, APP_ORIGIN, USER_ID, billingFor, seedCatalog, seedPermits, seedTrip } from './stubs/purchase-fixtures'
import type { PaymentReference, Purchase, PurchaseEvent } from '@/types/db'

/**
 * Tests C and K (docs/TASKS-2026-09-29-SYNCHRON-PURCHASING.md §13):
 * checkout through the mock adapter in `SYNCHRON_PAYMENTS_MODE=mock`.
 */

const db = createFakeSupabase()
const mocks = vi.hoisted(() => ({
  logTripEvent: vi.fn(async () => {}),
  syncTripToHha: vi.fn(async () => ({ ok: true })),
  logOperationEvent: vi.fn(async () => {}),
}))
vi.mock('@/lib/supabase/admin', () => ({ createAdminClient: () => db }))
vi.mock('@/lib/audit', () => ({ logTripEvent: mocks.logTripEvent }))
vi.mock('@/lib/integrations/hha-trip-sync', () => ({ syncTripToHha: mocks.syncTripToHha }))
vi.mock('@/lib/observability/operation-events', () => ({ logOperationEvent: mocks.logOperationEvent }))

const { addCartItem, createPurchaseFromCart, requestCheckout } = await import('@/lib/data/purchases')
const { resolveRoutingProduct } = await import('@/lib/data/routing-products')

beforeEach(() => {
  db.reset()
  seedCatalog(db)
  vi.stubEnv('SYNCHRON_PAYMENTS_MODE', 'mock')
})
afterEach(() => { vi.clearAllMocks(); vi.unstubAllEnvs() })

async function purchaseFor(trip: ReturnType<typeof seedTrip>, permitId: string) {
  const resolved = await resolveRoutingProduct('express_route')
  if (!resolved.ok) throw new Error(resolved.reason)
  const added = await addCartItem({ trip, ...ACTOR, permitId, productCode: 'express_route', product: resolved.product })
  if (!added.ok) throw new Error(added.error)
  const created = await createPurchaseFromCart({ trip, ...ACTOR, billing: billingFor(trip), companyId: null })
  if (!created.ok) throw new Error(created.error)
  return created.bundle
}

describe('Test C — two purchases on one Synchron order token', () => {
  it('each purchase keeps its own payment_references row and checkout session', async () => {
    expect(process.env.NODE_ENV).toBe('test')
    const trip = seedTrip(db, { synchron_order_token: 'ABC12345' })
    const [tx, ok] = seedPermits(db, trip.id, ['TX', 'OK'])
    // The first cart is checked out before the second line is added, so the
    // same user gets a second open cart → a second purchase on the same trip.
    const b1 = await purchaseFor(trip, tx.id as string)
    const b2 = await purchaseFor(trip, ok.id as string)
    expect(b1.purchase.id).not.toBe(b2.purchase.id)
    expect(db.table('trip_carts')).toHaveLength(2)

    const purchases = db.table('purchases') as unknown as Purchase[]
    expect(purchases.map((p) => p.synchron_order_token)).toEqual(['ABC12345', 'ABC12345'])

    const common = { billing: billingFor(trip), actorUserId: USER_ID, actorLabel: ACTOR.actorLabel, appOrigin: APP_ORIGIN, scenario: 'checkout_created' as const }
    const c1 = await requestCheckout({ bundle: b1, ...common })
    const c2 = await requestCheckout({ bundle: b2, ...common })
    expect(c1.ok && c2.ok).toBe(true)

    const refs = db.table('payment_references') as unknown as PaymentReference[]
    expect(refs).toHaveLength(2)
    expect(new Set(refs.map((r) => r.purchase_id))).toEqual(new Set([b1.purchase.id, b2.purchase.id]))
    expect(refs.every((r) => r.synchron_order_token === 'ABC12345')).toBe(true)
    expect(refs.every((r) => r.source_system === 'mock' && r.payment_status === 'pending')).toBe(true)
    expect(refs[0].stripe_checkout_session_id).toMatch(/^cs_test_mock_/)
    expect(refs[0].stripe_checkout_session_id).not.toBe(refs[1].stripe_checkout_session_id)
    expect(purchases.every((p) => p.purchase_status === 'pending_payment')).toBe(true)
  })
})

describe('Test K — Synchron unavailable at checkout', () => {
  it('returns 503 unavailable and leaves the purchase, cart lines and documents untouched', async () => {
    const trip = seedTrip(db)
    const [tx] = seedPermits(db, trip.id, ['TX'])
    db.seed('documents', [{ trip_id: trip.id, kind: 'permit', permit_id: tx.id, name: 'TX permit.pdf' }])
    const bundle = await purchaseFor(trip, tx.id as string)

    const result = await requestCheckout({
      bundle, billing: billingFor(trip), actorUserId: USER_ID, actorLabel: ACTOR.actorLabel, appOrigin: APP_ORIGIN, scenario: 'api_unavailable',
    })
    expect(result).toMatchObject({ ok: false, code: 'unavailable', status: 503 })

    const purchase = db.table('purchases')[0] as unknown as Purchase
    expect(purchase.purchase_status).toBe('pending_checkout')
    expect(db.table('trip_cart_items')).toHaveLength(1)
    expect(db.table('trip_cart_items')[0]).toMatchObject({ permit_id: tx.id, product_code: 'express_route' })
    expect(db.table('documents')).toHaveLength(1)
    expect(db.table('payment_references')).toHaveLength(0)

    const events = db.table('purchase_events') as unknown as PurchaseEvent[]
    const failure = events.find((e) => e.event === 'synchron_api_error')
    expect(failure).toBeDefined()
    expect(failure?.purchase_id).toBe(purchase.id)
    expect(failure?.detail).toMatchObject({ operation: 'requestRouteCheckout', type: 'SynchronUnavailableError' })
    expect(mocks.logOperationEvent).toHaveBeenCalledWith(expect.objectContaining({ area: 'billing', event: 'synchron_api_error', outcome: 'failed' }))
  })
})
