import fs from "fs";
import path from "path";
import { exec, execFile } from "child_process";
import { promisify } from "util";
import { getConfigDir } from "../config";
import type { IPty } from "node-pty";
import { AgentRunner } from "./AgentRunner";
import { isWindows, findGitBash, loadNodePty } from "./runtime";

const execFilePromise = promisify(execFile);

function getFilesForAider(dir: string): string {
  const maxFiles = 150;
  const files: string[] = [];
  
  function walk(current: string) {
    if (files.length >= maxFiles) return;
    try {
      const entries = fs.readdirSync(current, { withFileTypes: true });
      for (const entry of entries) {
        const name = entry.name;
        if (
          name.startsWith(".") ||
          name === "node_modules" ||
          name === "out" ||
          name === ".next" ||
          name === "bin" ||
          name === "obj" ||
          name === "dist" ||
          name === "build"
        ) {
          continue;
        }
        const fullPath = path.join(current, name);
        if (entry.isDirectory()) {
          walk(fullPath);
        } else {
          const relPath = path.relative(dir, fullPath);
          files.push(relPath);
          if (files.length >= maxFiles) return;
        }
      }
    } catch (err) {
      console.error("Error reading dir for Aider files list:", err);
    }
  }

  walk(dir);
  return files.map(f => `"${f.replace(/"/g, '\\"')}"`).join(" ");
}

export class AiderRunner extends AgentRunner {
  id: string;
  name: string;
  type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell';
  status: 'idle' | 'running' | 'stopped' = 'idle';
  createdAt: string;
  logs: string[] = [];

  private ptyProcess: IPty | null = null;
  private ptyModule: typeof import("node-pty") | null = null;

  constructor(id: string, type: 'aider' | 'aider-docker', name?: string) {
    super();
    this.id = id;
    this.type = type;
    this.name = name || (type === 'aider-docker' ? `Aider Docker ${id.substring(0, 8)}` : `Aider Session ${id.substring(0, 8)}`);
    this.createdAt = new Date().toISOString();
  }

  async start(resume?: boolean): Promise<void> {
    if (this.status === 'running') return;

    this.status = 'running';
    this.appendLog("\x1b[1;35m==================================================\x1b[0m\r\n");
    this.appendLog(`\x1b[1;36m       NEURALLOOM AIDER AGENT ROUTE (${this.type.toUpperCase()})    \x1b[0m\r\n`);
    this.appendLog("\x1b[1;35m==================================================\x1b[0m\r\n");

    const useCmd = isWindows();
    const shellPath = useCmd ? (process.env.COMSPEC || "cmd.exe") : findGitBash();
    if (!shellPath) {
      this.status = 'stopped';
      this.appendLog("\x1b[1;31m[System Error] Shell not found. Cannot spawn terminal.\x1b[0m\r\n");
      return;
    }
    const shellArgs = useCmd ? ["/c"] : ["-c"];

    try {
      this.ptyModule = loadNodePty();

      // Load Aider configuration
      let aiderConfig = {
        openaiApiKey: "",
        anthropicApiKey: "",
        geminiApiKey: "",
        model: "claude-3-5-sonnet",
        extraFlags: "",
        loadAllFiles: true,
      };

      const aiderConfigPath = path.join(getConfigDir(), "aider.json");
      if (fs.existsSync(aiderConfigPath)) {
        try {
          aiderConfig = JSON.parse(fs.readFileSync(aiderConfigPath, "utf8"));
        } catch (err) {
          console.error("Failed to parse Aider configuration:", err);
        }
      }
      // Record the model so fallback cost estimates use the right rate.
      this.model = aiderConfig.model;

      // Setup process environment with injected keys
      const customEnv = {
        ...process.env,
        TERM: "xterm-256color",
        COLORTERM: "truecolor",
        NEURAL_LOOM: "true",
      } as Record<string, string>;

      if (aiderConfig.openaiApiKey) {
        customEnv.OPENAI_API_KEY = aiderConfig.openaiApiKey;
      }
      if (aiderConfig.anthropicApiKey) {
        customEnv.ANTHROPIC_API_KEY = aiderConfig.anthropicApiKey;
      }
      if (aiderConfig.geminiApiKey) {
        customEnv.GEMINI_API_KEY = aiderConfig.geminiApiKey;
      }

      const targetCwd = (this.workspaceRoot && fs.existsSync(this.workspaceRoot)) 
        ? this.workspaceRoot 
        : process.env.ORIGINAL_CWD || process.cwd();

      const modelFlag = aiderConfig.model ? `--model ${aiderConfig.model}` : "";
      const extraFlags = aiderConfig.extraFlags || "";
      
      let loadAllFilesFlag = "";
      if (aiderConfig.loadAllFiles !== false) {
        if (extraFlags.includes("--no-git")) {
          loadAllFilesFlag = getFilesForAider(targetCwd);
        } else {
          loadAllFilesFlag = ".";
        }
      }

      if (this.type === "aider-docker") {
        this.appendLog("[System] Verifying Docker environment accessibility...\r\n");

        // Check if docker executable exists by running `docker --version`
        const hasDocker = await new Promise<boolean>((resolve) => {
          exec("docker --version", { env: process.env }, (error: Error | null, stdout: string) => {
            if (!error && stdout.toLowerCase().includes("version")) {
              resolve(true);
            } else {
              resolve(false);
            }
          });
        });

        if (!hasDocker) {
          this.status = 'stopped';
          this.appendLog("\x1b[1;31m[Dependency Warning] Docker command not recognized in local PATH.\x1b[0m\r\n");
          this.appendLog("\x1b[1;33mPlease install Docker Desktop and ensure the daemon is running.\x1b[0m\r\n");
          return;
        }

        this.appendLog("[System] Spawning Aider container environment...\r\n");
        const envFlags: string[] = [];
        if (aiderConfig.openaiApiKey) envFlags.push("-e OPENAI_API_KEY");
        if (aiderConfig.anthropicApiKey) envFlags.push("-e ANTHROPIC_API_KEY");
        if (aiderConfig.geminiApiKey) envFlags.push("-e GEMINI_API_KEY");
        envFlags.push("-e NEURAL_LOOM=true");
        const envFlagsStr = envFlags.join(" ");

        // Volume mounts for primary workspace and each scoped directory
        const mounts: string[] = [`-v "${targetCwd}:/workspace"`];
        if (this.scopedDirs && Array.isArray(this.scopedDirs)) {
          this.scopedDirs.forEach((dir) => {
            if (fs.existsSync(dir)) {
              const folderName = path.basename(dir).replace(/[^a-zA-Z0-9-_]/g, "_");
              mounts.push(`-v "${dir}:/scoped/${folderName}"`);
            }
          });
        }
        const mountsStr = mounts.join(" ");

        const dockerCmd = `docker run -it --name neural-loom-session-${this.id} --rm ${mountsStr} -w /workspace ${envFlagsStr} paulgauthier/aider ${modelFlag} ${extraFlags} --no-fancy-input ${loadAllFilesFlag}`.trim();

        this.ptyProcess = this.ptyModule!.spawn(shellPath, [...shellArgs, dockerCmd], {
          name: "xterm-256color",
          cols: 120,
          rows: 30,
          cwd: targetCwd,
          env: customEnv,
        });
      } else {
        this.appendLog("[System] Verifying 'aider' command availability in local PATH...\r\n");

        // Check if aider executable exists by running `aider --version`
        const hasAider = await new Promise<boolean>((resolve) => {
          exec("aider --version", { env: process.env }, (error: Error | null, stdout: string) => {
            if (!error && stdout.toLowerCase().includes("aider")) {
              resolve(true);
            } else {
              resolve(false);
            }
          });
        });

        if (!hasAider) {
          this.status = 'stopped';
          this.appendLog("\x1b[1;31m[Dependency Warning] 'aider' executable not found on local PATH.\x1b[0m\r\n");
          this.appendLog("\x1b[1;33mPlease install Aider globally via pip:\r\n  pip install aider-chat\x1b[0m\r\n");
          return;
        }

        this.appendLog("[System] Spawning local Aider CLI...\r\n");
        const localCmd = `aider ${modelFlag} ${extraFlags} --no-fancy-input ${loadAllFilesFlag}`.trim();

        this.ptyProcess = this.ptyModule!.spawn(shellPath, [...shellArgs, localCmd], {
          name: "xterm-256color",
          cols: 120,
          rows: 30,
          cwd: targetCwd,
          env: customEnv,
        });
      }

      this.ptyProcess.onData((data: string) => {
        this.appendLog(data);
      });

      this.ptyProcess.onExit(({ exitCode, signal }: { exitCode: number; signal?: number }) => {
        this.status = 'stopped';
        this.appendLog(`\r\n\x1b[1;31m[System] Aider process exited (code=${exitCode}, signal=${signal}).\x1b[0m\r\n`);
        this.ptyProcess = null;
      });

    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : "Unknown error";
      this.status = 'stopped';
      this.appendLog(`\x1b[1;31m[System Error] Failed to launch Aider: ${errorMessage}\x1b[0m\r\n`);
    }
  }

  async stop(): Promise<void> {
    this.status = 'stopped';
    if (this.ptyProcess) {
      try {
        this.ptyProcess.kill();
      } catch (err) {
        console.error("Error killing Aider PTY process:", err);
      }
      this.ptyProcess = null;
    }
    // For containerised Aider, ensure the container is torn down (the PTY hangup
    // alone may not trigger the run's --rm cleanup).
    if (this.type === "aider-docker") {
      try {
        await execFilePromise("docker", ["stop", `neural-loom-session-${this.id}`]);
      } catch {
        // Container already gone or docker unavailable.
      }
    }
    this.appendLog("\r\n\x1b[1;31m[System] Aider session terminated.\x1b[0m\r\n");
  }

  async sendInput(text: string, isRaw = false): Promise<void> {
    if (this.status !== 'running' || !this.ptyProcess) {
      this.appendLog(`\r\n\x1b[1;31m[Error] Cannot write: Aider process is ${this.status}.\x1b[0m\r\n`);
      return;
    }

    this.agentState = 'thinking';
    await this.writeToPty(this.ptyProcess, isRaw ? text : text + "\r");
  }

  override resize(cols: number, rows: number): void {
    if (this.ptyProcess) {
      try {
        this.ptyProcess.resize(cols, rows);
      } catch (err) {
        console.error("[AiderRunner] Failed to resize PTY:", err);
      }
    }
  }

  override getPid(): number | undefined {
    return this.ptyProcess?.pid;
  }
}

