import { spawn } from "node:child_process";
import { constants as fsConstants } from "node:fs";
import { access, readdir, stat } from "node:fs/promises";
import path from "node:path";
import type { ConvexHttpClient } from "convex/browser";
import { api } from "@/lib/convex/api";
import {
  DocumentBridgeError,
  type BridgeTarget,
  type BridgeContext,
  type CommandEnvelope,
  type QueryEnvelope,
} from "@/lib/documents/bridge";
import { shouldUseBuiltBridgeRuntimeBinary } from "@/lib/documents/rust-runtime-selection";

export type RustRuntimeExecutedQuery<TResult = unknown> = {
  ok: true;
  result: TResult;
};

type RustRuntimeErrorKind =
  | "validation"
  | "unauthorized"
  | "conflict"
  | "not_found"
  | "transport"
  | "rejected";

type RustRuntimeErrorPayload = {
  kind: RustRuntimeErrorKind | string;
  message: string;
};

type RustRuntimeResponse =
  | {
      ok: true;
      plan: RustBridgeQueryPlan | RustBridgeCommandPlan | RustBridgeToolPlan | RustBridgeBuiltinToolPlan;
    }
  | RustRuntimeExecutedQuery
  | {
      ok: false;
      error: RustRuntimeErrorPayload;
    };

export type RustBridgeQueryPlan = {
  kind: "query";
  queryName: string;
  functionName: string;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  actorId: string;
  payloadJson: string;
  argsJson: Record<string, unknown>;
};

export type RustBridgeCommandPlan = {
  kind: "command";
  commandName: string;
  commandId: string;
  functionName: string;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  actorId: string;
  idempotencyKey: string | null;
  payloadJson: string;
  argsJson: Record<string, unknown>;
};

export type RustBridgeToolPlanStep = {
  kind: string;
  name: string;
  functionName: string | null;
  description: string;
  argsJson: Record<string, unknown>;
};

export type RustBridgeToolPlan = {
  kind: "tool";
  toolName: string;
  invocationKind: string;
  executionMode: string;
  effect: string;
  toolsetId: string;
  requiresConfirmation: boolean;
  requestId: string;
  traceId: string;
  actorId: string;
  validateOnly: boolean;
  dryRun: boolean;
  payloadJson: string;
  argsJson: Record<string, unknown>;
  target: Record<string, unknown> | null;
  steps: RustBridgeToolPlanStep[];
};

export type RustBridgeBuiltinToolPlan = {
  kind: "builtin_tool";
  toolName: string;
  toolsetId: string;
  status: "rust" | "mixed" | "ts" | "transport";
  note: string;
};

export type RustBridgeToolResult<TResult = unknown> = {
  plan: RustBridgeToolPlan;
  result: TResult;
};

type RuntimeProcessResult = {
  code: number | null;
  signal: NodeJS.Signals | null;
  stdout: string;
  stderr: string;
};

type RuntimeInvocation = {
  command: string;
  args: string[];
};

const RUST_RUNTIME_TIMEOUT_MS = 30_000;

async function pathExists(targetPath: string) {
  try {
    await access(targetPath, fsConstants.F_OK);
    return true;
  } catch {
    return false;
  }
}

async function resolveRepoRoot() {
  const candidates = [process.cwd(), path.resolve(process.cwd(), "..")];
  for (const candidate of candidates) {
    if (await pathExists(path.join(candidate, "rust", "Cargo.toml"))) {
      return candidate;
    }
  }
  throw new DocumentBridgeError("未找到 mnote 仓库根目录", 500, "TRANSPORT_ERROR");
}

async function readLatestMtimeMs(targetPath: string): Promise<number | null> {
  try {
    const stats = await stat(targetPath);
    if (stats.isFile()) {
      return stats.mtimeMs;
    }

    if (!stats.isDirectory()) {
      return null;
    }

    const entries = await readdir(targetPath, { withFileTypes: true });
    const nestedTimes = await Promise.all(
      entries
        .filter((entry) => !entry.name.startsWith("."))
        .map((entry) => readLatestMtimeMs(path.join(targetPath, entry.name))),
    );
    const latestChild = nestedTimes.reduce<number | null>(
      (current, next) => {
        if (typeof next !== "number" || !Number.isFinite(next)) {
          return current;
        }
        if (typeof current !== "number" || !Number.isFinite(current)) {
          return next;
        }
        return Math.max(current, next);
      },
      null,
    );
    return latestChild == null ? stats.mtimeMs : Math.max(stats.mtimeMs, latestChild);
  } catch {
    return null;
  }
}

async function readRuntimeSourceLatestMtimeMs(repoRoot: string) {
  const sourceRoots = [
    path.join(repoRoot, "rust", "Cargo.toml"),
    path.join(repoRoot, "rust", "Cargo.lock"),
    path.join(repoRoot, "rust", "crates", "bridge-runtime", "Cargo.toml"),
    path.join(repoRoot, "rust", "crates", "bridge-runtime", "src"),
    path.join(repoRoot, "rust", "crates", "storage-convex-bridge", "Cargo.toml"),
    path.join(repoRoot, "rust", "crates", "storage-convex-bridge", "src"),
    path.join(repoRoot, "rust", "crates", "core-protocol", "Cargo.toml"),
    path.join(repoRoot, "rust", "crates", "core-protocol", "src"),
  ];

  const mtimes = await Promise.all(sourceRoots.map((targetPath) => readLatestMtimeMs(targetPath)));
  return mtimes.reduce<number | null>(
    (current, next) => {
      if (typeof next !== "number" || !Number.isFinite(next)) {
        return current;
      }
      if (typeof current !== "number" || !Number.isFinite(current)) {
        return next;
      }
      return Math.max(current, next);
    },
    null,
  );
}

async function resolveRuntimeInvocation(): Promise<RuntimeInvocation> {
  const explicitBin = process.env.MNOTE_RUST_BRIDGE_BIN?.trim();
  if (explicitBin) {
    return {
      command: explicitBin,
      args: [],
    };
  }

  const repoRoot = await resolveRepoRoot();
  const builtBinary = path.join(repoRoot, "rust", "target", "debug", "bridge-runtime");
  if (await pathExists(builtBinary)) {
    const [builtBinaryMtimeMs, latestSourceMtimeMs] = await Promise.all([
      readLatestMtimeMs(builtBinary),
      readRuntimeSourceLatestMtimeMs(repoRoot),
    ]);

    if (
      shouldUseBuiltBridgeRuntimeBinary({
        builtBinaryMtimeMs,
        latestSourceMtimeMs,
      })
    ) {
      return {
        command: builtBinary,
        args: [],
      };
    }
  }

  return {
    command: "cargo",
    args: [
      "run",
      "--quiet",
      "--manifest-path",
      path.join(repoRoot, "rust", "Cargo.toml"),
      "-p",
      "bridge-runtime",
      "--",
    ],
  };
}

async function runRustRuntime(input: Record<string, unknown>): Promise<RustRuntimeResponse> {
  const invocation = await resolveRuntimeInvocation();
  const result = await new Promise<RuntimeProcessResult>((resolve, reject) => {
    const child = spawn(invocation.command, invocation.args, {
      stdio: ["pipe", "pipe", "pipe"],
      env: {
        ...process.env,
        CARGO_TERM_COLOR: "never",
      },
    });

    let stdout = "";
    let stderr = "";
    let timedOut = false;
    const timeout = setTimeout(() => {
      timedOut = true;
      child.kill("SIGKILL");
    }, RUST_RUNTIME_TIMEOUT_MS);

    child.stdout.setEncoding("utf8");
    child.stderr.setEncoding("utf8");
    child.stdout.on("data", (chunk: string) => {
      stdout += chunk;
    });
    child.stderr.on("data", (chunk: string) => {
      stderr += chunk;
    });
    child.on("error", (error) => {
      clearTimeout(timeout);
      reject(error);
    });
    child.on("close", (code, signal) => {
      clearTimeout(timeout);
      if (timedOut) {
        reject(new DocumentBridgeError("Rust runtime 执行超时", 504, "TRANSPORT_ERROR"));
        return;
      }
      resolve({
        code,
        signal,
        stdout,
        stderr,
      });
    });

    child.stdin.end(JSON.stringify(input));
  }).catch((error: unknown) => {
    if (error instanceof DocumentBridgeError) {
      throw error;
    }
    throw new DocumentBridgeError(
      error instanceof Error ? `Rust runtime 启动失败: ${error.message}` : "Rust runtime 启动失败",
      500,
      "TRANSPORT_ERROR",
    );
  });

  const raw = result.stdout.trim();
  if (!raw) {
    throw new DocumentBridgeError(
      result.stderr.trim() || "Rust runtime 未返回任何结果",
      500,
      "TRANSPORT_ERROR",
    );
  }

  let parsed: RustRuntimeResponse;
  try {
    parsed = JSON.parse(raw) as RustRuntimeResponse;
  } catch (error) {
    throw new DocumentBridgeError(
      `Rust runtime 返回了非法 JSON: ${error instanceof Error ? error.message : "unknown"}`,
      500,
      "TRANSPORT_ERROR",
      {
        stdout: raw,
        stderr: result.stderr.trim() || null,
      },
    );
  }

  if (result.code !== 0 || !parsed.ok) {
    if (!parsed.ok) {
      throw toDocumentBridgeError("error" in parsed ? parsed.error : { kind: "transport", message: "Rust runtime 执行失败" });
    }
    throw new DocumentBridgeError(
      result.stderr.trim() ||
        `Rust runtime 执行失败(code=${String(result.code)}, signal=${String(result.signal)})`,
      500,
      "TRANSPORT_ERROR",
    );
  }

  return parsed;
}

function toDocumentBridgeError(error: RustRuntimeErrorPayload) {
  switch (error.kind) {
    case "validation":
      return new DocumentBridgeError(error.message, 400, "VALIDATION_ERROR");
    case "unauthorized":
      return new DocumentBridgeError(error.message, 401, "UNAUTHORIZED");
    case "not_found":
      return new DocumentBridgeError(error.message, 404, "NOT_FOUND");
    case "conflict":
      return new DocumentBridgeError(error.message, 409, "REJECTED");
    case "rejected":
      return new DocumentBridgeError(error.message, 409, "REJECTED");
    case "transport":
    default:
      return new DocumentBridgeError(error.message, 500, "TRANSPORT_ERROR");
  }
}

function assertObjectArgs(argsJson: Record<string, unknown>) {
  if (!argsJson || typeof argsJson !== "object" || Array.isArray(argsJson)) {
    throw new DocumentBridgeError("Rust runtime 返回了非法 transport args", 500, "TRANSPORT_ERROR");
  }
  return argsJson;
}

function assertToolPlan(plan: RustBridgeQueryPlan | RustBridgeCommandPlan | RustBridgeToolPlan): RustBridgeToolPlan {
  if (plan.kind !== "tool") {
    throw new DocumentBridgeError("Rust runtime 返回了错误的 tool plan", 500, "TRANSPORT_ERROR");
  }
  return {
    ...plan,
    argsJson: assertObjectArgs(plan.argsJson),
    target:
      plan.target && typeof plan.target === "object" && !Array.isArray(plan.target)
        ? (plan.target as Record<string, unknown>)
        : null,
    steps: Array.isArray(plan.steps)
      ? plan.steps.map((step) => ({
          kind: String(step.kind ?? ""),
          name: String(step.name ?? ""),
          functionName: typeof step.functionName === "string" ? step.functionName : null,
          description: String(step.description ?? ""),
          argsJson:
            step.argsJson && typeof step.argsJson === "object" && !Array.isArray(step.argsJson)
              ? (step.argsJson as Record<string, unknown>)
              : {},
        }))
      : [],
  };
}

function assertStringArg(argsJson: Record<string, unknown>, field: string) {
  const value = argsJson[field];
  if (typeof value !== "string" || !value.trim()) {
    throw new DocumentBridgeError(`Rust runtime 缺少 ${field}`, 500, "TRANSPORT_ERROR");
  }
  return value;
}

function readOptionalIntegerArg(argsJson: Record<string, unknown>, field: string) {
  const value = argsJson[field];
  if (value === null || typeof value === "undefined") {
    return null;
  }
  if (typeof value === "number" && Number.isInteger(value)) {
    return value;
  }
  throw new DocumentBridgeError(`Rust runtime 的 ${field} 非法`, 500, "TRANSPORT_ERROR");
}

function readOptionalStringArg(argsJson: Record<string, unknown>, field: string) {
  const value = argsJson[field];
  if (value === null || typeof value === "undefined") {
    return null;
  }
  if (typeof value === "string") {
    const trimmed = value.trim();
    return trimmed || null;
  }
  throw new DocumentBridgeError(`Rust runtime 的 ${field} 非法`, 500, "TRANSPORT_ERROR");
}

function readRequiredNumberArg(argsJson: Record<string, unknown>, field: string) {
  const value = argsJson[field];
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }
  throw new DocumentBridgeError(`Rust runtime 缺少 ${field}`, 500, "TRANSPORT_ERROR");
}

export async function resolveRustBridgeQueryPlan<TPayload>(input: {
  context: BridgeContext;
  envelope: QueryEnvelope<TPayload>;
}): Promise<RustBridgeQueryPlan> {
  const response = await runRustRuntime({
    kind: "query",
    context: input.context,
    query: input.envelope,
  });

  if (!("plan" in response) || response.plan.kind !== "query") {
    throw new DocumentBridgeError("Rust runtime 返回了错误的 query plan", 500, "TRANSPORT_ERROR");
  }

  return {
    ...response.plan,
    argsJson: assertObjectArgs(response.plan.argsJson),
  };
}

export async function executeRustBridgeQuery<TResult>(input: {
  context: BridgeContext;
  envelope: QueryEnvelope<unknown>;
  data?: Record<string, unknown>;
}): Promise<TResult> {
  const response = await runRustRuntime({
    kind: "query",
    context: input.context,
    query: input.envelope,
    data: input.data ?? {},
  });

  if (!("result" in response)) {
    throw new DocumentBridgeError("Rust runtime 未返回 query result", 500, "TRANSPORT_ERROR");
  }

  return response.result as TResult;
}

export async function resolveRustBridgeCommandPlan<TPayload>(input: {
  context: BridgeContext;
  envelope: CommandEnvelope<TPayload>;
}): Promise<RustBridgeCommandPlan> {
  const response = await runRustRuntime({
    kind: "command",
    context: input.context,
    command: input.envelope,
  });

  if (!("plan" in response) || response.plan.kind !== "command") {
    throw new DocumentBridgeError("Rust runtime 返回了错误的 command plan", 500, "TRANSPORT_ERROR");
  }

  return {
    ...response.plan,
    argsJson: assertObjectArgs(response.plan.argsJson),
  };
}

export async function resolveRustBridgeToolPlan(input: {
  context: BridgeContext;
  toolName: string;
  invocationKind: "command" | "query" | "job";
  args: Record<string, unknown>;
  target?: BridgeTarget | null;
  reason?: string | null;
  refs?: string[];
  mode?: "plan" | "result" | "explain-plan";
}): Promise<RustBridgeToolPlan> {
  const response = await runRustRuntime({
    kind: "tool",
    context: input.context,
    tool: {
      tool: input.toolName,
      kind: input.invocationKind,
      mode: input.mode ?? "plan",
      argsJson: input.args,
      target: input.target ?? null,
      reason: input.reason ?? null,
      refs: input.refs ?? [],
    },
  });

  if (!("plan" in response)) {
    throw new DocumentBridgeError("Rust runtime 未返回 tool plan", 500, "TRANSPORT_ERROR");
  }

  if (response.plan.kind !== "tool") {
    throw new DocumentBridgeError("Rust runtime 返回了错误的 tool plan", 500, "TRANSPORT_ERROR");
  }
  return assertToolPlan(response.plan);
}

export async function executeRustBridgeTool<TResult>(input: {
  context: BridgeContext;
  toolName: string;
  invocationKind: "command" | "query" | "job";
  args: Record<string, unknown>;
  data: Record<string, unknown>;
  target?: BridgeTarget | null;
  reason?: string | null;
  refs?: string[];
  mode?: "result" | "explain-plan";
}): Promise<RustBridgeToolResult<TResult>> {
  const response = await runRustRuntime({
    kind: "tool",
    context: input.context,
    tool: {
      tool: input.toolName,
      kind: input.invocationKind,
      mode: input.mode ?? "result",
      argsJson: input.args,
      target: input.target ?? null,
      reason: input.reason ?? null,
      refs: input.refs ?? [],
    },
    data: input.data,
  });

  if (!("result" in response)) {
    throw new DocumentBridgeError("Rust runtime 未返回 tool result", 500, "TRANSPORT_ERROR");
  }

  const plan = await resolveRustBridgeToolPlan({
    context: input.context,
    toolName: input.toolName,
    invocationKind: input.invocationKind,
    args: input.args,
    target: input.target,
    reason: input.reason,
    refs: input.refs,
    mode: input.mode === "explain-plan" ? "explain-plan" : "plan",
  });

  return {
    plan,
    result: response.result as TResult,
  };
}

export async function executeRustBridgeQueryTransport<TResult>(input: {
  client: ConvexHttpClient;
  plan: RustBridgeQueryPlan;
}): Promise<TResult> {
  const bridgeLogsApi = api as any;
  const query = input.client.query.bind(input.client) as (
    queryReference: unknown,
    args: Record<string, unknown>,
  ) => Promise<TResult>;

  switch (input.plan.functionName) {
    case "documents:getContent":
      return query(api.documents.getContent, {
        id: assertStringArg(input.plan.argsJson, "id"),
      });
    case "mindmaps:get":
      return query(api.mindmaps.get, {
        docId: assertStringArg(input.plan.argsJson, "docId"),
        mindmapId: assertStringArg(input.plan.argsJson, "mindmapId"),
      });
    case "sidebar:datasetList":
      return query(api.sidebar.datasetList, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
      });
    case "blocks:getById":
      return query(api.blocks.getById, {
        userId: assertStringArg(input.plan.argsJson, "userId"),
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
        blockId: assertStringArg(input.plan.argsJson, "blockId"),
      });
    case "bridgeLogs:listByRequest":
      return query(bridgeLogsApi.bridgeLogs.listByRequest, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
        requestId: assertStringArg(input.plan.argsJson, "requestId"),
      });
    case "bridgeLogs:listByTrace":
      return query(bridgeLogsApi.bridgeLogs.listByTrace, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
        traceId: assertStringArg(input.plan.argsJson, "traceId"),
      });
    case "bridgeLogs:listByCommand":
      return query(bridgeLogsApi.bridgeLogs.listByCommand, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
        commandId: assertStringArg(input.plan.argsJson, "commandId"),
      });
    case "bridgeLogs:listWorkspaceOverview":
      return query(bridgeLogsApi.bridgeLogs.listWorkspaceOverview, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
        limit:
          typeof input.plan.argsJson.limit === "number" && Number.isFinite(input.plan.argsJson.limit)
            ? input.plan.argsJson.limit
            : undefined,
        cursor: readOptionalStringArg(input.plan.argsJson, "cursor"),
        commandStatus: readOptionalStringArg(input.plan.argsJson, "commandStatus"),
        eventStatus: readOptionalStringArg(input.plan.argsJson, "eventStatus"),
        targetPageId: readOptionalStringArg(input.plan.argsJson, "targetPageId"),
        targetBlockId: readOptionalStringArg(input.plan.argsJson, "targetBlockId"),
        aggregateType: readOptionalStringArg(input.plan.argsJson, "aggregateType"),
        aggregateId: readOptionalStringArg(input.plan.argsJson, "aggregateId"),
      });
    default:
      throw new DocumentBridgeError(
        `未注册的 Rust query transport: ${input.plan.functionName}`,
        500,
        "TRANSPORT_ERROR",
      );
  }
}

export async function executeRustBridgeMutationTransport<TResult>(input: {
  client: ConvexHttpClient;
  plan: RustBridgeCommandPlan;
}): Promise<TResult> {
  const mutation = input.client.mutation.bind(input.client) as (
    mutationReference: unknown,
    args: Record<string, unknown>,
  ) => Promise<TResult>;

  switch (input.plan.functionName) {
    case "documents:createWithParentReference":
      return mutation(api.documents.create, {
        id: assertStringArg(input.plan.argsJson, "id"),
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
        parentId: readOptionalStringArg(input.plan.argsJson, "parentId"),
        title: assertStringArg(input.plan.argsJson, "title"),
        accessScope: assertStringArg(input.plan.argsJson, "accessScope"),
        content: input.plan.argsJson.content,
      });
    case "documents:move":
      return mutation(api.documents.move, {
        id: assertStringArg(input.plan.argsJson, "id"),
        parentId: readOptionalStringArg(input.plan.argsJson, "parentId"),
        sortOrder: readRequiredNumberArg(input.plan.argsJson, "sortOrder"),
      });
    case "documents:softDelete":
      return mutation(api.documents.softDelete, {
        id: assertStringArg(input.plan.argsJson, "id"),
      });
    case "documents:restore":
      return mutation(api.documents.restore, {
        id: assertStringArg(input.plan.argsJson, "id"),
      });
    case "documents:duplicateWithMindmaps":
      return mutation(api.documents.duplicate, {
        sourceId: assertStringArg(input.plan.argsJson, "sourceId"),
        newId: assertStringArg(input.plan.argsJson, "newId"),
        title: readOptionalStringArg(input.plan.argsJson, "title"),
      });
    case "documents:setTemplate":
      return mutation(api.documents.setTemplate, {
        id: assertStringArg(input.plan.argsJson, "id"),
        isTemplate: Boolean(input.plan.argsJson.isTemplate),
      });
    case "documents:emptyTrashByWorkspace":
      return mutation(api.documents.emptyTrashByWorkspace, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
      });
    case "documents:purge":
      return mutation(api.documents.purge, {
        id: assertStringArg(input.plan.argsJson, "id"),
      });
    case "documents:updateTitle":
      return mutation(api.documents.updateTitle, {
        id: assertStringArg(input.plan.argsJson, "id"),
        title: assertStringArg(input.plan.argsJson, "title"),
      });
    case "documents:updateContent":
      return mutation(api.documents.updateContent, {
        id: assertStringArg(input.plan.argsJson, "id"),
        content: input.plan.argsJson.content,
        expectedRevision: readOptionalIntegerArg(input.plan.argsJson, "expectedRevision"),
        conflictDetectionKey: readOptionalStringArg(input.plan.argsJson, "conflictDetectionKey"),
      });
    case "mindmaps:put":
      return mutation(api.mindmaps.put, {
        docId: assertStringArg(input.plan.argsJson, "docId"),
        mindmapId: assertStringArg(input.plan.argsJson, "mindmapId"),
        data: input.plan.argsJson.data,
        createOnly:
          typeof input.plan.argsJson.createOnly === "boolean"
            ? input.plan.argsJson.createOnly
            : undefined,
      });
    case "mindmaps:softDelete":
      return mutation(api.mindmaps.softDelete, {
        docId: assertStringArg(input.plan.argsJson, "docId"),
        mindmapId: assertStringArg(input.plan.argsJson, "mindmapId"),
      });
    case "mindmaps:restore":
      return mutation(api.mindmaps.restore, {
        docId: assertStringArg(input.plan.argsJson, "docId"),
        mindmapId: assertStringArg(input.plan.argsJson, "mindmapId"),
      });
    case "mindmaps:purge":
      return mutation(api.mindmaps.purge, {
        docId: assertStringArg(input.plan.argsJson, "docId"),
        mindmapId: assertStringArg(input.plan.argsJson, "mindmapId"),
      });
    case "mindmaps:emptyTrashByWorkspace":
      return mutation(api.mindmaps.emptyTrashByWorkspace, {
        workspaceId: assertStringArg(input.plan.argsJson, "workspaceId"),
      });
    default:
      throw new DocumentBridgeError(
        `未注册的 Rust mutation transport: ${input.plan.functionName}`,
        500,
        "TRANSPORT_ERROR",
      );
  }
}
