"""Spawn a Cursor or Codex agent to fix deploy/CI failures."""

from __future__ import annotations

import subprocess
import sys
import threading
import time
from pathlib import Path
from typing import List, Optional

CURSOR_CMD = "cursor"
CODEX_CMD = "codex"


def run_fix_agent(
    project_dir: Path,
    prompt: str,
    *,
    executor: str = "cursor",
    model: Optional[str] = None,
    timeout: int = 1800,
    log=None,
) -> int:
    """Run a Cursor/Codex agent with *prompt* and return its exit code.

    The agent output is streamed to *log* (callable) and stdout.
    Returns 124 on timeout.
    """
    if log is None:
        log = lambda msg: print(msg, file=sys.stderr)  # noqa: E731

    cmd = _build_cmd(executor, model, prompt)
    log(f"Spawning {executor} fix agent (timeout={timeout}s)...")

    try:
        proc = subprocess.Popen(
            cmd, cwd=str(project_dir),
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
            text=True, bufsize=1,
        )
    except FileNotFoundError:
        log(f"Fix agent binary not found: {cmd[0]}")
        return 127

    timed_out = False

    def _watchdog():
        nonlocal timed_out
        deadline = time.monotonic() + timeout
        while proc.poll() is None:
            if time.monotonic() > deadline:
                timed_out = True
                log(f"Fix agent timed out ({timeout}s) — killing pid {proc.pid}")
                _kill_tree(proc.pid)
                return
            time.sleep(5)

    watcher = threading.Thread(target=_watchdog, daemon=True)
    watcher.start()

    try:
        for line in proc.stdout:
            log(f"  [fix-agent] {line.rstrip()}")
        proc.wait()
    except KeyboardInterrupt:
        _kill_tree(proc.pid)
        raise
    finally:
        watcher.join(timeout=5)

    if timed_out:
        return 124
    return proc.returncode


def build_ci_fix_prompt(ci_logs: str) -> str:
    """Build a prompt for the CI fix agent."""
    return (
        "The CI/CD pipeline failed. Read the logs, diagnose, and fix.\n\n"
        f"FAILURE LOGS:\n```\n{ci_logs}\n```\n\n"
        "Fix the code/tests/config. Commit your fix:\n"
        "  git add -A && git commit -m \"fix: resolve CI failure\"\n"
        "  Do NOT run git push.\n"
    )


def build_deploy_fix_prompt(
    deploy_output: str,
    *,
    project_id: str,
    port: int,
    server_host: str = "",
    server_user: str = "",
    deploy_domain: str = "",
    ssh_key: str = "",
    app_dir: str = "",
) -> str:
    """Build a prompt for the deploy fix agent."""
    parts = [
        "The deployment to the production server failed. "
        "Diagnose and fix the issue.\n\n"
        f"DEPLOYMENT ERROR OUTPUT:\n```\n{deploy_output}\n```\n\n"
    ]
    if server_host and server_user:
        effective_key = ssh_key or "~/.ssh/id_ed25519"
        effective_app_dir = app_dir or f"~/apps/{project_id}"
        parts.append(
            f"SERVER ACCESS:\n"
            f"  SSH: ssh -i {effective_key} {server_user}@{server_host}\n"
            f"  App dir: {effective_app_dir}\n"
            f"  Port: {port}\n"
        )
        if deploy_domain:
            parts.append(f"  URL: https://{project_id}.{deploy_domain}\n")
        parts.append("\n")
    parts.append(
        "Fix code locally or on the server. If you make local changes, "
        "commit them:\n"
        "  git add -A && git commit -m \"fix: resolve deployment failure\"\n"
        "  Do NOT run git push.\n"
    )
    return "".join(parts)


# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------

def _build_cmd(executor: str, model: Optional[str], prompt: str) -> List[str]:
    if executor == "codex":
        cmd = [CODEX_CMD, "exec", "--dangerously-bypass-approvals-and-sandbox"]
        if model:
            cmd.extend(["--model", model])
        cmd.append(prompt)
    else:
        cmd = [CURSOR_CMD, "agent", "--print", "--yolo", "--approve-mcps"]
        if model:
            cmd.extend(["--model", model])
        cmd.append(prompt)
    return cmd


def _kill_tree(pid: int) -> None:
    """Best-effort SIGTERM + SIGKILL for *pid* and descendants."""
    import os
    import signal

    def _descendants(p: int) -> List[int]:
        try:
            result = subprocess.run(
                ["pgrep", "-P", str(p)],
                capture_output=True, text=True, timeout=5,
            )
            children = [int(x) for x in result.stdout.split() if x.strip()]
        except Exception:
            return []
        desc = list(children)
        for c in children:
            desc.extend(_descendants(c))
        return desc

    pids = _descendants(pid) + [pid]
    for p in pids:
        try:
            os.kill(p, signal.SIGTERM)
        except OSError:
            pass
    time.sleep(2)
    for p in pids:
        try:
            os.kill(p, signal.SIGKILL)
        except OSError:
            pass
