import { randomUUID } from "crypto";
import type { ConvexHttpClient } from "convex/browser";
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
import { apiErrorResponse } from "@/lib/api-utils";
import type { PageOptionsState } from "@/types/page-options";

export type BridgeActor = {
  actorType: string;
  actorId: string;
  sessionId: string | null;
};

export type BridgeSource = {
  channel: string;
  client: string;
};

export type BridgeTarget = {
  workspaceId?: string | null;
  pageId?: string | null;
  blockId?: string | null;
};

export type BridgeRequestMeta = {
  idempotencyKey: string | null;
  validateOnly: boolean;
  dryRun: boolean;
};

export type BridgeContext = {
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  actor: BridgeActor;
  source: BridgeSource;
  tenantId: string | null;
  authToken: string | null;
  idempotencyKey: string | null;
  validateOnly: boolean;
  dryRun: boolean;
};

export type BridgeErrorCode =
  | "VALIDATION_ERROR"
  | "UNAUTHORIZED"
  | "FORBIDDEN"
  | "NOT_FOUND"
  | "TRANSPORT_ERROR"
  | "REJECTED";

export class DocumentBridgeError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly code: BridgeErrorCode,
    public readonly details?: unknown,
  ) {
    super(message);
    this.name = "DocumentBridgeError";
  }
}

export type CommandEnvelope<T> = {
  name: string;
  commandId: string;
  idempotencyKey: string | null;
  actor: BridgeActor;
  source: BridgeSource;
  target: BridgeTarget | null;
  payload: T;
  reason: string | null;
  refs: string[];
  dryRun: boolean;
  validateOnly: boolean;
};

export type QueryEnvelope<T> = {
  name: string;
  payload: T;
};

export type DocumentBridgeMutationRequest<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
> = {
  functionName: string;
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  idempotencyKey: string | null;
  actorId: string;
  payloadJson: string;
  args: TArgs;
};

export type DocumentBridgeQueryRequest<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
> = {
  functionName: string;
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  actorId: string;
  payloadJson: string;
  args: TArgs;
};

const DOCUMENT_BRIDGE_QUERY_FUNCTIONS = {
  "documents.content.get": "documents:getContent",
  "documents.meta.get": "documents:getMeta",
  "blocks.get": "documents:getContent",
} as const satisfies Record<string, string>;

const DOCUMENT_BRIDGE_MUTATION_FUNCTIONS = {
  "documents.create": "documents:createWithParentReference",
  "documents.move": "documents:move",
  "documents.delete": "documents:softDelete",
  "documents.restore": "documents:restore",
  "documents.duplicate": "documents:duplicateWithMindmaps",
  "documents.copy_tree": "documents:copyTree",
  "documents.template": "documents:setTemplate",
  "documents.emptyTrashByWorkspace": "documents:emptyTrashByWorkspace",
  "documents.purge": "documents:purge",
  "documents.embed": "documents:updateContent",
  "blocks.patch": "documents:updateContent",
  "blocks.move": "documents:updateContent",
  "blocks.embed": "documents:updateContent",
  "documents.title.update": "documents:updateTitle",
  "documents.stats.update": "documents:updateStats",
  "documents.options.update": "documents:updateOptions",
  "documents.save": "documents:updateContent",
  "page.head.updateTitle": "documents:updateTitle",
  "page.layout.updateOptions": "documents:updateOptions",
  "page.body.save": "documents:updateContent",
  "mindmaps.delete": "mindmaps:softDelete",
  "mindmaps.restore": "mindmaps:restore",
  "mindmaps.purge": "mindmaps:purge",
  "mindmaps.emptyTrashByWorkspace": "mindmaps:emptyTrashByWorkspace",
  "media.assets.replace_storage": "mediaAssets:replaceStorageFromUpload",
} as const satisfies Record<string, string>;

function readHeaderValue(headerList: Headers, ...candidates: string[]): string | null {
  for (const candidate of candidates) {
    const value = headerList.get(candidate);
    if (typeof value === "string" && value.trim()) {
      return value.trim();
    }
  }
  return null;
}

function toBooleanFlag(raw: string | null): boolean {
  if (!raw) return false;
  return raw === "1" || raw.toLowerCase() === "true";
}

function makeFallbackId(prefix: string): string {
  return `${prefix}_${randomUUID()}`;
}

export async function buildDocumentBridgeContext(input: {
  request: Request;
  workspaceId?: string | null;
  idempotencyKey?: string | null;
  validateOnly?: boolean;
  dryRun?: boolean;
}): Promise<BridgeContext> {
  let auth;
  try {
    auth = await requireAuthContext();
  } catch (error) {
    if (error instanceof HttpError) {
      throw new DocumentBridgeError(error.message || "未登录", error.status, "UNAUTHORIZED");
    }
    throw new DocumentBridgeError("未登录", 401, "UNAUTHORIZED");
  }

  const headerList = input.request.headers;
  const requestId =
    readHeaderValue(headerList, "x-request-id", "x-mnote-request-id") ?? makeFallbackId("req");
  const traceId =
    readHeaderValue(headerList, "x-trace-id", "x-mnote-trace-id", "x-request-id") ?? makeFallbackId("trace");
  const sessionId = readHeaderValue(headerList, "x-session-id", "x-mnote-session-id");
  const sourceChannel = readHeaderValue(headerList, "x-source-channel") ?? "next-route";
  const sourceClient = readHeaderValue(headerList, "x-source-client", "user-agent") ?? "wolai-frontend";
  const deploymentId =
    readHeaderValue(headerList, "x-deployment-id") ?? process.env.VERCEL_DEPLOYMENT_ID ?? null;
  const projectId = readHeaderValue(headerList, "x-project-id") ?? process.env.VERCEL_PROJECT_ID ?? null;
  const tenantId = readHeaderValue(headerList, "x-tenant-id");
  const authToken = readHeaderValue(headerList, "authorization");
  const idempotencyKey =
    input.idempotencyKey ?? readHeaderValue(headerList, "idempotency-key", "x-idempotency-key");
  const validateOnly = input.validateOnly ?? toBooleanFlag(readHeaderValue(headerList, "x-validate-only"));
  const dryRun = input.dryRun ?? toBooleanFlag(readHeaderValue(headerList, "x-dry-run"));

  return {
    deploymentId,
    projectId,
    workspaceId: input.workspaceId ?? null,
    requestId,
    traceId,
    actor: {
      actorType: "user",
      actorId: auth.userId,
      sessionId,
    },
    source: {
      channel: sourceChannel,
      client: sourceClient,
    },
    tenantId,
    authToken,
    idempotencyKey,
    validateOnly,
    dryRun,
  };
}

export function buildDocumentBridgeContextWithActor(input: {
  request: Request;
  actor: BridgeActor;
  workspaceId?: string | null;
  idempotencyKey?: string | null;
  validateOnly?: boolean;
  dryRun?: boolean;
  source?: Partial<BridgeSource>;
  authToken?: string | null;
}): BridgeContext {
  const headerList = input.request.headers;
  const requestId =
    readHeaderValue(headerList, "x-request-id", "x-mnote-request-id") ?? makeFallbackId("req");
  const traceId =
    readHeaderValue(headerList, "x-trace-id", "x-mnote-trace-id", "x-request-id") ?? makeFallbackId("trace");
  const sourceChannel = input.source?.channel?.trim() || readHeaderValue(headerList, "x-source-channel") || "next-route";
  const sourceClient =
    input.source?.client?.trim() ||
    readHeaderValue(headerList, "x-source-client", "user-agent") ||
    "wolai-frontend";
  const deploymentId =
    readHeaderValue(headerList, "x-deployment-id") ?? process.env.VERCEL_DEPLOYMENT_ID ?? null;
  const projectId = readHeaderValue(headerList, "x-project-id") ?? process.env.VERCEL_PROJECT_ID ?? null;
  const tenantId = readHeaderValue(headerList, "x-tenant-id");
  const authToken = input.authToken ?? readHeaderValue(headerList, "authorization");
  const idempotencyKey =
    input.idempotencyKey ?? readHeaderValue(headerList, "idempotency-key", "x-idempotency-key");
  const validateOnly = input.validateOnly ?? toBooleanFlag(readHeaderValue(headerList, "x-validate-only"));
  const dryRun = input.dryRun ?? toBooleanFlag(readHeaderValue(headerList, "x-dry-run"));

  return {
    deploymentId,
    projectId,
    workspaceId: input.workspaceId ?? null,
    requestId,
    traceId,
    actor: input.actor,
    source: {
      channel: sourceChannel,
      client: sourceClient,
    },
    tenantId,
    authToken,
    idempotencyKey,
    validateOnly,
    dryRun,
  };
}

export function buildDocumentCommandEnvelope<T>(input: {
  name: string;
  payload: T;
  context: BridgeContext;
  target?: BridgeTarget | null;
  reason?: string | null;
  refs?: string[];
}): CommandEnvelope<T> {
  return {
    name: input.name,
    commandId: makeFallbackId("cmd"),
    idempotencyKey: input.context.idempotencyKey,
    actor: input.context.actor,
    source: input.context.source,
    target: input.target ?? null,
    payload: input.payload,
    reason: input.reason ?? null,
    refs: input.refs ?? [],
    dryRun: input.context.dryRun,
    validateOnly: input.context.validateOnly,
  };
}

export function buildDocumentQueryEnvelope<T>(input: { name: string; payload: T }): QueryEnvelope<T> {
  return {
    name: input.name,
    payload: input.payload,
  };
}

function getDocumentBridgeMutationFunctionName(commandName: string): string {
  const functionName =
    DOCUMENT_BRIDGE_MUTATION_FUNCTIONS[
      commandName as keyof typeof DOCUMENT_BRIDGE_MUTATION_FUNCTIONS
    ];
  if (!functionName) {
    throw new DocumentBridgeError(
      `未注册文档 bridge mutation: ${commandName}`,
      500,
      "TRANSPORT_ERROR",
    );
  }
  return functionName;
}

function buildDocumentCommandPayloadJson(input: {
  context: BridgeContext;
  commandName: string;
  workspaceId: string | null;
  idempotencyKey: string | null;
}): string {
  return JSON.stringify({
    kind: "command",
    name: input.commandName,
    request_id: input.context.requestId,
    trace_id: input.context.traceId,
    deployment_id: input.context.deploymentId,
    project_id: input.context.projectId,
    workspace_id: input.workspaceId,
    tenant_id: input.context.tenantId,
    idempotency_key: input.idempotencyKey,
    actor: {
      type: input.context.actor.actorType,
      id: input.context.actor.actorId,
      session_id: input.context.actor.sessionId,
    },
    source: {
      channel: input.context.source.channel,
      client: input.context.source.client,
    },
  });
}

function buildDocumentQueryPayloadJson(input: {
  context: BridgeContext;
  queryName: string;
  workspaceId: string | null;
}): string {
  return JSON.stringify({
    kind: "query",
    name: input.queryName,
    request_id: input.context.requestId,
    trace_id: input.context.traceId,
    deployment_id: input.context.deploymentId,
    project_id: input.context.projectId,
    workspace_id: input.workspaceId,
    tenant_id: input.context.tenantId,
    actor_id: input.context.actor.actorId,
    source: {
      channel: input.context.source.channel,
      client: input.context.source.client,
    },
  });
}

export function buildDocumentBridgeMutationRequest<
  TPayload,
  TArgs extends Record<string, unknown>,
>(input: {
  context: BridgeContext;
  envelope: CommandEnvelope<TPayload>;
  mapConvexArgs: (payload: TPayload) => TArgs;
}): DocumentBridgeMutationRequest<TArgs> {
  const workspaceId = input.envelope.target?.workspaceId ?? input.context.workspaceId ?? null;
  const idempotencyKey = input.envelope.idempotencyKey ?? input.context.idempotencyKey;

  return {
    functionName: getDocumentBridgeMutationFunctionName(input.envelope.name),
    deploymentId: input.context.deploymentId,
    projectId: input.context.projectId,
    workspaceId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    idempotencyKey,
    actorId: input.context.actor.actorId,
    payloadJson: buildDocumentCommandPayloadJson({
      context: input.context,
      commandName: input.envelope.name,
      workspaceId,
      idempotencyKey,
    }),
    args: input.mapConvexArgs(input.envelope.payload),
  };
}

function getDocumentBridgeQueryFunctionName(queryName: string): string {
  const functionName =
    DOCUMENT_BRIDGE_QUERY_FUNCTIONS[
      queryName as keyof typeof DOCUMENT_BRIDGE_QUERY_FUNCTIONS
    ];
  if (!functionName) {
    throw new DocumentBridgeError(
      `未注册文档 bridge query: ${queryName}`,
      500,
      "TRANSPORT_ERROR",
    );
  }
  return functionName;
}

export function buildDocumentBridgeQueryRequest<
  TPayload,
  TArgs extends Record<string, unknown>,
>(input: {
  context: BridgeContext;
  envelope: QueryEnvelope<TPayload>;
  mapConvexArgs: (payload: TPayload) => TArgs;
}): DocumentBridgeQueryRequest<TArgs> {
  const workspaceId = input.context.workspaceId ?? null;
  return {
    functionName: getDocumentBridgeQueryFunctionName(input.envelope.name),
    deploymentId: input.context.deploymentId,
    projectId: input.context.projectId,
    workspaceId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    actorId: input.context.actor.actorId,
    payloadJson: buildDocumentQueryPayloadJson({
      context: input.context,
      queryName: input.envelope.name,
      workspaceId,
    }),
    args: input.mapConvexArgs(input.envelope.payload),
  };
}

export async function executeDocumentBridgeMutationRequest<
  TArgs extends Record<string, unknown>,
  TResult,
>(input: {
  client: ConvexHttpClient;
  mutation: unknown;
  request: DocumentBridgeMutationRequest<TArgs>;
}): Promise<TResult> {
  const mutate = input.client.mutation.bind(input.client) as (
    mutation: unknown,
    args: TArgs,
  ) => Promise<TResult>;
  return mutate(input.mutation, input.request.args);
}

export function assertDocumentId(documentId: string | null | undefined): string {
  const normalized = typeof documentId === "string" ? documentId.trim() : "";
  if (!normalized) {
    throw new DocumentBridgeError("缺少 documentId", 400, "VALIDATION_ERROR", [
      { field: "documentId", reason: "required" },
    ]);
  }
  return normalized;
}

export function assertTitle(title: string | null | undefined): string {
  if (typeof title !== "string") {
    throw new DocumentBridgeError("缺少 title", 400, "VALIDATION_ERROR", [
      { field: "title", reason: "required" },
    ]);
  }
  const normalized = title.trim() || "无标题";
  return normalized;
}

export function assertBlockId(blockId: string | null | undefined): string {
  const normalized = typeof blockId === "string" ? blockId.trim() : "";
  if (!normalized) {
    throw new DocumentBridgeError("缺少 blockId", 400, "VALIDATION_ERROR", [
      { field: "blockId", reason: "required" },
    ]);
  }
  return normalized;
}

export function assertNextBlock(nextBlock: unknown): asserts nextBlock is Record<string, unknown> {
  if (!nextBlock || typeof nextBlock !== "object" || Array.isArray(nextBlock)) {
    throw new DocumentBridgeError("缺少 nextBlock", 400, "VALIDATION_ERROR", [
      { field: "nextBlock", reason: "required object" },
    ]);
  }
}

export function assertStats(stats: unknown): asserts stats is {
  wordCount: number;
  characterCount: number;
  blockCount: number;
  todoTotal: number;
  todoDone: number;
} {
  if (!stats || typeof stats !== "object") {
    throw new DocumentBridgeError("缺少 stats", 400, "VALIDATION_ERROR", [
      { field: "stats", reason: "required" },
    ]);
  }
  const record = stats as Record<string, unknown>;
  const fields = ["wordCount", "characterCount", "blockCount", "todoTotal", "todoDone"] as const;
  for (const field of fields) {
    if (typeof record[field] !== "number" || !Number.isFinite(record[field] as number)) {
      throw new DocumentBridgeError("stats 字段非法", 400, "VALIDATION_ERROR", [
        { field, reason: "must be finite number" },
      ]);
    }
  }
}

const PAGE_FONT_VALUES = new Set<PageOptionsState["pageFont"]>(["default", "song", "kai"]);
const PAGE_LAYOUT_DENSITY_VALUES = new Set<PageOptionsState["layoutDensity"]>(["compact", "normal", "spacious"]);
const PAGE_OPTION_BOOLEAN_FIELDS = [
  "wideLayout",
  "smallText",
  "showHeadingNumbers",
  "showToc",
  "showStructure",
  "protectEditing",
  "showWordCount",
  "collapseBacklinks",
  "hideChildPages",
  "showBlockRefCount",
] as const satisfies readonly (keyof PageOptionsState)[];
const PAGE_OPTION_ALLOWED_FIELDS = new Set<keyof PageOptionsState>([
  ...PAGE_OPTION_BOOLEAN_FIELDS,
  "pageFont",
  "layoutDensity",
  "embedDefaultBlockId",
]);

export function assertOptionsPatch(
  options: unknown,
): asserts options is Partial<Pick<PageOptionsState, keyof PageOptionsState>> {
  if (!options || typeof options !== "object" || Array.isArray(options)) {
    throw new DocumentBridgeError("缺少 options", 400, "VALIDATION_ERROR", [
      { field: "options", reason: "required object" },
    ]);
  }

  const record = options as Record<string, unknown>;
  const entries = Object.entries(record);
  if (entries.length === 0) {
    throw new DocumentBridgeError("缺少 options", 400, "VALIDATION_ERROR", [
      { field: "options", reason: "must not be empty" },
    ]);
  }

  for (const [field, value] of entries) {
    if (!PAGE_OPTION_ALLOWED_FIELDS.has(field as keyof PageOptionsState)) {
      throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
        { field, reason: "unexpected field" },
      ]);
    }

    if ((PAGE_OPTION_BOOLEAN_FIELDS as readonly string[]).includes(field)) {
      if (typeof value !== "boolean") {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be boolean" },
        ]);
      }
      continue;
    }

    if (field === "pageFont") {
      if (typeof value !== "string" || !PAGE_FONT_VALUES.has(value as PageOptionsState["pageFont"])) {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be one of default/song/kai" },
        ]);
      }
      continue;
    }

    if (field === "layoutDensity") {
      if (
        typeof value !== "string" ||
        !PAGE_LAYOUT_DENSITY_VALUES.has(value as PageOptionsState["layoutDensity"])
      ) {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be one of compact/normal/spacious" },
        ]);
      }
      continue;
    }

    if (field === "embedDefaultBlockId" && value !== null && typeof value !== "string") {
      throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
        { field, reason: "must be string or null" },
      ]);
    }
  }
}

export function documentBridgeErrorResponse(error: unknown) {
  if (error instanceof DocumentBridgeError) {
    return apiErrorResponse(error.message, error.status, {
      code: error.code,
      details: error.details,
    });
  }
  if (error instanceof HttpError) {
    return apiErrorResponse(error.message || "未登录", error.status);
  }
  return apiErrorResponse(error instanceof Error ? error.message : "服务器错误", 500);
}
