import { headers } from "next/headers";
import { resolveMnoteWebInternalUrl } from "@/lib/mnote-web/internal-url";
import type { PageAggregateProjection } from "@/lib/documents/page-aggregate";

const FORWARDED_REQUEST_HEADERS = [
  "cookie",
  "authorization",
  "x-request-id",
  "x-mnote-request-id",
  "x-trace-id",
  "x-mnote-trace-id",
  "x-session-id",
  "x-mnote-session-id",
  "x-source-channel",
  "x-mnote-source-channel",
  "x-source-client",
  "x-mnote-source-client",
  "user-agent",
] as const;

const RUST_PAGE_AGGREGATE_TIMEOUT_MS = 3_000;

export type LoadedPageAggregate = {
  page: PageAggregateProjection;
  bridge: {
    requestId: string;
    traceId: string;
    queryName: "documents.page.get";
  };
};

function copyForwardHeaderIfPresent(target: Headers, source: Headers, name: string) {
  const value = source.get(name);
  if (value) {
    target.set(name, value);
  }
}

function readHeaderAlias(source: Headers, ...names: string[]) {
  for (const name of names) {
    const value = source.get(name);
    if (value) return value;
  }
  return "";
}

function copyNormalizedHeader(target: Headers, source: Headers, targetName: string, ...sourceNames: string[]) {
  const value = readHeaderAlias(source, ...sourceNames);
  if (value) {
    target.set(targetName, value);
  }
}

function buildForwardHeaders(source: Headers) {
  const requestHeaders = new Headers({
    accept: "application/json",
  });
  copyForwardHeaderIfPresent(requestHeaders, source, "cookie");
  copyForwardHeaderIfPresent(requestHeaders, source, "authorization");
  copyNormalizedHeader(requestHeaders, source, "x-request-id", "x-request-id", "x-mnote-request-id");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-request-id", "x-mnote-request-id", "x-request-id");
  copyNormalizedHeader(requestHeaders, source, "x-trace-id", "x-trace-id", "x-mnote-trace-id");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-trace-id", "x-mnote-trace-id", "x-trace-id");
  copyNormalizedHeader(requestHeaders, source, "x-session-id", "x-session-id", "x-mnote-session-id");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-session-id", "x-mnote-session-id", "x-session-id");
  copyNormalizedHeader(requestHeaders, source, "x-source-channel", "x-source-channel", "x-mnote-source-channel");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-source-channel", "x-mnote-source-channel", "x-source-channel");
  copyNormalizedHeader(requestHeaders, source, "x-source-client", "x-source-client", "x-mnote-source-client");
  copyNormalizedHeader(requestHeaders, source, "x-mnote-source-client", "x-mnote-source-client", "x-source-client");
  copyForwardHeaderIfPresent(requestHeaders, source, "user-agent");
  return requestHeaders;
}

type RustPageAggregateSnapshotResponse = {
  ok?: boolean;
  schema?: string;
  result?: unknown;
  requestId?: string | null;
  traceId?: string | null;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object";
}

function isBoolean(value: unknown) {
  return typeof value === "boolean";
}

function isNullableNumber(value: unknown) {
  return value === null || typeof value === "number";
}

function isNullableString(value: unknown) {
  return value === null || typeof value === "string";
}

function isTrustedMnoteWebInternalUrl(raw: string) {
  try {
    const url = new URL(raw);
    const hostname = url.hostname.replace(/^\[(.*)\]$/, "$1").trim().toLowerCase();
    return (
      hostname === "localhost" ||
      hostname === "127.0.0.1" ||
      hostname === "::1" ||
      hostname === "0.0.0.0" ||
      hostname === "::"
    );
  } catch {
    return false;
  }
}

function isRustPageAggregateProjection(
  value: unknown,
  input: { documentId: string; workspaceId?: string | null },
): value is PageAggregateProjection {
  if (!isRecord(value)) return false;
  const record = value as Record<string, unknown>;
  const identity = isRecord(record.identity) ? record.identity : null;
  const head = isRecord(record.head) ? record.head : null;
  const permissions = head && isRecord(head.permissions) ? head.permissions : null;
  const layout = isRecord(record.layout) ? record.layout : null;
  const pageOptions = layout && isRecord(layout.pageOptions) ? layout.pageOptions : null;
  const body = isRecord(record.body) ? record.body : null;
  const tree = isRecord(record.tree) ? record.tree : null;
  const stats = isRecord(record.stats) ? record.stats : null;
  const requestedWorkspaceId = input.workspaceId?.trim() || null;
  return (
    record.schema === "mnote.page_aggregate.v1" &&
    typeof record.projectionVersion === "number" &&
    typeof record.source === "string" &&
    Boolean(identity) &&
    identity.documentId === input.documentId &&
    typeof identity.workspaceId === "string" &&
    (!requestedWorkspaceId || identity.workspaceId === requestedWorkspaceId) &&
    Boolean(head) &&
    typeof head.title === "string" &&
    isNullableString(head.updatedAt) &&
    Boolean(permissions) &&
    isBoolean(permissions.readOnly) &&
    isBoolean(permissions.disableDownload) &&
    isBoolean(permissions.disableCopy) &&
    Boolean(layout) &&
    Boolean(pageOptions) &&
    Boolean(body) &&
    "content" in body &&
    isNullableNumber(body.revision) &&
    isNullableString(body.conflictDetectionKey) &&
    Boolean(tree) &&
    Object.prototype.hasOwnProperty.call(tree, "pageSubtree") &&
    Boolean(stats) &&
    typeof stats.wordCount === "number" &&
    typeof stats.characterCount === "number" &&
    typeof stats.blockCount === "number" &&
    typeof stats.todoTotal === "number" &&
    typeof stats.todoDone === "number"
  );
}

export async function loadPageAggregateFromRustSnapshot(input: {
  request: Request;
  documentId: string;
  workspaceId?: string | null;
}): Promise<LoadedPageAggregate | null> {
  const internalBaseUrl = await resolveMnoteWebInternalUrl();
  if (!isTrustedMnoteWebInternalUrl(internalBaseUrl)) {
    return null;
  }
  const url = new URL(`/api/page-aggregate/${encodeURIComponent(input.documentId)}`, `${internalBaseUrl}/`);
  const workspaceId = input.workspaceId?.trim();
  if (workspaceId) {
    url.searchParams.set("workspaceId", workspaceId);
  }

  let response: Response;
  try {
    response = await fetch(url.toString(), {
      method: "GET",
      headers: buildForwardHeaders(input.request.headers),
      cache: "no-store",
      signal: AbortSignal.timeout(RUST_PAGE_AGGREGATE_TIMEOUT_MS),
    });
  } catch {
    return null;
  }

  if (!response.ok) {
    return null;
  }

  const payload = await response.json().catch(() => null) as RustPageAggregateSnapshotResponse | null;
  const page = payload?.result;
  if (
    payload?.schema !== "mnote.page_aggregate.v1" ||
    !isRustPageAggregateProjection(page, input)
  ) {
    return null;
  }

  return {
    page,
    bridge: {
      requestId: typeof payload.requestId === "string" && payload.requestId.trim() ? payload.requestId : "unknown",
      traceId: typeof payload.traceId === "string" && payload.traceId.trim() ? payload.traceId : "unknown",
      queryName: "documents.page.get",
    },
  };
}

async function buildServerBridgeRequest(pathname: string): Promise<Request> {
  const headerList = await headers();
  const requestHeaders = new Headers();
  FORWARDED_REQUEST_HEADERS.forEach((name) => {
    copyForwardHeaderIfPresent(requestHeaders, headerList, name);
  });
  const internalBaseUrl = await resolveMnoteWebInternalUrl();

  return new Request(new URL(pathname, `${internalBaseUrl}/`).toString(), {
    method: "GET",
    headers: requestHeaders,
  });
}

export async function loadPageAggregate(input: {
  request: Request;
  documentId: string;
  workspaceId?: string | null;
}): Promise<LoadedPageAggregate | null> {
  return loadPageAggregateFromRustSnapshot(input);
}

export async function loadPageAggregateFromNextHeaders(input: {
  documentId: string;
  workspaceId?: string | null;
}): Promise<LoadedPageAggregate | null> {
  const request = await buildServerBridgeRequest("/documents/page");
  return loadPageAggregate({
    request,
    documentId: input.documentId,
    workspaceId: input.workspaceId,
  });
}
