import type { APIRoute } from 'astro';
import { env } from 'cloudflare:workers';
import type { Querier } from '@platform-modules/db';
import { stripe } from '@platform-modules/billing/stripe';
import {
  startCheckout,
  isCheckoutValidationError,
  isPaymentFailedError,
  isOrderNotChargeableError,
  isFulfillmentIncompleteError,
  type CheckoutDeps,
  type OrdersSchema,
} from '@platform-modules/commerce-checkout';
import type { FulfillmentDbSchema } from '@platform-modules/commerce-fulfillment'; // type-only used for cast below
import { appendEntry } from '@platform-modules/ledger';
import type { LedgerSeam } from '@platform-modules/billing';
import { getTransactionalDb, type DbEnv } from '../../../lib/db.js';
import { getSession, type SessionEnv } from '../../../lib/session.js';
import { getStoreSettings } from '../../../lib/settings.js';
import { createDbIntentStore } from '../../../lib/checkout-store.js';
import {
  createStorefrontFulfillmentPorts,
  createR2StorageAdapter,
  createNoOpStorageAdapter,
} from '../../../lib/fulfillment.js';
import { jsonError, jsonOk } from '../../../lib/http.js';

export const prerender = false;

const MAX_START_BODY_BYTES = 64 * 1024;

/** Stream-read the body with a byte cap — never trust Content-Length (chunked TE has none). */
async function readCappedStartBody(
  request: Request,
): Promise<{ ok: true; body: string } | { ok: false }> {
  const reader = request.body?.getReader();
  if (!reader) return { ok: true, body: '' };

  const chunks: Uint8Array[] = [];
  let total = 0;
  for (;;) {
    const { done, value } = await reader.read();
    if (done) break;
    if (!value) continue;
    total += value.byteLength;
    if (total > MAX_START_BODY_BYTES) {
      await reader.cancel();
      return { ok: false };
    }
    chunks.push(value);
  }

  const merged = new Uint8Array(total);
  let offset = 0;
  for (const chunk of chunks) {
    merged.set(chunk, offset);
    offset += chunk.byteLength;
  }
  return { ok: true, body: new TextDecoder().decode(merged) };
}

type StartEnv = SessionEnv & DbEnv & { STRIPE_SECRET_KEY?: string; MEDIA?: R2Bucket };

export const POST: APIRoute = async ({ request, cookies }) => {
  const cfEnv = env as StartEnv | undefined;
  if (!cfEnv?.SESSION) return jsonError(503, 'service_unavailable', 'Session store not configured.');
  if (!cfEnv?.STRIPE_SECRET_KEY) return jsonError(503, 'payment_not_configured', 'Payment provider is not configured.');
  if (!cfEnv?.DB && !cfEnv?.DATABASE_URL) return jsonError(503, 'service_unavailable', 'Database is not configured.');

  const session = await getSession(cfEnv, cookies);
  if (!session) return jsonError(401, 'unauthorized', 'Authentication required.');

  const capped = await readCappedStartBody(request);
  if (!capped.ok) {
    return jsonError(413, 'payload_too_large', 'Request body exceeds size limit.');
  }

  let body: Record<string, unknown>;
  try {
    body = JSON.parse(capped.body) as Record<string, unknown>;
  } catch {
    return jsonError(400, 'invalid_json', 'Request body must be valid JSON.');
  }

  const rawKey = typeof body.idempotencyKey === 'string' ? body.idempotencyKey : null;
  if (!rawKey) return jsonError(400, 'missing_field', 'idempotencyKey is required.');
  const idempotencyKey = `${session.userId}:${rawKey}`;
  if (!body.cart) return jsonError(400, 'missing_field', 'cart is required.');
  const buyerCountry = typeof body.buyerCountry === 'string' ? body.buyerCountry : '';
  if (!buyerCountry) return jsonError(400, 'missing_field', 'buyerCountry is required.');
  // Trust-boundary cap: reject oversized carts before any DB work.
  const cartLines = Array.isArray((body.cart as Record<string, unknown>).lines)
    ? ((body.cart as Record<string, unknown>).lines as unknown[])
    : [];
  if (cartLines.length > 100) return jsonError(422, 'cart_too_large', 'Cart may not exceed 100 lines.');

  for (const line of cartLines) {
    const qty = line && typeof line === 'object' ? (line as Record<string, unknown>).qty : undefined;
    if (typeof qty !== 'number' || !Number.isInteger(qty) || qty <= 0) {
      return jsonError(422, 'invalid_qty', 'Cart line quantity must be a positive integer.');
    }
  }

  const currency = typeof body.currency === 'string' ? body.currency : 'USD';

  const { db } = getTransactionalDb(cfEnv);
  const { priceMode } = await getStoreSettings(db as unknown as Querier);
  const ledger: LedgerSeam = { appendEntry: appendEntry as LedgerSeam['appendEntry'] };
  const storage = cfEnv.MEDIA ? createR2StorageAdapter(cfEnv.MEDIA) : createNoOpStorageAdapter();
  const fulfillment = createStorefrontFulfillmentPorts(
    db as unknown as import('@platform-modules/db').TransactionalDatabase<FulfillmentDbSchema>,
    storage,
  );

  const deps = {
    db,
    provider: stripe({ secretKey: cfEnv.STRIPE_SECRET_KEY }),
    intentStore: createDbIntentStore(db as unknown as Querier),
    ledger,
    fulfillment,
  } as unknown as CheckoutDeps<OrdersSchema>;

  const input = {
    idempotencyKey,
    buyerRef: { userId: session.userId },
    priceMode,
    currency,
    buyerCountry,
    cart: body.cart,
  } as Parameters<typeof startCheckout>[1];

  try {
    const result = await startCheckout(deps, input);
    return jsonOk(result);
  } catch (e) {
    if (isCheckoutValidationError(e)) return jsonError(422, e.reason, e.message);
    if (isOrderNotChargeableError(e)) return jsonError(409, 'order_conflict', 'Order not in a chargeable state.');
    if (isPaymentFailedError(e)) return jsonError(402, 'payment_failed', 'Payment could not be processed.');
    if (isFulfillmentIncompleteError(e)) return jsonError(500, 'fulfillment_error', 'Order paid but fulfillment incomplete.');
    return jsonError(500, 'internal_error', 'An unexpected error occurred.');
  }
};
