import { spawn, type Subprocess } from "bun";
import type { PiExecutionResult, PiEvent } from "./types.js";
import { logger } from "../logger.js";
import type { Config } from "../config.js";

const PI_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes max per task

export class PiExecutor {
  private config: Config;
  private runningProcess: Subprocess | null = null;

  constructor(config: Config) {
    this.config = config;
  }

  async execute(task: string, context?: string, timeoutMs?: number, cwd?: string): Promise<PiExecutionResult> {
    const startTime = Date.now();
    let output = "";
    let toolCalls = 0;
    let apiError = "";

    const fullPrompt = context ? `${context}\n\nTask: ${task}` : task;

    const args = [
      this.config.piBin,
      "--mode", "json",
      "-p",
      "--no-extensions",
      "--model", this.config.piModel,
      "--tools", "bash,read,write,edit,grep,find,ls",
      "--thinking", "off",
      "--append-system-prompt", this.buildSystemPrompt(),
      fullPrompt,
    ];

    const workingDir = cwd || this.config.targetProjectPath;

    logger.info("pi-executor", `Executing task: ${task.substring(0, 100)}...`, {
      model: this.config.piModel,
      cwd: workingDir,
    });

    try {
      // Don't pass ANTHROPIC_API_KEY — Pi uses OAuth from ~/.pi/agent/auth.json
      // Passing the API key overrides OAuth and may hit a depleted prepaid balance
      const { ANTHROPIC_API_KEY: _removed, ...cleanEnv } = process.env;

      const proc = spawn({
        cmd: args,
        cwd: workingDir,
        stdout: "pipe",
        stderr: "pipe",
        env: cleanEnv,
      });

      this.runningProcess = proc;

      // Set timeout
      const timeout = setTimeout(() => {
        logger.warn("pi-executor", "Task timed out, killing process");
        proc.kill();
      }, timeoutMs || PI_TIMEOUT_MS);

      // Read stdout line by line
      const reader = proc.stdout.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";

        for (const line of lines) {
          if (!line.trim()) continue;
          try {
            const event: PiEvent = JSON.parse(line);
            
            if (event.type === "message_update" || event.type === "message_end") {
              output += event.content || "";
            }
            if (event.type === "tool_execution_start") {
              toolCalls++;
            }
            // Detect API errors in the event stream (Pi exits 0 even on API errors)
            const msg = event.message || event;
            if (msg.errorMessage) {
              apiError = msg.errorMessage;
              logger.error("pi-executor", `API error in event stream: ${apiError}`);
            }
            if (msg.stopReason === "error" && !apiError) {
              apiError = "Pi agent stopped with error (no details)";
            }
          } catch {
            // Not JSON, append as raw output
            output += line + "\n";
          }
        }
      }

      clearTimeout(timeout);
      const exitCode = await proc.exited;
      this.runningProcess = null;

      // Read all stderr
      let stderr = "";
      const stderrReader = proc.stderr.getReader();
      while (true) {
        const { done: stderrDone, value: stderrValue } = await stderrReader.read();
        if (stderrDone) break;
        stderr += decoder.decode(stderrValue, { stream: true });
      }

      if (exitCode !== 0) {
        return {
          success: false,
          output: output || stderr,
          toolCalls,
          durationMs: Date.now() - startTime,
          error: `Pi exited with code ${exitCode}: ${stderr}`,
        };
      }

      // Treat API errors as failures even if exit code was 0
      if (apiError) {
        return {
          success: false,
          output: output.substring(0, 10000),
          toolCalls,
          durationMs: Date.now() - startTime,
          error: apiError,
        };
      }

      return {
        success: true,
        output: output.substring(0, 10000), // Truncate output
        toolCalls,
        durationMs: Date.now() - startTime,
      };
    } catch (error: any) {
      this.runningProcess = null;
      return {
        success: false,
        output: "",
        toolCalls,
        durationMs: Date.now() - startTime,
        error: error.message,
      };
    }
  }

  // Execute with the full agent-team (dispatch model)
  async executeWithAgentTeam(task: string, context?: string): Promise<PiExecutionResult> {
    const startTime = Date.now();
    let output = "";
    let toolCalls = 0;

    const fullPrompt = context ? `${context}\n\nTask: ${task}` : task;

    const args = [
      this.config.piBin,
      "--mode", "json",
      "-p",
      "-e", `${this.config.extensionsPath}/agent-team.ts`,
      "-e", `${this.config.extensionsPath}/theme-cycler.ts`,
      "--model", this.config.piModel,
      "--thinking", "off",
      fullPrompt,
    ];

    logger.info("pi-executor", `Executing with agent-team: ${task.substring(0, 100)}...`);

    try {
      // Don't pass ANTHROPIC_API_KEY — Pi uses OAuth from ~/.pi/agent/auth.json
      const { ANTHROPIC_API_KEY: _removed, ...cleanEnv } = process.env;

      const proc = spawn({
        cmd: args,
        cwd: this.config.targetProjectPath,
        stdout: "pipe",
        stderr: "pipe",
        env: cleanEnv,
      });

      this.runningProcess = proc;

      const timeout = setTimeout(() => {
        proc.kill();
      }, PI_TIMEOUT_MS);

      const reader = proc.stdout.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";

        for (const line of lines) {
          if (!line.trim()) continue;
          try {
            const event: PiEvent = JSON.parse(line);
            if (event.type === "message_update" || event.type === "message_end") {
              output += event.content || "";
            }
            if (event.type === "tool_execution_start") {
              toolCalls++;
            }
          } catch {
            output += line + "\n";
          }
        }
      }

      clearTimeout(timeout);
      const exitCode = await proc.exited;
      this.runningProcess = null;

      return {
        success: exitCode === 0,
        output: output.substring(0, 10000),
        toolCalls,
        durationMs: Date.now() - startTime,
        error: exitCode !== 0 ? `Exited with code ${exitCode}` : undefined,
      };
    } catch (error: any) {
      this.runningProcess = null;
      return {
        success: false,
        output: "",
        toolCalls,
        durationMs: Date.now() - startTime,
        error: error.message,
      };
    }
  }

  abort() {
    if (this.runningProcess) {
      this.runningProcess.kill();
      this.runningProcess = null;
    }
  }

  private buildSystemPrompt(): string {
    return `You are Pi Worker — an autonomous DevOps and development agent running inside cr-server-new.
You work on the CharityRight ecosystem projects. You have full bash access.

RULES:
- Always read relevant files before making changes
- Make small, focused changes
- Test your changes when possible (run linters, type checks)
- Never modify .env files or credentials
- Never run destructive commands (rm -rf, DROP DATABASE, etc)
- Commit your changes with clear commit messages
- If a task is unclear, document what you understood and what you did

AVAILABLE PROJECTS:
- The current working directory contains the project code
- Use git to track all changes
- Write clear, maintainable code`;
  }
}
