import { createHmac, timingSafeEqual } from "node:crypto";

import { NextResponse } from "next/server";

import { getAppCloudflareEnv } from "../runtime/cloudflare";

export const apiSessionCookieName = "geo_api_session";

const sessionTokenMessage = "geo-agent-api-session:v1";

interface ApiAccessOptions {
  apiAccessKey?: string;
  authDisabled?: boolean;
}

type ApiAccessResult =
  | { ok: true }
  | { ok: false; response: NextResponse<{ error: string }> };

export function requireApiAccess(
  request: Request,
  options: ApiAccessOptions = {
    apiAccessKey: getConfiguredApiAccessKey(),
    authDisabled: process.env.API_AUTH_DISABLED === "true",
  },
): ApiAccessResult {
  if (options.authDisabled) {
    return { ok: true };
  }

  if (!options.apiAccessKey) {
    return {
      ok: false,
      response: NextResponse.json(
        { error: "API access key is not configured" },
        { status: 401 },
      ),
    };
  }

  const provided = request.headers.get("x-api-key");
  if (
    provided !== options.apiAccessKey &&
    !hasValidApiSession(request, options.apiAccessKey)
  ) {
    return {
      ok: false,
      response: NextResponse.json({ error: "Unauthorized" }, { status: 401 }),
    };
  }

  return { ok: true };
}

export function hasValidApiSession(request: Request, apiAccessKey: string) {
  const cookieValue = parseCookieHeader(request.headers.get("cookie"))[
    apiSessionCookieName
  ];
  if (!cookieValue) return false;
  return safeEqual(cookieValue, createApiSessionToken(apiAccessKey));
}

export function createApiSessionToken(apiAccessKey: string) {
  return createHmac("sha256", apiAccessKey)
    .update(sessionTokenMessage)
    .digest("hex");
}

export function getConfiguredApiAccessKey() {
  return getAppCloudflareEnv()?.API_ACCESS_KEY ?? process.env.API_ACCESS_KEY;
}

function parseCookieHeader(value: string | null) {
  const cookies: Record<string, string> = {};
  if (!value) return cookies;

  for (const part of value.split(";")) {
    const separator = part.indexOf("=");
    if (separator < 0) continue;
    const name = part.slice(0, separator).trim();
    const cookieValue = part.slice(separator + 1).trim();
    if (name) cookies[name] = decodeURIComponent(cookieValue);
  }

  return cookies;
}

function safeEqual(left: string, right: string) {
  const leftBuffer = Buffer.from(left);
  const rightBuffer = Buffer.from(right);
  if (leftBuffer.length !== rightBuffer.length) return false;
  return timingSafeEqual(leftBuffer, rightBuffer);
}
