import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { describe, expect, it } from "vitest";
import { CLI_COMMANDS } from "../src/cli/args";
import onboardingMetadata from "../src/cli/onboarding-metadata.json";
import { COMMAND_HELP_DETAILS, HELP_COMMAND_GROUPS, HELP_FLAG_GROUPS, HELP_REFERENCE_ENTRIES, HELP_TOOL_ENTRIES } from "../src/cli/help";
import { INSPIREDESIGN_HANDOFF_COMMANDS, INSPIREDESIGN_HANDOFF_GUIDANCE } from "../src/inspiredesign/handoff";
import { LOCAL_ONLY_TOOL_NAMES } from "../src/tools";
import { TOOL_SURFACE_ENTRIES } from "../src/public-surface/generated-manifest";

const MIN_LAZY_COMMAND_IMPORTS = 40;
const GENERATED_WORKFLOW_COMMANDS = ["research", "shopping", "product-video", "inspiredesign"] as const;
const GENERATED_WORKFLOW_TOOL_NAMES = [
  "opendevbrowser_research_run",
  "opendevbrowser_shopping_run",
  "opendevbrowser_product_video_run",
  "opendevbrowser_inspiredesign_run"
] as const;
const FORBIDDEN_GENERATED_WORKFLOW_ROOTS = [
  "/tmp/inspiredesign",
  "/tmp/product-video",
  "artifacts/pinterest-harvest"
] as const;

function getRuntimeCommandDescriptions(): Record<string, string> {
  const source = readFileSync(resolve(process.cwd(), "src/cli/index.ts"), "utf8");
  return Object.fromEntries(
    [...source.matchAll(/registerCommand\(\{\s*name:\s*"([^"]+)",\s*description:\s*"([^"]+)"/gs)]
      .map((match) => [match[1], match[2]])
  );
}

function getLazyCommandImports(): Array<{ modulePath: string; exportName: string }> {
  const source = readFileSync(resolve(process.cwd(), "src/cli/index.ts"), "utf8");
  return [...source.matchAll(/runLazyCommand\(parsedArgs, \(\) => import\("([^"]+)"\), "([^"]+)"\)/g)]
    .map((match) => ({
      modulePath: match[1] ?? "",
      exportName: match[2] ?? ""
    }));
}

describe("cli help parity", () => {
  it("covers the full CLI command inventory", () => {
    const helpCommands = HELP_COMMAND_GROUPS.flatMap((group) => [...group.commands]);

    expect(new Set(helpCommands).size).toBe(CLI_COMMANDS.length);
    expect(new Set(helpCommands)).toEqual(new Set(CLI_COMMANDS));
    expect(new Set(Object.keys(COMMAND_HELP_DETAILS))).toEqual(new Set(CLI_COMMANDS));
  });

  it("tracks the expected tool inventory size", () => {
    const names = HELP_TOOL_ENTRIES.map((entry) => entry.name);

    expect(new Set(names).size).toBe(HELP_TOOL_ENTRIES.length);
    expect(HELP_TOOL_ENTRIES).toEqual(TOOL_SURFACE_ENTRIES);
    for (const entry of HELP_TOOL_ENTRIES) {
      expect(entry.example?.length ?? 0).toBeGreaterThan(0);
    }
  });

  it("includes the session-inspector public-surface tranche", () => {
    const commandNames = HELP_COMMAND_GROUPS.flatMap((group) => [...group.commands]);
    const toolNames = HELP_TOOL_ENTRIES.map((entry) => entry.name);

    expect(commandNames).toContain("session-inspector");
    expect(HELP_COMMAND_GROUPS.find((group) => group.commands.includes("session-inspector"))?.commands).toContain("session-inspector");
    expect(toolNames).toContain("opendevbrowser_session_inspector");
    expect(COMMAND_HELP_DETAILS["session-inspector"].flags).toEqual(expect.arrayContaining([
      "--session-id",
      "--include-urls",
      "--since-console-seq",
      "--since-network-seq",
      "--since-exception-seq",
      "--max",
      "--request-id"
    ]));
  });

  it("includes screencast and desktop public-surface entries", () => {
    const commandNames = HELP_COMMAND_GROUPS.flatMap((group) => [...group.commands]);
    const toolNames = HELP_TOOL_ENTRIES.map((entry) => entry.name);

    expect(commandNames).toEqual(expect.arrayContaining([
      "screencast-start",
      "screencast-stop",
      "desktop-status",
      "desktop-windows",
      "desktop-active-window",
      "desktop-capture-desktop",
      "desktop-capture-window",
      "desktop-accessibility-snapshot"
    ]));
    expect(HELP_COMMAND_GROUPS.find((group) => group.title === "Browser Replay")?.commands).toEqual([
      "screencast-start",
      "screencast-stop"
    ]);
    expect(HELP_COMMAND_GROUPS.find((group) => group.title === "Desktop Observation")?.commands).toEqual([
      "desktop-status",
      "desktop-windows",
      "desktop-active-window",
      "desktop-capture-desktop",
      "desktop-capture-window",
      "desktop-accessibility-snapshot"
    ]);
    expect(HELP_COMMAND_GROUPS.find((group) => group.title === "Desktop Observation")?.summary).toContain("swift command");
    expect(HELP_COMMAND_GROUPS.find((group) => group.title === "Desktop Observation")?.summary).toContain("screencapture");
    expect(toolNames).toEqual(expect.arrayContaining([
      "opendevbrowser_screencast_start",
      "opendevbrowser_screencast_stop",
      "opendevbrowser_desktop_status",
      "opendevbrowser_desktop_windows",
      "opendevbrowser_desktop_active_window",
      "opendevbrowser_desktop_capture_desktop",
      "opendevbrowser_desktop_capture_window",
      "opendevbrowser_desktop_accessibility_snapshot"
    ]));
    expect(COMMAND_HELP_DETAILS["screencast-start"].flags).toEqual(expect.arrayContaining([
      "--output-dir",
      "--interval-ms",
      "--max-frames"
    ]));
    expect(COMMAND_HELP_DETAILS["desktop-capture-window"].flags).toEqual(expect.arrayContaining([
      "--window-id",
      "--reason",
      "--timeout-ms"
    ]));
  });

  it("includes inspiredesign command, tool, and required workflow flags", () => {
    const commandNames = HELP_COMMAND_GROUPS.flatMap((group) => [...group.commands]);
    const toolNames = HELP_TOOL_ENTRIES.map((entry) => entry.name);

    expect(commandNames).toContain("inspiredesign");
    expect(toolNames).toContain("opendevbrowser_inspiredesign_run");
    expect(COMMAND_HELP_DETAILS.inspiredesign.flags).toEqual(expect.arrayContaining([
      "--brief",
      "--url",
      "--query",
      "--provider",
      "--max-references",
      "--visual-evidence",
      "--capture-mode",
      "--include-prototype-guidance",
      "--browser-mode"
    ]));
  });

  it("keeps runtime command registration aligned with the declared CLI inventory", () => {
    const source = readFileSync(resolve(process.cwd(), "src/cli/index.ts"), "utf8");
    const registeredNames = [...source.matchAll(/registerCommand\(\{\s*name:\s*"([^"]+)"/gs)].map((match) => match[1]);

    expect(new Set(registeredNames)).toEqual(new Set(CLI_COMMANDS));
  });

  it("keeps runtime command descriptions aligned with generated help descriptions", () => {
    const runtimeDescriptions = getRuntimeCommandDescriptions();

    expect(new Set(Object.keys(runtimeDescriptions))).toEqual(new Set(CLI_COMMANDS));
    for (const command of CLI_COMMANDS) {
      expect(COMMAND_HELP_DETAILS[command].description.length).toBeGreaterThan(0);
      expect(COMMAND_HELP_DETAILS[command].description).toBe(runtimeDescriptions[command]);
      expect(COMMAND_HELP_DETAILS[command].examples.length).toBeGreaterThan(0);
      for (const example of COMMAND_HELP_DETAILS[command].examples) {
        expect(example.startsWith("npx opendevbrowser")).toBe(true);
      }
    }
  });

  it("keeps lazy command import contracts resolvable", async () => {
    const lazyImports = getLazyCommandImports();

    expect(lazyImports.length).toBeGreaterThan(MIN_LAZY_COMMAND_IMPORTS);
    for (const lazyImport of lazyImports) {
      const absolutePath = resolve(process.cwd(), "src/cli", `${lazyImport.modulePath}.ts`);
      const moduleExports = await import(pathToFileURL(absolutePath).href) as Record<string, unknown>;

      expect(moduleExports[lazyImport.exportName], `${lazyImport.modulePath} should export ${lazyImport.exportName}`)
        .toBeTypeOf("function");
    }
  });

  it("keeps runtime tool names aligned with the mirrored tool inventory", () => {
    const source = readFileSync(resolve(process.cwd(), "src/tools/index.ts"), "utf8");
    const runtimeToolNames = [...source.matchAll(/\s(opendevbrowser_[a-z_]+):/g)].map((match) => match[1]);

    expect(new Set(runtimeToolNames)).toEqual(new Set(TOOL_SURFACE_ENTRIES.map((entry) => entry.name)));
  });

  it("keeps generated workflow examples on preferred output roots", () => {
    for (const command of GENERATED_WORKFLOW_COMMANDS) {
      const detail = COMMAND_HELP_DETAILS[command];
      const generatedCommandText = [...detail.examples, ...detail.notes].join("\n");

      for (const forbiddenRoot of FORBIDDEN_GENERATED_WORKFLOW_ROOTS) {
        expect(generatedCommandText, `${command} generated help should not teach ${forbiddenRoot}`)
          .not.toContain(forbiddenRoot);
      }

      expect(generatedCommandText).toContain("Routine workflow runs should omit --output-dir");
      expect(generatedCommandText).toContain("--output-dir .opendevbrowser");
    }

    for (const toolName of GENERATED_WORKFLOW_TOOL_NAMES) {
      const entry = HELP_TOOL_ENTRIES.find((candidate) => candidate.name === toolName);

      expect(entry, `${toolName} should be present in generated tool help`).toBeDefined();
      if (!entry) {
        throw new Error(`${toolName} should be present in generated tool help`);
      }
      const generatedToolText = [entry.example, ...(entry.notes ?? [])].join("\n");

      expect(entry.example.length).toBeGreaterThan(0);
      for (const forbiddenRoot of FORBIDDEN_GENERATED_WORKFLOW_ROOTS) {
        expect(generatedToolText, `${toolName} generated help should not teach ${forbiddenRoot}`)
          .not.toContain(forbiddenRoot);
      }
      expect(generatedToolText).toContain("Routine workflow runs should omit --output-dir");
      expect(generatedToolText).toContain("--output-dir .opendevbrowser");
    }
  });

  it("exposes media-analysis dependency guidance in generated command and tool help", () => {
    const inspiredesignCommandText = [
      ...COMMAND_HELP_DETAILS.inspiredesign.examples,
      ...COMMAND_HELP_DETAILS.inspiredesign.notes
    ].join("\n");
    const statusCapabilitiesText = [
      ...COMMAND_HELP_DETAILS["status-capabilities"].examples,
      ...COMMAND_HELP_DETAILS["status-capabilities"].notes
    ].join("\n");
    const inspiredesignTool = HELP_TOOL_ENTRIES.find((entry) => entry.name === "opendevbrowser_inspiredesign_run");
    const inspiredesignToolText = [inspiredesignTool?.example ?? "", ...(inspiredesignTool?.notes ?? [])].join("\n");

    for (const generatedText of [inspiredesignCommandText, statusCapabilitiesText, inspiredesignToolText]) {
      expect(generatedText).toContain("FFmpeg");
      expect(generatedText).toContain("FFprobe");
    }

    expect(inspiredesignCommandText).toContain("recommended optional host tools");
    expect(inspiredesignCommandText).toContain("OPENDEVBROWSER_FFMPEG_PATH");
    expect(inspiredesignCommandText).toContain("OPENDEVBROWSER_FFPROBE_PATH");
    expect(inspiredesignCommandText).toContain("inspiredesign.mediaAnalysis.ffmpegPath");
    expect(inspiredesignCommandText).toContain("inspiredesign.mediaAnalysis.ffprobePath");
    expect(inspiredesignCommandText).toContain("then ffmpeg and ffprobe on PATH");
    expect(inspiredesignCommandText).toContain("common absolute install directories for implicit PATH-source ENOENT misses only");
    expect(inspiredesignCommandText).toContain("Invalid env or config paths stay diagnostic and do not fall back");
    expect(inspiredesignCommandText).toContain("Missing binaries degrade media-analysis.json only");
    expect(inspiredesignCommandText).toContain("never make media-analysis.json satisfy product readiness");
    expect(statusCapabilitiesText).toContain("host.mediaAnalysis");
    expect(statusCapabilitiesText).toContain("optional media-analysis host capability");
    expect(statusCapabilitiesText).not.toContain("media-analysis prerequisites");
    expect(inspiredesignToolText).toContain("host.mediaAnalysis");
    expect(inspiredesignToolText).toContain("optional media-analysis host capability");
    expect(inspiredesignToolText).not.toContain("media-analysis prerequisites");
  });

  it("keeps multi-flag help metadata aligned for workflow and run commands", () => {
    expect(COMMAND_HELP_DETAILS.uninstall.usage).toContain("--quiet");
    expect(COMMAND_HELP_DETAILS.run.examples[0]).toContain("run --script ./workflow.json");
    expect(COMMAND_HELP_DETAILS.run.flags).toEqual(expect.arrayContaining(["--headless", "--persist-profile"]));
    expect(COMMAND_HELP_DETAILS.launch.flags).toEqual(expect.arrayContaining([
      "--google-auth-intent",
      "--disable-system-cookie-bootstrap",
      "--allow-google-cookie-bootstrap"
    ]));
    expect(COMMAND_HELP_DETAILS.connect.flags).toEqual(expect.arrayContaining([
      "--google-auth-intent",
      "--disable-system-cookie-bootstrap",
      "--allow-google-cookie-bootstrap"
    ]));
    expect(COMMAND_HELP_DETAILS.research.flags).toEqual(expect.arrayContaining(["--output-dir", "--ttl-hours", "--browser-mode", "--profile"]));
    expect(COMMAND_HELP_DETAILS.research.examples[0]).toContain("research run");
    expect(COMMAND_HELP_DETAILS.shopping.flags).toEqual(expect.arrayContaining(["--output-dir", "--ttl-hours", "--profile"]));
    expect(COMMAND_HELP_DETAILS.shopping.examples[0]).toContain("shopping run");
    expect(COMMAND_HELP_DETAILS["product-video"].flags).toEqual(expect.arrayContaining(["--output-dir", "--ttl-hours", "--browser-mode", "--profile"]));
    expect(COMMAND_HELP_DETAILS["product-video"].examples[0]).toContain("product-video run");
    expect(COMMAND_HELP_DETAILS.inspiredesign.flags).toEqual(expect.arrayContaining([
      "--brief",
      "--url",
      "--query",
      "--provider",
      "--max-references",
      "--visual-evidence",
      "--capture-mode",
      "--include-prototype-guidance",
      "--browser-mode",
      "--profile"
    ]));
    expect(COMMAND_HELP_DETAILS.inspiredesign.examples[0]).toContain("inspiredesign run");
    expect(COMMAND_HELP_DETAILS.inspiredesign.examples[1]).toContain("inspiredesign harvest");
    expect(HELP_COMMAND_GROUPS.flatMap((group) => [...group.commands])).toContain("inspiredesign");
    expect(HELP_TOOL_ENTRIES.map((entry) => entry.name)).toContain("opendevbrowser_inspiredesign_run");
  });

  it("documents Google OAuth continuity flags in CLI help", () => {
    const launchHelpText = [
      COMMAND_HELP_DETAILS.launch.usage,
      ...COMMAND_HELP_DETAILS.launch.examples,
      ...COMMAND_HELP_DETAILS.launch.notes
    ].join("\n");
    const connectHelpText = [
      COMMAND_HELP_DETAILS.connect.usage,
      ...COMMAND_HELP_DETAILS.connect.examples,
      ...COMMAND_HELP_DETAILS.connect.notes
    ].join("\n");
    const sessionFlagText = HELP_FLAG_GROUPS
      .flatMap((group) => group.flags)
      .filter((entry) => entry.flag === "--google-auth-intent"
        || entry.flag === "--disable-system-cookie-bootstrap"
        || entry.flag === "--allow-google-cookie-bootstrap")
      .map((entry) => `${entry.flag} ${entry.description} ${entry.example ?? ""}`)
      .join("\n");

    expect(launchHelpText).toContain("--google-auth-intent user-owned");
    expect(launchHelpText).toContain("--disable-system-cookie-bootstrap");
    expect(launchHelpText).toContain("--allow-google-cookie-bootstrap");
    expect(connectHelpText).toContain("--google-auth-intent user-owned");
    expect(connectHelpText).toContain("--disable-system-cookie-bootstrap");
    expect(connectHelpText).toContain("--allow-google-cookie-bootstrap");
    expect(sessionFlagText).toContain("extension /ops");
    expect(sessionFlagText).toContain("copied cookies are not Google auth proof");
    expect(sessionFlagText).toContain("explicitly import Google-sensitive cookies");
    expect(sessionFlagText).toContain("diagnostics.authProvenance");
  });

  it("exposes lifecycle skill flags in generated update and uninstall help", () => {
    expect(COMMAND_HELP_DETAILS.update.usage).toContain("--skills-global|--skills-local|--no-skills");
    expect(COMMAND_HELP_DETAILS.update.flags).toEqual(expect.arrayContaining([
      "--global",
      "--local",
      "--skills-global",
      "--skills-local",
      "--no-skills"
    ]));
    expect(COMMAND_HELP_DETAILS.uninstall.usage).toContain("--no-skills");
    expect(COMMAND_HELP_DETAILS.uninstall.flags).toEqual(expect.arrayContaining([
      "--global",
      "--local",
      "--no-skills",
      "--no-prompt",
      "--quiet"
    ]));
  });

  it("mentions both help invocations in the generated help text", () => {
    const labels = HELP_REFERENCE_ENTRIES.map((entry) => entry.label);

    expect(labels).toContain("opendevbrowser --help");
    expect(labels).toContain("opendevbrowser help");
    expect(labels).toContain("src/cli/help.ts");
    expect(labels).toContain("src/cli/onboarding-metadata.json");
    expect(labels).toContain("src/providers/workflow-handoff.ts");
    expect(labels).toContain("src/inspiredesign/handoff.ts");
    expect(labels).toContain("src/public-surface/generated-manifest.ts");
    expect(labels).toContain("docs/WORKFLOW_SURFACE_MAP.md");
    expect(labels).toContain(onboardingMetadata.referencePaths.onboardingDoc);
    expect(labels).toContain(onboardingMetadata.referencePaths.skillDoc);
    expect(labels).not.toContain("~/.codex/skills/opendevbrowser-best-practices");
    expect(labels).not.toContain("src/tools/surface.ts");
  });

  it("keeps inspiredesign followthrough commands sourced from the shared handoff module", () => {
    const inspiredesignEntry = HELP_REFERENCE_ENTRIES.find((entry) => entry.label === "src/inspiredesign/handoff.ts");

    expect(inspiredesignEntry?.description).toContain("Shared inspiredesign follow-through commands");
    expect(INSPIREDESIGN_HANDOFF_COMMANDS.loadBestPractices).toContain("opendevbrowser-best-practices");
    expect(INSPIREDESIGN_HANDOFF_COMMANDS.loadDesignAgent).toContain("opendevbrowser-design-agent");
    expect(INSPIREDESIGN_HANDOFF_COMMANDS.loadMotionDesign).toContain("opendevbrowser-motion-design");
    expect(INSPIREDESIGN_HANDOFF_GUIDANCE.reviewAdvancedBrief).toContain("advanced-brief.md");
    expect(INSPIREDESIGN_HANDOFF_GUIDANCE.prepareCanvasPlanRequest).toContain("canvas-plan.request.json");
    expect(INSPIREDESIGN_HANDOFF_COMMANDS.continueInCanvas).toContain("canvas.plan.set");
  });

  it("keeps onboarding-recommended tool names local-only", () => {
    expect(new Set(onboardingMetadata.localOnlyToolNames)).toEqual(LOCAL_ONLY_TOOL_NAMES);
  });

  it("documents the temporary-profile default for one-shot run commands", () => {
    const persistProfileEntry = HELP_FLAG_GROUPS
      .flatMap((group) => group.flags)
      .find((entry) => entry.flag === "--persist-profile");

    expect(persistProfileEntry?.description).toContain("`run` uses a temporary profile by default");
  });
});
