import { type NextRequest } from "next/server";

import {
  applyCorsHeadersForRequest,
  buildOriginRejectionResponse,
  buildPreflightResponse,
} from "./_shared/cors";
import { resolveUpstreamForProxy } from "./_shared/upstream";

export const dynamic = "force-dynamic";

const tenantCookieName = process.env.ATHENA_AUTH_TENANT_COOKIE_NAME || "athena_auth_tenant";
const tenantHeaderCookieName =
  process.env.ATHENA_AUTH_TENANT_HEADER_COOKIE_NAME || "athena_auth_tenant_header";

function buildUpstreamUrl(request: NextRequest, upstreamBase: URL): URL {
  const url = new URL(upstreamBase.toString());
  url.pathname = "/";
  url.search = request.nextUrl.search;
  return url;
}

function formatUpstreamRoute(url: URL): string {
  return `${url.pathname}${url.search}`;
}

function normalizeAuthBasePath(raw: string | undefined): string {
  const configured = raw?.trim();
  if (!configured || configured === "/") return "/api/auth";
  return configured.startsWith("/") ? configured : `/${configured}`;
}

function joinBasePath(basePath: string, relativePath: string): string {
  const trimmedBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
  const trimmedRelative = relativePath.replace(/^\/+/, "");
  if (!trimmedRelative) return trimmedBase || "/";
  const joined = `${trimmedBase}/${trimmedRelative}`;
  return joined.replace(/\/{2,}/g, "/");
}

function buildFallbackUpstreamUrl(originalUrl: URL, relativePath: string): URL | null {
  const fallbackBasePath = normalizeAuthBasePath(process.env.NEXT_PUBLIC_AUTH_BASE_PATH);
  const fallbackPathname = joinBasePath(fallbackBasePath, relativePath);
  if (fallbackPathname === originalUrl.pathname) return null;

  const fallbackUrl = new URL(originalUrl.toString());
  fallbackUrl.pathname = fallbackPathname;
  return fallbackUrl;
}

function cloneRequestBody(body: ArrayBuffer | undefined): ArrayBuffer | undefined {
  return body ? body.slice(0) : undefined;
}

function getRequestId(request: NextRequest): string {
  return (
    request.headers.get("x-request-id") ||
    request.headers.get("x-athena-proxy-request-id") ||
    crypto.randomUUID()
  );
}

function isValidHeaderName(value: string): boolean {
  return /^[a-z0-9-]+$/i.test(value);
}

function applyTenantHeaderFromCookie(request: NextRequest, headers: Headers) {
  const hasExplicitTenantHeader =
    request.headers.get("x-athena-tenant") || request.headers.get("x-athena-client");
  if (hasExplicitTenantHeader) return;

  const tenantValue = request.cookies.get(tenantCookieName)?.value?.trim() ?? "";
  if (!tenantValue) return;

  const headerFromCookie =
    request.cookies.get(tenantHeaderCookieName)?.value?.trim() || "x-athena-tenant";
  const normalizedHeaderName = headerFromCookie.toLowerCase();
  if (!isValidHeaderName(normalizedHeaderName)) return;

  headers.set(normalizedHeaderName, tenantValue);
}

function applyProxyDiagnosticHeaders(
  headers: Headers,
  {
    requestId,
    incomingPath,
    upstreamRoute,
    upstreamSource,
  }: {
    requestId: string;
    incomingPath: string;
    upstreamRoute: string;
    upstreamSource: string;
  },
) {
  headers.set("x-athena-proxy-request-id", requestId);
  headers.set("x-athena-proxy-incoming-path", incomingPath);
  headers.set("x-athena-proxy-upstream-route", upstreamRoute);
  headers.set("x-athena-proxy-upstream-source", upstreamSource);
}

async function readUpstreamErrorPayload(response: Response): Promise<unknown> {
  const contentType = response.headers.get("content-type") ?? "";
  if (contentType.toLowerCase().includes("application/json")) {
    return response
      .clone()
      .json()
      .catch(() => null);
  }

  const raw = await response
    .clone()
    .text()
    .catch(() => "");
  const trimmed = raw.trim();
  if (!trimmed) return null;

  return {
    text: trimmed.slice(0, 2000),
    truncated: trimmed.length > 2000,
  };
}

async function proxyAuthRoot(request: NextRequest) {
  const originRejection = buildOriginRejectionResponse(request, "proxy_request");
  if (originRejection) return originRejection;

  const upstream = resolveUpstreamForProxy(request);
  if (!upstream.ok) return upstream.response;

  const relativePath = "";
  let url = buildUpstreamUrl(request, upstream.upstreamBase);
  let upstreamRoute = formatUpstreamRoute(url);
  const startedAt = performance.now();
  const requestId = getRequestId(request);
  const incomingPath = request.nextUrl.pathname;
  const requestBody =
    request.method === "GET" || request.method === "HEAD"
      ? undefined
      : await request.arrayBuffer();
  let fallbackAttempted = false;
  let fallbackUpstreamRoute: string | null = null;
  let fallbackStatus: number | null = null;
  let fallbackUpstreamError: unknown = null;
  let primaryUpstreamError: unknown = null;
  let fallbackUsed = false;

  const headers = new Headers(request.headers);
  headers.delete("host");
  headers.set("x-athena-proxy-request-id", requestId);
  applyTenantHeaderFromCookie(request, headers);

  console.info("[athena-proxy] forwarding auth root request", {
    requestId,
    method: request.method,
    incomingPath,
    upstream: url.toString(),
  });

  let response: Response;
  try {
    response = await fetch(url, {
      method: request.method,
      headers,
      body: cloneRequestBody(requestBody),
      redirect: "manual",
      cache: "no-store",
    });
  } catch (error) {
    const proxyElapsedMs = Math.max(0, Math.round(performance.now() - startedAt));
    const detail = error instanceof Error ? error.message : "Unknown proxy error";
    const upstreamErrorHeaders = new Headers({
      "x-athena-proxy-time": `${proxyElapsedMs}ms`,
      "x-athena-proxy-error": "upstream_unreachable",
    });
    applyProxyDiagnosticHeaders(upstreamErrorHeaders, {
      requestId,
      incomingPath,
      upstreamRoute,
      upstreamSource: upstream.source,
    });
    applyCorsHeadersForRequest(request, upstreamErrorHeaders);
    console.error("[athena-proxy] auth root upstream request failed", {
      requestId,
      method: request.method,
      incomingPath,
      upstream: url.toString(),
      proxyElapsedMs,
      detail,
    });
    return Response.json(
      {
        code: "UPSTREAM_UNREACHABLE",
        message:
          "Auth upstream request failed. Verify ATHENA_AUTH_ORIGIN/NEXT_PUBLIC_AUTH_BASE_URL and ensure athena-auth server is running.",
        requestId,
        method: request.method,
        incomingPath,
        upstreamRoute,
        upstreamSource: upstream.source,
        upstream: url.toString(),
        detail,
      },
      {
        status: 502,
        headers: upstreamErrorHeaders,
      },
    );
  }

  if (response.status === 404) {
    primaryUpstreamError = await readUpstreamErrorPayload(response);
    const fallbackUrl = buildFallbackUpstreamUrl(url, relativePath);
    if (fallbackUrl) {
      fallbackAttempted = true;
      const resolvedFallbackRoute = formatUpstreamRoute(fallbackUrl);
      fallbackUpstreamRoute = resolvedFallbackRoute;
      console.warn("[athena-proxy] auth root primary route returned 404; attempting fallback auth base path", {
        requestId,
        method: request.method,
        incomingPath,
        primaryUpstreamRoute: upstreamRoute,
        fallbackUpstreamRoute,
      });

      try {
        const fallbackResponse = await fetch(fallbackUrl, {
          method: request.method,
          headers,
          body: cloneRequestBody(requestBody),
          redirect: "manual",
          cache: "no-store",
        });
        fallbackStatus = fallbackResponse.status;
        if (fallbackResponse.status !== 404) {
          response = fallbackResponse;
          url = fallbackUrl;
          upstreamRoute = resolvedFallbackRoute;
          fallbackUsed = true;
        } else {
          fallbackUpstreamError = await readUpstreamErrorPayload(fallbackResponse);
        }
      } catch (fallbackError) {
        fallbackUpstreamError = {
          detail:
            fallbackError instanceof Error
              ? fallbackError.message
              : "Unknown fallback proxy error",
        };
      }
    }
  }

  const proxyElapsedMs = Math.max(0, Math.round(performance.now() - startedAt));
  const forwardedHeaders = new Headers(response.headers);
  const athenaTime = response.headers.get("x-athena-time");
  if (athenaTime) {
    forwardedHeaders.set("x-athena-time", athenaTime);
    forwardedHeaders.set("x-athena-upstream-time", athenaTime);
  }
  forwardedHeaders.set("x-athena-proxy-time", `${proxyElapsedMs}ms`);
  applyProxyDiagnosticHeaders(forwardedHeaders, {
    requestId,
    incomingPath,
    upstreamRoute,
    upstreamSource: upstream.source,
  });
  applyCorsHeadersForRequest(request, forwardedHeaders);

  console.info("[athena-proxy] auth root upstream response", {
    requestId,
    method: request.method,
    incomingPath,
    upstream: url.toString(),
    upstreamStatus: response.status,
    proxyElapsedMs,
    fallbackAttempted,
    fallbackUsed,
    fallbackStatus,
  });
  if (fallbackAttempted) {
    forwardedHeaders.set("x-athena-proxy-fallback-attempted", "true");
  }
  if (fallbackUsed) {
    forwardedHeaders.set("x-athena-proxy-fallback-used", "true");
  }

  if (response.status === 404) {
    const upstreamError =
      fallbackAttempted && fallbackUpstreamError !== null
        ? fallbackUpstreamError
        : primaryUpstreamError ?? (await readUpstreamErrorPayload(response));
    forwardedHeaders.set("x-athena-proxy-error", "upstream_route_not_found");
    console.warn("[athena-proxy] auth root upstream route returned 404", {
      requestId,
      method: request.method,
      incomingPath,
      upstreamRoute,
      upstream: url.toString(),
      upstreamSource: upstream.source,
      proxyElapsedMs,
    });
    return Response.json(
      {
        code: "UPSTREAM_ROUTE_NOT_FOUND",
        message: `Auth upstream returned 404 for ${request.method} ${upstreamRoute}.`,
        requestId,
        method: request.method,
        incomingPath,
        upstreamRoute,
        upstreamSource: upstream.source,
        upstream: url.toString(),
        upstreamStatus: response.status,
        upstreamStatusText: response.statusText,
        proxyElapsedMs,
        fallbackAttempted,
        fallbackUsed,
        fallbackUpstreamRoute,
        fallbackStatus,
        primaryUpstreamError,
        upstreamError,
      },
      {
        status: 404,
        headers: forwardedHeaders,
      },
    );
  }

  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: forwardedHeaders,
  });
}

export async function GET(request: NextRequest) {
  return proxyAuthRoot(request);
}

export async function POST(request: NextRequest) {
  return proxyAuthRoot(request);
}

export async function PUT(request: NextRequest) {
  return proxyAuthRoot(request);
}

export async function PATCH(request: NextRequest) {
  return proxyAuthRoot(request);
}

export async function DELETE(request: NextRequest) {
  return proxyAuthRoot(request);
}

export async function OPTIONS(request: NextRequest) {
  const preflight = buildPreflightResponse(request);
  if (preflight) return preflight;
  return proxyAuthRoot(request);
}

export async function HEAD(request: NextRequest) {
  return proxyAuthRoot(request);
}
