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

interface EnvFile {
  name: string;
  path: string;
  vars: Map<string, string>;
}

export default function (pi: ExtensionAPI) {
  function parseEnvFile(filePath: string): Map<string, string> {
    const vars = new Map<string, string>();
    if (!existsSync(filePath)) return vars;
    try {
      const content = readFileSync(filePath, "utf-8");
      for (const line of content.split("\n")) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith("#")) continue;
        const eqIndex = trimmed.indexOf("=");
        if (eqIndex === -1) continue;
        const key = trimmed.substring(0, eqIndex).trim();
        let value = trimmed.substring(eqIndex + 1).trim();
        // Strip quotes
        if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
          value = value.slice(1, -1);
        }
        vars.set(key, value);
      }
    } catch { /* ignore read errors */ }
    return vars;
  }

  function findEnvFiles(cwd: string): EnvFile[] {
    const envFiles: EnvFile[] = [];

    function scanDir(dir: string, depth: number = 0): void {
      if (depth > 3) return; // Don't go too deep
      try {
        const entries = readdirSync(dir);
        for (const entry of entries) {
          if (entry === "node_modules" || entry === ".git" || entry === "dist" || entry === "build") continue;
          const fullPath = join(dir, entry);
          try {
            const stat = statSync(fullPath);
            if (stat.isFile() && (entry.startsWith(".env") || entry.endsWith(".env"))) {
              envFiles.push({
                name: relative(cwd, fullPath) || entry,
                path: fullPath,
                vars: parseEnvFile(fullPath),
              });
            } else if (stat.isDirectory() && depth < 3) {
              scanDir(fullPath, depth + 1);
            }
          } catch { /* skip inaccessible */ }
        }
      } catch { /* skip unreadable dirs */ }
    }

    scanDir(cwd);
    return envFiles;
  }

  function maskValue(value: string): string {
    if (value.length <= 4) return "****";
    return value.substring(0, 2) + "*".repeat(Math.min(value.length - 4, 20)) + value.substring(value.length - 2);
  }

  function diffEnvFiles(fileA: EnvFile, fileB: EnvFile): string {
    const allKeys = new Set([...fileA.vars.keys(), ...fileB.vars.keys()]);
    const lines: string[] = [
      `\u{1F504} Diff: ${fileA.name} vs ${fileB.name}`,
      `${'\u2500'.repeat(50)}`,
    ];

    let onlyA: string[] = [];
    let onlyB: string[] = [];
    let different: string[] = [];
    let same = 0;

    for (const key of Array.from(allKeys).sort()) {
      const hasA = fileA.vars.has(key);
      const hasB = fileB.vars.has(key);

      if (hasA && !hasB) {
        onlyA.push(key);
      } else if (!hasA && hasB) {
        onlyB.push(key);
      } else if (hasA && hasB) {
        const valA = fileA.vars.get(key)!;
        const valB = fileB.vars.get(key)!;
        if (valA !== valB) {
          different.push(`  ${key}: ${maskValue(valA)} \u2192 ${maskValue(valB)}`);
        } else {
          same++;
        }
      }
    }

    if (onlyA.length > 0) lines.push(`\n\u{1F7E2} Only in ${fileA.name}:\n  ${onlyA.join(", ")}`);
    if (onlyB.length > 0) lines.push(`\n\u{1F535} Only in ${fileB.name}:\n  ${onlyB.join(", ")}`);
    if (different.length > 0) lines.push(`\n\u{1F7E1} Different values:\n${different.join("\n")}`);
    lines.push(`\n\u2705 ${same} variables identical`);

    return lines.join("\n");
  }

  function checkMissingVars(envFiles: EnvFile[]): string {
    if (envFiles.length < 2) return "Need at least 2 .env files to check for missing variables.";

    // Collect all keys across all files
    const allKeys = new Set<string>();
    for (const f of envFiles) {
      for (const key of f.vars.keys()) {
        allKeys.add(key);
      }
    }

    const lines: string[] = [
      `\u{1F50D} Missing Variables Check`,
      `${'\u2500'.repeat(50)}`,
      `Files: ${envFiles.map((f) => f.name).join(", ")}`,
      `Total unique keys: ${allKeys.size}`,
      "",
    ];

    let missingCount = 0;
    for (const key of Array.from(allKeys).sort()) {
      const presentIn: string[] = [];
      const missingFrom: string[] = [];
      for (const f of envFiles) {
        if (f.vars.has(key)) presentIn.push(f.name);
        else missingFrom.push(f.name);
      }
      if (missingFrom.length > 0) {
        lines.push(`  \u26A0\uFE0F ${key}: missing from ${missingFrom.join(", ")}`);
        missingCount++;
      }
    }

    if (missingCount === 0) {
      lines.push("\u2705 All variables present in all env files!");
    } else {
      lines.push(`\n\u{1F4CA} ${missingCount} variables missing across environments`);
    }

    return lines.join("\n");
  }

  function syncEnvFiles(source: EnvFile, target: EnvFile): { added: string[]; content: string } {
    const added: string[] = [];
    const targetContent = existsSync(target.path) ? readFileSync(target.path, "utf-8") : "";
    let newContent = targetContent;

    for (const [key, value] of source.vars) {
      if (!target.vars.has(key)) {
        added.push(key);
        const appendLine = `\n# Synced from ${source.name}\n${key}=${value}`;
        newContent += appendLine;
      }
    }

    return { added, content: newContent };
  }

  pi.registerTool({
    name: "env_manage",
    label: "Environment Manager",
    description: "Manage .env files: list, diff, check for missing vars, or sync between environments.",
    parameters: Type.Object({
      action: Type.String({ description: "Action: 'list', 'diff', 'check', 'sync'" }),
      source: Type.Optional(Type.String({ description: "Source env file name for diff/sync" })),
      target: Type.Optional(Type.String({ description: "Target env file name for diff/sync" })),
    }),
    execute: async (args, ctx) => {
      const envFiles = findEnvFiles(ctx.cwd);
      const action = args.action.toLowerCase();

      switch (action) {
        case "list": {
          if (envFiles.length === 0) return { output: "No .env files found in project." };
          const list = envFiles
            .map((f) => `  ${f.name}: ${f.vars.size} variables`)
            .join("\n");
          return { output: `\u{1F4C1} Environment Files:\n${list}` };
        }
        case "diff": {
          if (envFiles.length < 2) return { error: "Need at least 2 .env files to diff." };
          const source = args.source ? envFiles.find((f) => f.name.includes(args.source!)) : envFiles[0];
          const target = args.target ? envFiles.find((f) => f.name.includes(args.target!)) : envFiles[1];
          if (!source || !target) return { error: "Could not find specified env files." };
          return { output: diffEnvFiles(source, target) };
        }
        case "check": {
          return { output: checkMissingVars(envFiles) };
        }
        case "sync": {
          if (!args.source || !args.target) return { error: "Specify source and target env files." };
          const source = envFiles.find((f) => f.name.includes(args.source!));
          const target = envFiles.find((f) => f.name.includes(args.target!));
          if (!source || !target) return { error: "Could not find specified env files." };
          const { added, content } = syncEnvFiles(source, target);
          if (added.length === 0) return { output: "\u2705 Target already has all source variables." };
          writeFileSync(target.path, content, "utf-8");
          return { output: `\u2705 Synced ${added.length} variables to ${target.name}:\n  ${added.join(", ")}` };
        }
        default:
          return { error: `Unknown action '${action}'. Use: list, diff, check, sync` };
      }
    },
  });

  pi.registerCommand("env", {
    description: "Manage env files: /env [diff|check|sync <source> <target>]",
    handler: async (args, ctx) => {
      const parts = args.trim().split(/\s+/);
      const subcommand = (parts[0] || "list").toLowerCase();
      const envFiles = findEnvFiles(ctx.cwd);

      switch (subcommand) {
        case "list":
        case "": {
          if (envFiles.length === 0) {
            ctx.ui.notify("\u{1F4C1} No .env files found in this project.", "info");
            return;
          }
          const list = envFiles
            .map((f) => {
              const keys = Array.from(f.vars.keys()).slice(0, 5).join(", ");
              const more = f.vars.size > 5 ? ` +${f.vars.size - 5} more` : "";
              return `  \u{1F4C4} ${f.name} (${f.vars.size} vars): ${keys}${more}`;
            })
            .join("\n");
          ctx.ui.notify(`\u{1F4C1} Environment Files:\n\n${list}`, "info");
          break;
        }
        case "diff": {
          if (envFiles.length < 2) {
            ctx.ui.notify("Need at least 2 .env files to diff.", "warning");
            return;
          }
          const source = parts[1] ? envFiles.find((f) => f.name.includes(parts[1])) : envFiles[0];
          const target = parts[2] ? envFiles.find((f) => f.name.includes(parts[2])) : envFiles[1];
          if (!source || !target) {
            ctx.ui.notify("Could not find specified env files. Use /env to list them.", "warning");
            return;
          }
          ctx.ui.notify(diffEnvFiles(source, target), "info");
          break;
        }
        case "check": {
          ctx.ui.notify(checkMissingVars(envFiles), "info");
          break;
        }
        case "sync": {
          const sourceName = parts[1];
          const targetName = parts[2];
          if (!sourceName || !targetName) {
            ctx.ui.notify("Usage: /env sync <source> <target>\n\nExample: /env sync .env.development .env.production", "warning");
            return;
          }
          const source = envFiles.find((f) => f.name.includes(sourceName));
          const target = envFiles.find((f) => f.name.includes(targetName));
          if (!source) {
            ctx.ui.notify(`Could not find env file matching '${sourceName}'.`, "warning");
            return;
          }
          if (!target) {
            ctx.ui.notify(`Could not find env file matching '${targetName}'.`, "warning");
            return;
          }
          const { added, content } = syncEnvFiles(source, target);
          if (added.length === 0) {
            ctx.ui.notify("\u2705 Target already has all source variables. Nothing to sync.", "info");
            return;
          }
          writeFileSync(target.path, content, "utf-8");
          ctx.ui.notify(`\u2705 Synced ${added.length} missing variables to ${target.name}:\n  ${added.join(", ")}`, "info");
          break;
        }
        default:
          ctx.ui.notify(
            `\u{1F4C1} Environment Manager\n${'\u2500'.repeat(30)}\n\n` +
              "Commands:\n" +
              "  /env              - List all .env files\n" +
              "  /env diff [a] [b] - Compare two env files\n" +
              "  /env check        - Find missing vars across files\n" +
              "  /env sync <s> <t> - Sync missing vars from source to target",
            "info",
          );
      }
    },
  });
}
