import 'server-only'

import { createHash } from 'node:crypto'
import {
  SynchronUnavailableError,
  type CardOnFile,
  type CardSetupSession,
  type CardSetupStatus,
  type CustomerPaymentStatus,
  type PurchaseItemReport,
  type PurchaseStatusReport,
  type RoutePurchaseRequest,
  type SynchronPaymentsAdapter,
} from './types'

/**
 * Development mock of the Synchron payments API (task §27).
 *
 * Selected ONLY by src/lib/integrations/synchron-payments/index.ts, which
 * refuses it in production. Every report it produces carries
 * source_system 'mock' so the confirmation code path can refuse it too.
 *
 * The scenario is chosen per call (see `mockScenario()` in index.ts):
 *   card_on_file · card_missing · card_setup_pending · card_setup_failed ·
 *   checkout_created · payment_pending · payment_confirmed · payment_failed ·
 *   multi_item_paid · multi_purchase · routing_processing · routing_completed ·
 *   api_unavailable
 *
 * Checkout URLs point at the local dev page /dev-preview/synchron-checkout,
 * where the tester picks the outcome; that page then posts a mock
 * confirmation through the same verified path a real callback would use.
 */

export type MockScenario =
  | 'card_on_file' | 'card_missing' | 'card_setup_pending' | 'card_setup_failed'
  | 'checkout_created' | 'payment_pending' | 'payment_confirmed' | 'payment_failed'
  | 'multi_item_paid' | 'multi_purchase' | 'routing_processing' | 'routing_completed'
  | 'api_unavailable'

export const MOCK_SCENARIOS: readonly MockScenario[] = [
  'card_on_file', 'card_missing', 'card_setup_pending', 'card_setup_failed',
  'checkout_created', 'payment_pending', 'payment_confirmed', 'payment_failed',
  'multi_item_paid', 'multi_purchase', 'routing_processing', 'routing_completed',
  'api_unavailable',
] as const

export function isMockScenario(value: unknown): value is MockScenario {
  return typeof value === 'string' && (MOCK_SCENARIOS as readonly string[]).includes(value)
}

const MOCK_CARDS: CardOnFile[] = [
  { id: 'mock_card_visa_4242', brand: 'Visa', last4: '4242', expMonth: 12, expYear: 2030, available: true, isDefault: true },
  { id: 'mock_card_mc_4444', brand: 'Mastercard', last4: '4444', expMonth: 6, expYear: 2029, available: true, isDefault: false },
]

const stableId = (...parts: string[]) => createHash('sha256').update(parts.join('|')).digest('hex').slice(0, 24)

/**
 * In-memory outcomes chosen on the dev checkout page. Module-level state is
 * fine for a single dev server; it never exists in production.
 */
// Kept on globalThis: in `next dev` every route handler is its own module
// graph, so a plain module-level Map written by the dev-preview outcome route
// would not be visible to the route that later reads it.
type MockState = {
  outcomes: Map<string, 'paid' | 'failed' | 'cancelled' | 'pending'>
  cardSetups: Map<string, 'confirmed' | 'failed' | 'pending'>
}
const mockState: MockState = ((globalThis as { __hhaSynchronMock?: MockState }).__hhaSynchronMock ??= {
  outcomes: new Map(),
  cardSetups: new Map(),
})
const chosenOutcomes = mockState.outcomes
const chosenCardSetups = mockState.cardSetups

export function mockChooseCheckoutOutcome(purchaseReference: string, outcome: 'paid' | 'failed' | 'cancelled' | 'pending') {
  chosenOutcomes.set(purchaseReference, outcome)
}
export function mockChooseCardSetupOutcome(setupReference: string, outcome: 'confirmed' | 'failed' | 'pending') {
  chosenCardSetups.set(setupReference, outcome)
}

function report(req: { purchaseReference: string }, status: PurchaseStatusReport['paymentStatus'], version: string): PurchaseStatusReport {
  const paid = status === 'paid'
  return {
    purchaseReference: req.purchaseReference,
    paymentStatus: status,
    synchronPurchaseToken: `mock_sp_${stableId(req.purchaseReference)}`,
    stripeCheckoutSessionId: `cs_test_mock_${stableId(req.purchaseReference, 'cs')}`,
    stripePaymentIntentId: paid ? `pi_test_mock_${stableId(req.purchaseReference, 'pi')}` : null,
    stripeChargeId: paid ? `ch_test_mock_${stableId(req.purchaseReference, 'ch')}` : null,
    paymentConfirmedAt: paid ? new Date().toISOString() : null,
    externalEventId: `mock_evt_${stableId(req.purchaseReference, status, version)}`,
  }
}

export function createMockSynchronPayments(scenario: MockScenario, appOrigin: string): SynchronPaymentsAdapter {
  const unavailable = () => { throw new SynchronUnavailableError('Synchron Permits (mock) is unavailable: scenario api_unavailable.') }
  const guard = () => { if (scenario === 'api_unavailable') unavailable() }

  return {
    mode: 'mock',

    async createRoutePurchase(req) {
      guard()
      return { synchronPurchaseToken: `mock_sp_${stableId(req.purchaseReference)}` }
    },

    async requestRouteCheckout(req: RoutePurchaseRequest) {
      guard()
      const url = new URL(`/dev-preview/synchron-checkout/${encodeURIComponent(req.purchaseReference)}`, appOrigin)
      url.searchParams.set('return', req.returnUrl)
      url.searchParams.set('cancel', req.cancelUrl)
      url.searchParams.set('total', String(req.totalCents))
      return {
        synchronPurchaseToken: `mock_sp_${stableId(req.purchaseReference)}`,
        stripeCheckoutSessionId: `cs_test_mock_${stableId(req.purchaseReference, 'cs')}`,
        checkoutUrl: url.toString(),
        expiresAt: new Date(Date.now() + 30 * 60_000).toISOString(),
      }
    },

    async getRoutePurchaseStatus(purchaseReference) {
      guard()
      const chosen = chosenOutcomes.get(purchaseReference)
      const status = chosen === 'paid' ? 'paid' : chosen === 'failed' ? 'failed' : chosen === 'cancelled' ? 'cancelled'
        : scenario === 'payment_confirmed' || scenario === 'multi_item_paid' || scenario === 'multi_purchase' ? 'paid'
        : scenario === 'payment_failed' ? 'failed' : 'pending'
      return report({ purchaseReference }, status, 'v1')
    },

    async reconcilePurchaseStatus(purchaseReference, billing) {
      return this.getRoutePurchaseStatus(purchaseReference, billing)
    },

    async getRoutePurchaseItems(purchaseReference): Promise<PurchaseItemReport[]> {
      guard()
      // The mock does not know item ids; the caller maps by index. Status only.
      const status = scenario === 'routing_completed' ? 'completed' : scenario === 'routing_processing' ? 'processing' : 'queued'
      return [{ purchaseItemId: '*', fulfillmentStatus: status, routeLinks: status === 'completed' ? [`https://maps.google.com/?q=mock+${purchaseReference}`] : undefined }]
    },

    async getCustomerPaymentStatus(): Promise<CustomerPaymentStatus> {
      guard()
      if (scenario === 'card_missing') return { readiness: 'card_required', cards: [] }
      if (scenario === 'card_setup_pending') return { readiness: 'setup_pending', cards: [] }
      if (scenario === 'card_setup_failed') return { readiness: 'setup_failed', cards: [] }
      return { readiness: 'card_on_file', cards: MOCK_CARDS }
    },

    async requestCardSetup(billing, returnUrl): Promise<CardSetupSession> {
      guard()
      const setupReference = `mock_setup_${stableId(billing.hhaUserId, billing.hhaTripId, String(Date.now()))}`
      const url = new URL(`/dev-preview/synchron-card-setup/${encodeURIComponent(setupReference)}`, appOrigin)
      url.searchParams.set('return', returnUrl)
      url.searchParams.set('mode', 'add')
      return { setupUrl: url.toString(), setupReference }
    },

    async confirmCardUse(billing, cardId, returnUrl): Promise<CardSetupSession> {
      guard()
      const setupReference = `mock_confirm_${stableId(billing.hhaUserId, billing.hhaTripId, cardId, String(Date.now()))}`
      const url = new URL(`/dev-preview/synchron-card-setup/${encodeURIComponent(setupReference)}`, appOrigin)
      url.searchParams.set('return', returnUrl)
      url.searchParams.set('mode', 'confirm')
      url.searchParams.set('card', cardId)
      return { setupUrl: url.toString(), setupReference }
    },

    async getCardSetupStatus(setupReference): Promise<CardSetupStatus> {
      guard()
      const chosen = chosenCardSetups.get(setupReference)
      const status = chosen ?? (scenario === 'card_setup_failed' ? 'failed' : scenario === 'card_setup_pending' ? 'pending' : 'confirmed')
      return {
        setupReference,
        status,
        card: status === 'confirmed' ? MOCK_CARDS[0] : null,
        paymentConfirmationToken: status === 'confirmed' ? `mock_pct_${stableId(setupReference, 'pct')}` : null,
      }
    },

    async submitPermitOrder(req) {
      guard()
      if (!req.paymentConfirmationToken.startsWith('mock_pct_')) return { accepted: false, synchronOrderToken: null, paymentMethodRef: null }
      return {
        accepted: true,
        synchronOrderToken: req.billing.synchronOrderToken,
        paymentMethodRef: `${MOCK_CARDS[0].id}:${MOCK_CARDS[0].brand}:${MOCK_CARDS[0].last4}`,
      }
    },
  }
}
