import {
  createCipayBuyerHandler,
  createCipayClient,
} from "@cipay/client-sdk/api";
import express from "express";
import { allowCipayRequest, rateLimited } from "../shared/rate-limit.js";

const client = createCipayClient({
  apiKey: process.env.CIPAY_SANDBOX_API_KEY!,
  mode: "sandbox",
});

const handler = createCipayBuyerHandler({
  client,
  storefront: { locator: process.env.CIPAY_STOREFRONT_LOCATOR! },
  resolveCustomer: async (request) => {
    const session = await readVerifiedSession(request);
    return session
      ? { subject: session.userId, customerId: session.cipayCustomerId }
      : null;
  },
});

const app = express();

app.use("/api/cipay", async (request, response) => {
  // Build an absolute URL from trusted deployment configuration.
  const incoming = new Request(
    `https://shop.example.test${request.originalUrl}`,
    {
      method: request.method,
      headers: request.headers as HeadersInit,
    },
  );

  if (!allowCipayRequest(incoming)) {
    return send(response, rateLimited());
  }

  // Bound write bodies before buffering them in memory.
  const body = request.method === "GET" || request.method === "HEAD"
    ? undefined
    : await readBody(request, 16_384);

  const result = await handler(new Request(incoming, {
    method: request.method,
    headers: request.headers as HeadersInit,
    body: body ? Buffer.from(body) : undefined,
  }));

  return send(response, result);
});

async function send(response: express.Response, result: Response) {
  response.status(result.status);
  result.headers.forEach((value, name) => response.setHeader(name, value));
  response.send(Buffer.from(await result.arrayBuffer()));
}

async function readVerifiedSession(request: Request) {
  // Application integration point: replace this stub with verified server auth.
  void request;
  return null as null | { userId: string; cipayCustomerId: string };
}

async function readBody(
  request: express.Request,
  limit: number,
): Promise<Uint8Array> {
  const chunks: Buffer[] = [];
  let size = 0;

  for await (const chunk of request) {
    size += chunk.length;
    if (size > limit) throw new Error("Request body too large");
    chunks.push(chunk);
  }

  return Buffer.concat(chunks);
}

app.listen(3000);
