/**
 * Idempotent order intake at the edge.
 *
 * A Cloudflare Worker that accepts order submissions over HTTP and guarantees
 * at-most-once processing per client-supplied Idempotency-Key. A retried POST
 * — the client timed out, the network blipped, a proxy replayed it — returns
 * the original response instead of placing a second order.
 *
 * Storage is a single Workers KV namespace. The first request for a key writes
 * a short-lived "pending" marker, processes the order, then overwrites it with
 * the final response. Concurrent retries that arrive mid-flight see the marker
 * and are told to back off rather than racing a duplicate through.
 */

interface Env {
  ORDERS: KVNamespace;
}

interface OrderRequest {
  symbol: string;
  side: "buy" | "sell";
  quantity: number;
}

interface OrderResult {
  orderId: string;
  status: "accepted";
  symbol: string;
  side: "buy" | "sell";
  quantity: number;
  acceptedAt: string;
}

// Keep a completed response addressable by its key for 24h of retries.
const RESULT_TTL_SECONDS = 60 * 60 * 24;
// A pending marker should outlive a single request but expire quickly if the
// Worker is evicted mid-flight, so a stuck key self-heals.
const PENDING_TTL_SECONDS = 30;

const json = (body: unknown, status = 200): Response =>
  new Response(JSON.stringify(body), {
    status,
    headers: { "content-type": "application/json" },
  });

function validate(body: unknown): OrderRequest | null {
  if (typeof body !== "object" || body === null) return null;
  const { symbol, side, quantity } = body as Record<string, unknown>;
  if (typeof symbol !== "string" || symbol.length === 0) return null;
  if (side !== "buy" && side !== "sell") return null;
  if (typeof quantity !== "number" || !Number.isInteger(quantity) || quantity <= 0) return null;
  return { symbol, side, quantity };
}

async function placeOrder(order: OrderRequest): Promise<OrderResult> {
  // Stand-in for the real order-management call. In production this is where the
  // FIX session or OMS gateway lives; here we just mint a deterministic receipt.
  return {
    orderId: crypto.randomUUID(),
    status: "accepted",
    symbol: order.symbol,
    side: order.side,
    quantity: order.quantity,
    acceptedAt: new Date().toISOString(),
  };
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return json({ error: "method_not_allowed" }, 405);
    }

    const key = request.headers.get("Idempotency-Key");
    if (!key) {
      return json({ error: "missing_idempotency_key" }, 400);
    }

    // Replay path: a finished result already exists for this key.
    const cached = await env.ORDERS.get(`result:${key}`);
    if (cached) {
      return new Response(cached, {
        status: 200,
        headers: { "content-type": "application/json", "Idempotent-Replay": "true" },
      });
    }

    // Claim the key. If another in-flight request already claimed it, ask the
    // caller to retry once the first one settles.
    const pending = await env.ORDERS.get(`pending:${key}`);
    if (pending) {
      return json({ error: "in_progress", retryAfter: PENDING_TTL_SECONDS }, 409);
    }
    await env.ORDERS.put(`pending:${key}`, "1", { expirationTtl: PENDING_TTL_SECONDS });

    const order = validate(await request.json().catch(() => null));
    if (!order) {
      await env.ORDERS.delete(`pending:${key}`);
      return json({ error: "invalid_order" }, 422);
    }

    const result = await placeOrder(order);
    const body = JSON.stringify(result);

    await env.ORDERS.put(`result:${key}`, body, { expirationTtl: RESULT_TTL_SECONDS });
    await env.ORDERS.delete(`pending:${key}`);

    return new Response(body, { status: 201, headers: { "content-type": "application/json" } });
  },
};
