import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { join, relative } from "node:path";
import { Type } from "@sinclair/typebox";

interface AuditResult {
  outdated: string[];
  vulnerabilities: string[];
  unused: string[];
  missing: string[];
  summary: string;
}

export default function (pi: ExtensionAPI) {
  async function parsePackageJson(cwd: string): Promise<{ deps: Record<string, string>; devDeps: Record<string, string>; scripts: Record<string, string> } | null> {
    const pkgPath = join(cwd, "package.json");
    if (!existsSync(pkgPath)) return null;
    try {
      const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
      return {
        deps: pkg.dependencies || {},
        devDeps: pkg.devDependencies || {},
        scripts: pkg.scripts || {},
      };
    } catch {
      return null;
    }
  }

  async function runNpmAudit(cwd: string): Promise<string> {
    try {
      const result = await pi.exec("npm", ["audit", "--json"], { cwd });
      if (result.stdout.trim()) {
        try {
          const audit = JSON.parse(result.stdout);
          const meta = audit.metadata || {};
          const vulns = meta.vulnerabilities || {};
          const lines: string[] = [];
          let totalVulns = 0;
          for (const [severity, count] of Object.entries(vulns)) {
            if (typeof count === "number" && count > 0) {
              lines.push(`  ${severity}: ${count}`);
              totalVulns += count;
            }
          }
          if (totalVulns === 0) return "\u2705 No known vulnerabilities found.";
          return `\u26A0\uFE0F Found ${totalVulns} vulnerabilities:\n${lines.join("\n")}`;
        } catch {
          return result.stdout.substring(0, 1000);
        }
      }
      return result.stderr.trim() || "No audit data available.";
    } catch (err: any) {
      return `Audit error: ${err.message}`;
    }
  }

  async function runNpmOutdated(cwd: string): Promise<string[]> {
    try {
      const result = await pi.exec("npm", ["outdated", "--json"], { cwd });
      if (result.stdout.trim()) {
        try {
          const outdated = JSON.parse(result.stdout);
          const lines: string[] = [];
          for (const [pkg, info] of Object.entries(outdated)) {
            const i = info as any;
            lines.push(`  ${pkg}: ${i.current || "?"} \u2192 ${i.latest || "?"}`);
          }
          return lines;
        } catch {
          return [result.stdout.substring(0, 500)];
        }
      }
      return [];
    } catch {
      return [];
    }
  }

  async function findUnusedDeps(cwd: string, deps: Record<string, string>): Promise<string[]> {
    const unused: string[] = [];
    const depNames = Object.keys(deps);
    if (depNames.length === 0) return unused;

    for (const dep of depNames) {
      try {
        // Search for import/require of this dep in source files
        const result = await pi.exec(
          "grep",
          ["-r", "-l", "--include=*.ts", "--include=*.tsx", "--include=*.js", "--include=*.jsx", "--include=*.mjs", "--include=*.cjs", dep, "."],
          { cwd },
        );
        if (result.code !== 0 || !result.stdout.trim()) {
          // Also check if referenced in config files
          const configResult = await pi.exec(
            "grep",
            ["-r", "-l", "--include=*.json", "--include=*.yaml", "--include=*.yml", "--include=*.toml", "--include=*.config.*", dep, "."],
            { cwd },
          );
          // Exclude package.json and lock files
          const configFiles = (configResult.stdout || "").split("\n").filter(
            (f) => f.trim() && !f.includes("package.json") && !f.includes("package-lock") && !f.includes("node_modules"),
          );
          if (configFiles.length === 0) {
            unused.push(dep);
          }
        }
      } catch {
        // grep failed, skip
      }
    }
    return unused;
  }

  async function fullAudit(cwd: string): Promise<AuditResult> {
    const pkg = await parsePackageJson(cwd);
    if (!pkg) {
      return {
        outdated: [],
        vulnerabilities: ["No package.json found in current directory."],
        unused: [],
        missing: [],
        summary: "\u274C No package.json found. Navigate to a Node.js project directory.",
      };
    }

    const allDeps = { ...pkg.deps, ...pkg.devDeps };
    const totalDeps = Object.keys(allDeps).length;

    // Run checks in parallel
    const [outdated, securityReport, unused] = await Promise.all([
      runNpmOutdated(cwd),
      runNpmAudit(cwd),
      findUnusedDeps(cwd, pkg.deps),
    ]);

    // Check for missing deps (referenced but not in package.json)
    const missing: string[] = [];
    try {
      const result = await pi.exec(
        "grep",
        ["-r", "-h", "-o", "--include=*.ts", "--include=*.tsx", "--include=*.js", "--include=*.jsx",
         `from ['"][^./][^'"]*['"]`, "."],
        { cwd },
      );
      if (result.stdout) {
        const imports = new Set(
          result.stdout.split("\n")
            .map((line) => {
              const match = line.match(/from\s+['"]([^./][^'"]*)['"]/);
              return match ? match[1].split("/")[0] : null;
            })
            .filter((name): name is string => name !== null && !name.startsWith("node:")),
        );
        for (const imp of imports) {
          if (!allDeps[imp] && !imp.startsWith("@")) {
            missing.push(imp);
          }
        }
      }
    } catch { /* ignore grep errors */ }

    const summaryLines = [
      `\u{1F4E6} Dependencies: ${totalDeps} total (${Object.keys(pkg.deps).length} prod, ${Object.keys(pkg.devDeps).length} dev)`,
      `\u{1F504} Outdated: ${outdated.length > 0 ? outdated.length + " packages" : "all up to date \u2705"}`,
      `\u{1F6E1}\uFE0F Security: ${securityReport}`,
      `\u{1F5D1}\uFE0F Potentially unused: ${unused.length > 0 ? unused.join(", ") : "none detected \u2705"}`,
    ];
    if (missing.length > 0) {
      summaryLines.push(`\u26A0\uFE0F Possibly missing: ${missing.join(", ")}`);
    }

    return {
      outdated,
      vulnerabilities: [securityReport],
      unused,
      missing,
      summary: summaryLines.join("\n"),
    };
  }

  pi.registerTool({
    name: "audit_dependencies",
    label: "Audit Dependencies",
    description: "Scan project dependencies for outdated packages, security vulnerabilities, and unused dependencies. Optionally auto-fix issues.",
    parameters: Type.Object({
      action: Type.Optional(Type.String({ description: "Action: 'scan' (default), 'fix', 'security'" })),
    }),
    execute: async (args, ctx) => {
      const action = (args.action || "scan").toLowerCase();

      if (action === "fix") {
        const updateResult = await pi.exec("npm", ["update"], { cwd: ctx.cwd });
        const auditFixResult = await pi.exec("npm", ["audit", "fix"], { cwd: ctx.cwd });
        return {
          output: `\u{1F527} Auto-fix results:\n\nnpm update:\n${updateResult.stdout || updateResult.stderr}\n\nnpm audit fix:\n${auditFixResult.stdout || auditFixResult.stderr}`,
        };
      }

      if (action === "security") {
        const report = await runNpmAudit(ctx.cwd);
        return { output: `\u{1F6E1}\uFE0F Security Report:\n\n${report}` };
      }

      const result = await fullAudit(ctx.cwd);
      return { output: result.summary };
    },
  });

  pi.registerCommand("audit", {
    description: "Audit dependencies: /audit [fix|security]",
    handler: async (args, ctx) => {
      const subcommand = args.trim().toLowerCase();

      if (subcommand === "fix") {
        ctx.ui.notify("\u{1F527} Running npm update and audit fix...", "info");
        const updateResult = await pi.exec("npm", ["update"], { cwd: ctx.cwd });
        const auditFixResult = await pi.exec("npm", ["audit", "fix"], { cwd: ctx.cwd });
        ctx.ui.notify(
          `\u{1F527} Fix Results:\n\nnpm update:\n${(updateResult.stdout || updateResult.stderr || "done").substring(0, 500)}\n\nnpm audit fix:\n${(auditFixResult.stdout || auditFixResult.stderr || "done").substring(0, 500)}`,
          "info",
        );
        return;
      }

      if (subcommand === "security") {
        ctx.ui.notify("\u{1F6E1}\uFE0F Running security audit...", "info");
        const report = await runNpmAudit(ctx.cwd);
        ctx.ui.notify(`\u{1F6E1}\uFE0F Security Report:\n\n${report}`, "info");
        return;
      }

      ctx.ui.notify("\u{1F50D} Scanning dependencies...", "info");
      const result = await fullAudit(ctx.cwd);

      let output = `\u{1F4CB} Dependency Audit Report\n${'\u2500'.repeat(40)}\n\n${result.summary}`;

      if (result.outdated.length > 0) {
        output += `\n\n\u{1F504} Outdated Packages:\n${result.outdated.join("\n")}`;
      }

      if (result.unused.length > 0) {
        output += `\n\n\u{1F5D1}\uFE0F Potentially Unused:\n  ${result.unused.join(", ")}`;
        output += `\n  Run: npm uninstall ${result.unused.join(" ")}`;
      }

      if (result.missing.length > 0) {
        output += `\n\n\u26A0\uFE0F Possibly Missing:\n  ${result.missing.join(", ")}`;
        output += `\n  Run: npm install ${result.missing.join(" ")}`;
      }

      output += `\n\n\u{1F4A1} Tip: /audit fix to auto-update, /audit security for vuln details`;

      ctx.ui.notify(output, "info");
    },
  });
}
