import { describe, expect, it } from 'vitest'
import {
  ROUTE_PRODUCT_CODES,
  formatCents,
  isRouteProductCode,
  productCodeForRouteType,
  routeTypeForProduct,
} from '@/lib/domain/routing-products'
import {
  FULFILLMENT_STATUSES,
  PURCHASE_STATUSES,
  canTransitionFulfillment,
  canTransitionPurchase,
  isCheckoutReopenable,
  lineTotal,
  purchaseReferenceFrom,
  returnStateFor,
} from '@/lib/domain/purchases'
import type { FulfillmentStatus, PurchaseStatus } from '@/types/db'

describe('routing product codes', () => {
  it('maps code ↔ route type both ways', () => {
    expect([...ROUTE_PRODUCT_CODES]).toEqual(['express_route', 'extended_route'])
    expect(routeTypeForProduct('express_route')).toBe('express')
    expect(routeTypeForProduct('extended_route')).toBe('extended')
    expect(productCodeForRouteType('express')).toBe('express_route')
    expect(productCodeForRouteType('extended')).toBe('extended_route')
    for (const code of ROUTE_PRODUCT_CODES) expect(productCodeForRouteType(routeTypeForProduct(code))).toBe(code)
  })

  it('only accepts the two known codes', () => {
    expect(isRouteProductCode('express_route')).toBe(true)
    expect(isRouteProductCode('extended_route')).toBe(true)
    expect(isRouteProductCode('express')).toBe(false)
    expect(isRouteProductCode('')).toBe(false)
    expect(isRouteProductCode(null)).toBe(false)
    expect(isRouteProductCode(199)).toBe(false)
  })

  it('formats cents for display only', () => {
    expect(formatCents(199)).toBe('$1.99')
    expect(formatCents(1000)).toBe('$10.00')
    expect(formatCents(0)).toBe('$0.00')
    expect(formatCents(5)).toBe('$0.05')
    expect(formatCents(1398, 'USD')).toBe('$13.98')
    expect(formatCents(1398, 'CAD')).toBe('13.98 CAD')
  })

  it('sums in integer cents', () => {
    expect(lineTotal(199, 1)).toBe(199)
    expect(lineTotal(199, 3)).toBe(597)
    expect(lineTotal(1000, 0)).toBe(0)
  })
})

describe('purchase status transitions', () => {
  const from = (to: PurchaseStatus) => PURCHASE_STATUSES.filter((s) => s !== to && canTransitionPurchase(s, to))

  it('paid is reachable only from pending_payment', () => {
    // Before payment only pending_payment leads to paid; the only other entry
    // is refund_pending → paid (a refund request that was withdrawn).
    expect(from('paid')).toEqual(['pending_payment', 'refund_pending'])
    for (const s of ['draft', 'pending_checkout', 'payment_failed', 'expired', 'cancelled'] as const) {
      expect(canTransitionPurchase(s, 'paid')).toBe(false)
    }
    expect(canTransitionPurchase('pending_payment', 'paid')).toBe(true)
  })

  it('is reflexive and terminal states have no exits', () => {
    for (const s of PURCHASE_STATUSES) expect(canTransitionPurchase(s, s)).toBe(true)
    for (const target of PURCHASE_STATUSES) {
      if (target !== 'cancelled') expect(canTransitionPurchase('cancelled', target)).toBe(false)
      if (target !== 'refunded') expect(canTransitionPurchase('refunded', target)).toBe(false)
    }
  })

  it('follows the documented table', () => {
    expect(canTransitionPurchase('draft', 'pending_checkout')).toBe(true)
    expect(canTransitionPurchase('pending_checkout', 'pending_payment')).toBe(true)
    expect(canTransitionPurchase('pending_checkout', 'expired')).toBe(true)
    expect(canTransitionPurchase('pending_payment', 'payment_failed')).toBe(true)
    expect(canTransitionPurchase('payment_failed', 'pending_checkout')).toBe(true)
    expect(canTransitionPurchase('expired', 'pending_checkout')).toBe(true)
    expect(canTransitionPurchase('paid', 'refunded')).toBe(true)
    expect(canTransitionPurchase('paid', 'pending_payment')).toBe(false)
    expect(canTransitionPurchase('paid', 'cancelled')).toBe(false)
    expect(canTransitionPurchase('partially_refunded', 'refunded')).toBe(true)
    expect(canTransitionPurchase('partially_refunded', 'paid')).toBe(false)
  })

  it('checkout may be (re)opened only before payment', () => {
    const reopenable = PURCHASE_STATUSES.filter(isCheckoutReopenable)
    expect(reopenable).toEqual(['pending_checkout', 'pending_payment', 'payment_failed', 'expired'])
  })
})

describe('fulfillment transitions', () => {
  it('awaiting_payment can only be released or cancelled; completed is terminal', () => {
    expect(canTransitionFulfillment('awaiting_payment', 'ready_for_processing')).toBe(true)
    expect(canTransitionFulfillment('awaiting_payment', 'cancelled')).toBe(true)
    expect(canTransitionFulfillment('awaiting_payment', 'submitted_to_synchron')).toBe(false)
    expect(canTransitionFulfillment('awaiting_payment', 'completed')).toBe(false)
    expect(canTransitionFulfillment('ready_for_processing', 'submitted_to_synchron')).toBe(true)
    expect(canTransitionFulfillment('submitted_to_synchron', 'completed')).toBe(true)
    expect(canTransitionFulfillment('failed', 'ready_for_processing')).toBe(true)
    for (const s of FULFILLMENT_STATUSES) {
      expect(canTransitionFulfillment(s, s)).toBe(true)
      if (s !== 'completed') expect(canTransitionFulfillment('completed', s as FulfillmentStatus)).toBe(false)
      if (s !== 'cancelled') expect(canTransitionFulfillment('cancelled', s as FulfillmentStatus)).toBe(false)
    }
  })
})

describe('return page state and purchase references', () => {
  it('maps every purchase status to one of the five return states', () => {
    expect(returnStateFor('paid', { delayed: false })).toBe('confirmed')
    expect(returnStateFor('refunded', { delayed: true })).toBe('confirmed')
    expect(returnStateFor('payment_failed', { delayed: true })).toBe('failed')
    expect(returnStateFor('cancelled', { delayed: false })).toBe('cancelled')
    expect(returnStateFor('expired', { delayed: false })).toBe('cancelled')
    expect(returnStateFor('pending_payment', { delayed: false })).toBe('pending')
    expect(returnStateFor('pending_checkout', { delayed: false })).toBe('pending')
    expect(returnStateFor('pending_payment', { delayed: true })).toBe('delayed')
  })

  it('builds a URL-safe hhp_ reference from random bytes', () => {
    const ref = purchaseReferenceFrom(new Uint8Array(20).map((_, i) => i * 13))
    expect(ref).toMatch(/^hhp_[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{20}$/)
    expect(purchaseReferenceFrom(new Uint8Array([0]))).toBe('hhp_' + 'A'.repeat(20))
  })
})
