import { env } from "@/lib/env";
import { UpdotAuthManager } from "@/lib/updot-auth";
import { Hono } from "hono";

export const UpdotProxyRoutes = new Hono();

const FORBIDDEN_RELOGIN_COOLDOWN_MS = 60_000;
let lastForbiddenRelogin = 0;

UpdotProxyRoutes.all("/admin/*", async (ctx) => {
  const upstream = env.GetString("UPDOT_CORE_BASE_URL");
  if (!upstream) {
    return ctx.json({ error: "Updot Core Base URL not configured" }, 500);
  }
  const path = ctx.req.path;
  const url = `${upstream}${path}`;
  const method = ctx.req.method;

  const incomingUrl = new URL(ctx.req.url);
  const upstreamUrl = new URL(
    incomingUrl.pathname + incomingUrl.search,
    upstream,
  );

  const authManager = UpdotAuthManager.getInstance();

  const doProxyRequest = async (session: {
    admin_session: string;
    admin_session_id: string;
    admin_scopes?: string;
  }) => {
    const headers = new Headers(ctx.req.raw.headers);

    // admin_scopes carries the account's permission grants; scoped Updot
    // endpoints (e.g. itinerary units) answer 403 without it.
    const cookies = [
      `kc_session=${session.admin_session}`,
      `kc_session_id=${session.admin_session_id}`,
      `__sstkn=${session.admin_session}`,
      `__ssid=${session.admin_session_id}`,
      `admin_session=${session.admin_session}`,
      `admin_session_id=${session.admin_session_id}`,
      session.admin_scopes ? `admin_scopes=${session.admin_scopes}` : "",
    ]
      .filter(Boolean)
      .join("; ");

    headers.set("Cookie", cookies);
    // Hop-by-hop / framing headers describe the incoming connection, not the
    // new request. The body is re-sent as a buffered string below, so a copied
    // `transfer-encoding: chunked` (the console proxy streams bodies) makes
    // undici reject the request with "invalid transfer-encoding header", and a
    // stale content-length would mis-frame it. Let fetch recompute both.
    for (const h of [
      "host",
      "connection",
      "keep-alive",
      "transfer-encoding",
      "content-length",
      "te",
      "trailer",
      "upgrade",
      "expect",
    ]) {
      headers.delete(h);
    }

    const upstreamOrigin = new URL(upstream).origin;
    headers.set("Origin", upstreamOrigin);
    headers.set("Referer", `${upstreamOrigin}/`);

    const body = ["GET", "HEAD"].includes(method)
      ? undefined
      : await ctx.req.text();

    return await fetch(upstreamUrl.toString(), { method, headers, body });
  };

  try {
    let session = await authManager.getOrLogin();
    if (!session) {
      return ctx.json({ error: "Failed to authenticate with Updot Core" }, 401);
    }

    let response = await doProxyRequest(session);

    // 401 means the session is dead: always re-login. 403 is usually a missing
    // permission, which a fresh session will not fix — and every signin rotates
    // the shared admin session for all other callers. So a 403 only triggers a
    // re-login if none has happened in the last minute.
    const forbiddenRetryAllowed =
      response.status === 403 &&
      Date.now() - lastForbiddenRelogin > FORBIDDEN_RELOGIN_COOLDOWN_MS;

    if (response.status === 401 || forbiddenRetryAllowed) {
      console.warn(
        `[UpdotProxy] Got ${response.status}. Invalidating session and retrying...`,
      );
      if (response.status === 403) lastForbiddenRelogin = Date.now();
      authManager.invalidate();

      session = await authManager.getOrLogin();
      if (session) {
        response = await doProxyRequest(session);
      }
    }

    const respHeaders = new Headers(response.headers);
    respHeaders.delete("content-encoding");
    respHeaders.delete("content-length");

    if (!response.ok) {
      const errorBody = await response.clone().text();
      console.error(
        `[UpdotProxy] Error (${response.status}) from ${url}:`,
        errorBody,
      );
    }

    return new Response(response.body, {
      status: response.status,
      headers: respHeaders,
    });
  } catch (err) {
    console.error(`[UpdotProxy] Proxy Exception:`, err);
    return ctx.json({ error: "Proxy Failed", details: String(err) }, 502);
  }
});
