import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
import { writeFileSync, unlinkSync } from "node:fs";
import { tmpdir } from "node:os";
import { resolve } from "node:path";

let getToolName: (platform: string, bareTool: string) => string;
let createToolNamer: (platform: string) => (bareTool: string) => string;
let KNOWN_PLATFORMS: string[];
let routePreToolUse: (
  toolName: string,
  toolInput: Record<string, unknown>,
  projectDir?: string,
  platform?: string,
) => {
  action: string;
  reason?: string;
  updatedInput?: Record<string, unknown>;
  additionalContext?: string;
} | null;
let resetGuidanceThrottle: () => void;
let createRoutingBlock: (t: (tool: string) => string) => string;
let createReadGuidance: (t: (tool: string) => string) => string;
let createGrepGuidance: (t: (tool: string) => string) => string;
let createBashGuidance: (t: (tool: string) => string) => string;
let createExternalMcpGuidance: (t: (tool: string) => string) => string;
let ROUTING_BLOCK: string;
let READ_GUIDANCE: string;
let GREP_GUIDANCE: string;
let BASH_GUIDANCE: string;
let EXTERNAL_MCP_GUIDANCE: string;

beforeAll(async () => {
  const naming = await import("../../hooks/core/tool-naming.mjs");
  getToolName = naming.getToolName;
  createToolNamer = naming.createToolNamer;
  KNOWN_PLATFORMS = naming.KNOWN_PLATFORMS;

  const routing = await import("../../hooks/core/routing.mjs");
  routePreToolUse = routing.routePreToolUse;
  resetGuidanceThrottle = routing.resetGuidanceThrottle;

  const block = await import("../../hooks/routing-block.mjs");
  createRoutingBlock = block.createRoutingBlock;
  createReadGuidance = block.createReadGuidance;
  createGrepGuidance = block.createGrepGuidance;
  createBashGuidance = block.createBashGuidance;
  createExternalMcpGuidance = block.createExternalMcpGuidance;
  ROUTING_BLOCK = block.ROUTING_BLOCK;
  READ_GUIDANCE = block.READ_GUIDANCE;
  GREP_GUIDANCE = block.GREP_GUIDANCE;
  BASH_GUIDANCE = block.BASH_GUIDANCE;
  EXTERNAL_MCP_GUIDANCE = block.EXTERNAL_MCP_GUIDANCE;
});

// MCP readiness sentinel — routing.mjs checks process.ppid in-process
const _sentinelDir = process.platform === "win32" ? tmpdir() : "/tmp";
const mcpSentinel = resolve(_sentinelDir, `context-mode-mcp-ready-${process.pid}`);

beforeEach(() => {
  if (typeof resetGuidanceThrottle === "function") resetGuidanceThrottle();
  writeFileSync(mcpSentinel, String(process.pid));
});

afterEach(() => {
  try { unlinkSync(mcpSentinel); } catch {}
});

// ═══════════════════════════════════════════════════════════════════
// Tool Naming — getToolName and createToolNamer
// ═══════════════════════════════════════════════════════════════════

describe("getToolName", () => {
  it("returns correct name for claude-code", () => {
    expect(getToolName("claude-code", "ctx_fetch_and_index")).toBe(
      "mcp__plugin_context-mode_context-mode__ctx_fetch_and_index",
    );
  });

  it("returns correct name for gemini-cli", () => {
    expect(getToolName("gemini-cli", "ctx_fetch_and_index")).toBe(
      "mcp__context-mode__ctx_fetch_and_index",
    );
  });

  it("returns correct name for antigravity", () => {
    expect(getToolName("antigravity", "ctx_execute")).toBe(
      "mcp__context-mode__ctx_execute",
    );
  });

  it("returns correct name for opencode", () => {
    expect(getToolName("opencode", "ctx_search")).toBe(
      "context-mode_ctx_search",
    );
  });

  it("returns correct name for vscode-copilot", () => {
    expect(getToolName("vscode-copilot", "ctx_batch_execute")).toBe(
      "context-mode_ctx_batch_execute",
    );
  });

  it("returns correct name for kiro", () => {
    expect(getToolName("kiro", "ctx_execute_file")).toBe(
      "@context-mode/ctx_execute_file",
    );
  });

  it("returns correct name for zed", () => {
    expect(getToolName("zed", "ctx_index")).toBe(
      "mcp:context-mode:ctx_index",
    );
  });

  it("returns bare name for cursor", () => {
    expect(getToolName("cursor", "ctx_fetch_and_index")).toBe(
      "ctx_fetch_and_index",
    );
  });

  it("returns bare name for codex", () => {
    expect(getToolName("codex", "ctx_execute")).toBe("ctx_execute");
  });

  it("returns bare name for openclaw", () => {
    expect(getToolName("openclaw", "ctx_search")).toBe("ctx_search");
  });

  it("returns bare name for pi", () => {
    expect(getToolName("pi", "ctx_batch_execute")).toBe("ctx_batch_execute");
  });

  it("falls back to claude-code for unknown platforms", () => {
    expect(getToolName("unknown-platform", "ctx_search")).toBe(
      "mcp__plugin_context-mode_context-mode__ctx_search",
    );
  });
});

describe("createToolNamer", () => {
  it("returns a function that produces correct names", () => {
    const t = createToolNamer("gemini-cli");
    expect(t("ctx_execute")).toBe("mcp__context-mode__ctx_execute");
    expect(t("ctx_search")).toBe("mcp__context-mode__ctx_search");
  });
});

describe("KNOWN_PLATFORMS", () => {
  it("contains all platforms", () => {
    expect(KNOWN_PLATFORMS).toContain("claude-code");
    expect(KNOWN_PLATFORMS).toContain("gemini-cli");
    expect(KNOWN_PLATFORMS).toContain("antigravity");
    expect(KNOWN_PLATFORMS).toContain("opencode");
    expect(KNOWN_PLATFORMS).toContain("kilo");
    expect(KNOWN_PLATFORMS).toContain("vscode-copilot");
    expect(KNOWN_PLATFORMS).toContain("jetbrains-copilot");
    expect(KNOWN_PLATFORMS).toContain("kiro");
    expect(KNOWN_PLATFORMS).toContain("zed");
    expect(KNOWN_PLATFORMS).toContain("cursor");
    expect(KNOWN_PLATFORMS).toContain("codex");
    expect(KNOWN_PLATFORMS).toContain("openclaw");
    expect(KNOWN_PLATFORMS).toContain("pi");
    expect(KNOWN_PLATFORMS).toContain("qwen-code");
    expect(KNOWN_PLATFORMS.length).toBeGreaterThanOrEqual(14);
  });
});

// ═══════════════════════════════════════════════════════════════════
// Routing Block Factory Functions
// ═══════════════════════════════════════════════════════════════════

describe("createRoutingBlock", () => {
  it("produces block with platform-specific tool names for gemini-cli", () => {
    const t = createToolNamer("gemini-cli");
    const block = createRoutingBlock(t);
    expect(block).toContain("mcp__context-mode__ctx_batch_execute");
    expect(block).toContain("mcp__context-mode__ctx_search");
    expect(block).toContain("mcp__context-mode__ctx_execute");
    expect(block).toContain("mcp__context-mode__ctx_fetch_and_index");
    // Must NOT contain claude-code prefix
    expect(block).not.toContain("mcp__plugin_context-mode_context-mode__");
  });

  it("produces block with bare names for cursor", () => {
    const t = createToolNamer("cursor");
    const block = createRoutingBlock(t);
    expect(block).toContain("ctx_batch_execute(commands, queries)");
    expect(block).toContain("ctx_search(queries:");
    expect(block).not.toContain("mcp__");
  });
});

describe("createReadGuidance", () => {
  it("uses kiro-style tool names for kiro platform", () => {
    const t = createToolNamer("kiro");
    const guidance = createReadGuidance(t);
    expect(guidance).toContain("@context-mode/ctx_execute_file");
  });
});

describe("createGrepGuidance", () => {
  it("uses opencode-style tool names for opencode platform", () => {
    const t = createToolNamer("opencode");
    const guidance = createGrepGuidance(t);
    expect(guidance).toContain("context-mode_ctx_execute");
  });
});

describe("createBashGuidance", () => {
  it("uses zed-style tool names for zed platform", () => {
    const t = createToolNamer("zed");
    const guidance = createBashGuidance(t);
    expect(guidance).toContain("mcp:context-mode:ctx_batch_execute");
    expect(guidance).toContain("mcp:context-mode:ctx_execute");
  });
});

describe("createExternalMcpGuidance (#529)", () => {
  it("uses kiro-style tool names for kiro platform", () => {
    const t = createToolNamer("kiro");
    const guidance = createExternalMcpGuidance(t);
    expect(guidance).toContain("@context-mode/ctx_execute");
    expect(guidance).toContain("@context-mode/ctx_fetch_and_index");
    expect(guidance).toContain("@context-mode/ctx_search");
  });

  it("uses opencode-style tool names for opencode platform", () => {
    const t = createToolNamer("opencode");
    const guidance = createExternalMcpGuidance(t);
    expect(guidance).toContain("context-mode_ctx_execute");
    expect(guidance).toContain("context-mode_ctx_fetch_and_index");
    expect(guidance).toContain("context-mode_ctx_search");
  });

  it("uses zed-style tool names for zed platform", () => {
    const t = createToolNamer("zed");
    const guidance = createExternalMcpGuidance(t);
    expect(guidance).toContain("mcp:context-mode:ctx_execute");
    expect(guidance).toContain("mcp:context-mode:ctx_fetch_and_index");
    expect(guidance).toContain("mcp:context-mode:ctx_search");
  });

  it("mentions the routing intent so the model knows what to do", () => {
    const t = createToolNamer("claude-code");
    const guidance = createExternalMcpGuidance(t);
    // Identifies the situation
    expect(guidance).toContain("External MCP tools");
    // Points to the right tools — losing any of these defeats the guidance
    expect(guidance).toMatch(/ctx_execute/);
    expect(guidance).toMatch(/ctx_fetch_and_index/);
    expect(guidance).toMatch(/ctx_search/);
  });
});

// ═══════════════════════════════════════════════════════════════════
// Backward Compat — Static Exports
// ═══════════════════════════════════════════════════════════════════

describe("backward compat static exports", () => {
  it("ROUTING_BLOCK uses claude-code naming", () => {
    expect(ROUTING_BLOCK).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
    );
    expect(ROUTING_BLOCK).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_search",
    );
  });

  it("READ_GUIDANCE uses claude-code naming", () => {
    expect(READ_GUIDANCE).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_execute_file",
    );
  });

  it("GREP_GUIDANCE uses claude-code naming", () => {
    expect(GREP_GUIDANCE).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_execute",
    );
  });

  it("BASH_GUIDANCE uses claude-code naming", () => {
    expect(BASH_GUIDANCE).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_batch_execute",
    );
  });

  it("EXTERNAL_MCP_GUIDANCE uses claude-code naming and matches the factory (#529)", () => {
    expect(EXTERNAL_MCP_GUIDANCE).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_execute",
    );
    expect(EXTERNAL_MCP_GUIDANCE).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_fetch_and_index",
    );
    expect(EXTERNAL_MCP_GUIDANCE).toContain(
      "mcp__plugin_context-mode_context-mode__ctx_search",
    );
    // Drift guard: the static export must equal the factory output with the
    // default (claude-code) namer — they share a single template.
    const claudeCodeT = createToolNamer("claude-code");
    expect(EXTERNAL_MCP_GUIDANCE).toBe(createExternalMcpGuidance(claudeCodeT));
  });
});

// ═══════════════════════════════════════════════════════════════════
// routePreToolUse with Platform Parameter
// ═══════════════════════════════════════════════════════════════════

describe("routePreToolUse with platform parameter", () => {
  it("curl block message uses gemini-cli tool names when platform=gemini-cli", () => {
    const result = routePreToolUse("Bash", { command: "curl https://example.com" }, "/tmp", "gemini-cli");
    expect(result).not.toBeNull();
    expect(result!.action).toBe("modify");
    const cmd = (result!.updatedInput as Record<string, string>).command;
    expect(cmd).toContain("mcp__context-mode__ctx_fetch_and_index");
    expect(cmd).toContain("mcp__context-mode__ctx_execute");
    expect(cmd).not.toContain("mcp__plugin_context-mode_context-mode__");
  });

  it("curl block message uses claude-code tool names when platform is omitted", () => {
    const result = routePreToolUse("Bash", { command: "curl https://example.com" }, "/tmp");
    expect(result).not.toBeNull();
    const cmd = (result!.updatedInput as Record<string, string>).command;
    expect(cmd).toContain("mcp__plugin_context-mode_context-mode__ctx_fetch_and_index");
  });

  it("inline HTTP block uses cursor bare names when platform=cursor", () => {
    const result = routePreToolUse("Bash", {
      command: 'python -c "requests.get(\'http://example.com\')"',
    }, "/tmp", "cursor");
    expect(result).not.toBeNull();
    const cmd = (result!.updatedInput as Record<string, string>).command;
    expect(cmd).toContain("ctx_execute");
    // PR #683 follow-up (ADR-0003 amendment): "Think in Code" voice-of-trainer
    // marker was folded into the imperative call instruction. The deny reason
    // now opens with the affirmative redirect frame; assert on the explicit
    // ctx_execute call instruction that survived the rewrite.
    expect(cmd).toContain("Call ctx_execute");
    expect(cmd).not.toContain("mcp__");
  });

  it("WebFetch deny uses kiro tool names when platform=kiro", () => {
    const result = routePreToolUse("WebFetch", { url: "https://example.com" }, "/tmp", "kiro");
    expect(result).not.toBeNull();
    expect(result!.action).toBe("deny");
    expect(result!.reason).toContain("@context-mode/ctx_fetch_and_index");
    expect(result!.reason).toContain("@context-mode/ctx_search");
  });

  it("Task is no longer routed — returns null (#241)", () => {
    const result = routePreToolUse("Task", {
      prompt: "Analyze the code",
    }, "/tmp", "opencode");
    expect(result).toBeNull();
  });

  it("Read guidance uses vscode-copilot tool names when platform=vscode-copilot", () => {
    const result = routePreToolUse("Read", { file_path: "/tmp/a.ts" }, "/tmp", "vscode-copilot");
    expect(result).not.toBeNull();
    expect(result!.action).toBe("context");
    expect(result!.additionalContext).toContain("context-mode_ctx_execute_file");
    expect(result!.additionalContext).not.toContain("mcp__plugin_context-mode_context-mode__");
  });

  it("Grep guidance uses zed tool names when platform=zed", () => {
    const result = routePreToolUse("Grep", { pattern: "TODO" }, "/tmp", "zed");
    expect(result).not.toBeNull();
    expect(result!.action).toBe("context");
    expect(result!.additionalContext).toContain("mcp:context-mode:ctx_execute");
  });

  it("Bash guidance uses openclaw bare names when platform=openclaw", () => {
    // Use an unbounded command so the #463 structurally-bounded allowlist
    // does not short-circuit the guidance — this test is about platform
    // tool-naming inside the guidance, not allowlist behavior.
    const result = routePreToolUse("Bash", { command: "npm install" }, "/tmp", "openclaw");
    expect(result).not.toBeNull();
    expect(result!.action).toBe("context");
    expect(result!.additionalContext).toContain("ctx_batch_execute");
    expect(result!.additionalContext).toContain("ctx_execute");
    expect(result!.additionalContext).not.toContain("mcp__");
  });

  it("OpenClaw lowercase native tools route through canonical aliases", () => {
    const exec = routePreToolUse("exec", { command: "npm install" }, "/tmp", "openclaw");
    expect(exec).not.toBeNull();
    expect(exec!.action).toBe("context");
    expect(exec!.additionalContext).toContain("ctx_batch_execute");

    const read = routePreToolUse("read", { file_path: "/tmp/a.ts" }, "/tmp", "openclaw");
    expect(read).not.toBeNull();
    expect(read!.action).toBe("context");
    expect(read!.additionalContext).toContain("ctx_execute_file");

    const search = routePreToolUse("search", { pattern: "TODO" }, "/tmp", "openclaw");
    expect(search).not.toBeNull();
    expect(search!.action).toBe("context");
    expect(search!.additionalContext).toContain("ctx_execute");
  });

  it("build tool redirect uses platform tool names when platform=gemini-cli", () => {
    const result = routePreToolUse("Bash", { command: "./gradlew build" }, "/tmp", "gemini-cli");
    expect(result).not.toBeNull();
    expect(result!.action).toBe("modify");
    const cmd = (result!.updatedInput as Record<string, string>).command;
    expect(cmd).toContain("mcp__context-mode__ctx_execute");
    expect(cmd).not.toContain("mcp__plugin_context-mode_context-mode__");
  });

  // ─── SLICE Qwen-3: routing.mjs Qwen native names ───
  describe("Qwen Code native tool names route through canonical aliases", () => {
    it("run_shell_command + curl routes as Bash → modify (curl block)", () => {
      const result = routePreToolUse(
        "run_shell_command",
        { command: "curl https://example.com" },
        "/tmp",
        "qwen-code",
      );
      expect(result).not.toBeNull();
      expect(result!.action).toBe("modify");
      expect((result!.updatedInput as Record<string, string>).command).toContain(
        "curl/wget redirected",
      );
    });

    it("web_fetch routes as WebFetch → deny", () => {
      const result = routePreToolUse(
        "web_fetch",
        { url: "https://example.com" },
        "/tmp",
        "qwen-code",
      );
      expect(result).not.toBeNull();
      expect(result!.action).toBe("deny");
      expect(result!.reason).toContain("WebFetch redirected");
    });

    it("read_file routes as Read → context guidance", () => {
      const result = routePreToolUse(
        "read_file",
        { file_path: "/tmp/a.ts" },
        "/tmp",
        "qwen-code",
      );
      expect(result).not.toBeNull();
      expect(result!.action).toBe("context");
      expect(result!.additionalContext).toContain("ctx_execute_file");
    });

    it("grep_search routes as Grep → context guidance", () => {
      const result = routePreToolUse(
        "grep_search",
        { pattern: "TODO" },
        "/tmp",
        "qwen-code",
      );
      expect(result).not.toBeNull();
      expect(result!.action).toBe("context");
    });
  });
});

// ─── SLICE Qwen-1: sessionstart platform-aware tool namer ───
describe("sessionstart detectPlatformFromEnv", () => {
  let detectPlatformFromEnv: (env?: Record<string, string | undefined>) => string;

  beforeAll(async () => {
    const mod = await import("../../hooks/core/platform-detect.mjs");
    detectPlatformFromEnv = mod.detectPlatformFromEnv;
  });

  it("returns qwen-code when QWEN_PROJECT_DIR is set", () => {
    expect(detectPlatformFromEnv({ QWEN_PROJECT_DIR: "/tmp/qwen" })).toBe("qwen-code");
  });

  // QWEN_SESSION_ID retracted in v1.0.107 — 0 hits in qwen-code source
  // (verified Phase 7 against refs/platforms/qwen-code/). Only QWEN_PROJECT_DIR
  // is set by the Qwen hook runner — see src/adapters/detect.ts:69 comment
  // and refs/platforms/qwen-code/packages/core/src/hooks/hookRunner.ts SET site.
  it("does NOT promote bare QWEN_SESSION_ID (fabrication retraction)", () => {
    expect(detectPlatformFromEnv({ QWEN_SESSION_ID: "qwen-1" })).toBe("claude-code");
  });

  it("returns gemini-cli when GEMINI_PROJECT_DIR is set", () => {
    expect(detectPlatformFromEnv({ GEMINI_PROJECT_DIR: "/tmp/g" })).toBe("gemini-cli");
  });

  it("falls back to claude-code when no env var is set", () => {
    expect(detectPlatformFromEnv({})).toBe("claude-code");
  });

  it("Qwen-prefix MCP names are produced when platform=qwen-code", () => {
    const namer = createToolNamer("qwen-code");
    expect(namer("ctx_execute")).toBe("mcp__context-mode__ctx_execute");
  });
});
