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

import {
  adminTokenCookieOptions,
  expiredAdminTokenCookieOptions,
  readAdminTokenFromCookieString,
} from "@/lib/admin-token-cookie";
import { ADMIN_TOKEN_STORAGE_KEY } from "@/lib/admin-token-constants";
import { lotteryApiOrigin } from "@/lib/lottery-api-base";

const HOP_BY_HOP = new Set([
  "connection",
  "keep-alive",
  "proxy-authenticate",
  "proxy-authorization",
  "te",
  "trailers",
  "transfer-encoding",
  "upgrade",
]);

/** fetch 会解压 gzip，须去掉这些头，否则浏览器 ERR_CONTENT_DECODING_FAILED */
const STRIP_RESPONSE_HEADERS = [
  ...HOP_BY_HOP,
  "content-encoding",
  "content-length",
];

/** 浏览器请求同源 `/api/*`，Next 转发到 `LOTTERY_API_UPSTREAM`（本地/线上同一套）。 */
export async function proxyLotteryApi(
  request: NextRequest,
  pathSegments: string[],
): Promise<NextResponse> {
  const path = pathSegments.join("/");
  const target = `${lotteryApiOrigin()}/api/${path}${request.nextUrl.search}`;
  const isAdminAuthLogin =
    request.method === "POST" && path === "v1/admin/auth/login";
  const isAdminLogout =
    request.method === "POST" && path === "v1/admin/auth/logout";

  const headers = new Headers(request.headers);
  headers.delete("host");
  headers.delete("connection");
  headers.delete("accept-encoding");

  const cookieToken = readAdminTokenFromCookieString(
    request.headers.get("cookie"),
  );
  if (cookieToken && !headers.has("authorization")) {
    headers.set("authorization", `Bearer ${cookieToken}`);
  }

  const init: RequestInit = {
    method: request.method,
    headers,
    redirect: "manual",
  };

  if (request.method !== "GET" && request.method !== "HEAD") {
    init.body = await request.arrayBuffer();
  }

  let upstream: Response;
  try {
    upstream = await fetch(target, init);
  } catch (error: unknown) {
    return NextResponse.json(
      {
        msg: "Upstream Laravel unreachable",
        target,
        error: error instanceof Error ? error.message : "Unknown error",
      },
      { status: 502 },
    );
  }
  const responseHeaders = new Headers(upstream.headers);
  for (const name of STRIP_RESPONSE_HEADERS) {
    responseHeaders.delete(name);
  }

  if (isAdminAuthLogin) {
    return handleAdminLoginResponse(request, upstream, responseHeaders);
  }

  const response = new NextResponse(upstream.body, {
    status: upstream.status,
    statusText: upstream.statusText,
    headers: responseHeaders,
  });

  if (isAdminLogout) {
    response.cookies.set(
      ADMIN_TOKEN_STORAGE_KEY,
      "",
      expiredAdminTokenCookieOptions(isSecureRequest(request)),
    );
  }

  return response;
}

async function handleAdminLoginResponse(
  request: NextRequest,
  upstream: Response,
  responseHeaders: Headers,
): Promise<NextResponse> {
  let payload: unknown;
  try {
    payload = await upstream.json();
  } catch {
    const response = NextResponse.json(
      { msg: "Invalid login response from upstream" },
      { status: 502 },
    );
    response.cookies.set(
      ADMIN_TOKEN_STORAGE_KEY,
      "",
      expiredAdminTokenCookieOptions(request.nextUrl.protocol === "https:"),
    );
    return response;
  }

  const token = extractAdminToken(payload);
  if (token && upstream.ok) {
    stripAdminToken(payload);
  }

  responseHeaders.delete("content-type");
  const response = NextResponse.json(payload, {
    status: upstream.status,
    statusText: upstream.statusText,
    headers: responseHeaders,
  });

  const secure = isSecureRequest(request);
  if (token && upstream.ok) {
    response.cookies.set(
      ADMIN_TOKEN_STORAGE_KEY,
      token,
      adminTokenCookieOptions(secure),
    );
  } else {
    response.cookies.set(
      ADMIN_TOKEN_STORAGE_KEY,
      "",
      expiredAdminTokenCookieOptions(secure),
    );
  }

  return response;
}

function isSecureRequest(request: NextRequest): boolean {
  return (
    request.nextUrl.protocol === "https:" ||
    request.headers.get("x-forwarded-proto") === "https"
  );
}

function extractAdminToken(payload: unknown): string | null {
  if (!payload || typeof payload !== "object" || !("data" in payload)) {
    return null;
  }

  const data = (payload as { data?: unknown }).data;
  if (!data || typeof data !== "object" || !("token" in data)) {
    return null;
  }

  const token = (data as { token?: unknown }).token;
  return typeof token === "string" && token.trim() !== "" ? token.trim() : null;
}

function stripAdminToken(payload: unknown): void {
  if (!payload || typeof payload !== "object" || !("data" in payload)) {
    return;
  }

  const data = (payload as { data?: unknown }).data;
  if (!data || typeof data !== "object") {
    return;
  }

  delete (data as { token?: unknown }).token;
}
