"""Kiro CLI subprocess lifecycle."""

from __future__ import annotations

import logging
import subprocess
import sys
import threading
from pathlib import Path
from typing import Any

from coral.agent.exit_classifier import classify_by_uptime
from coral.agent.process import open_agent_stderr_for_log_dir
from coral.agent.runtime import (
    AgentHandle,
    apply_run_as_user,
    apply_sandbox,
    apply_sandbox_env,
    write_coral_log_entry,
)
from coral.sandbox.protocol import AgentSandboxSpec
from coral.workspace.repo import _clean_env

logger = logging.getLogger(__name__)


class KiroRuntime:
    """Spawn and manage Kiro CLI agent subprocesses."""

    @property
    def instruction_filename(self) -> str:
        return "KIRO.md"

    @property
    def shared_dir_name(self) -> str:
        return ".kiro"

    def extract_session_id(self, log_path: Path) -> str | None:
        return None  # Kiro doesn't expose session IDs in the same way

    def classify_exit(
        self,
        log_path: Path,
        exit_code: int | None,
        uptime_seconds: float | None,
        min_clean_runtime_seconds: int = 60,
    ) -> str:
        """Classify a Kiro subprocess exit using the uptime fallback.

        Kiro emits plain text without a stable terminal marker, so we treat
        an `exit_code==0` as clean only when the agent ran for at least
        `min_clean_runtime_seconds`; shorter exits count as crashes.
        """
        return classify_by_uptime(exit_code, uptime_seconds, min_clean_runtime_seconds)

    def start(
        self,
        worktree_path: Path,
        coral_md_path: Path,
        model: str = "default",
        runtime_options: dict[str, Any] | None = None,
        max_turns: int = 0,
        log_dir: Path | None = None,
        verbose: bool = False,
        resume_session_id: str | None = None,
        prompt: str | None = None,
        prompt_source: str | None = None,
        task_name: str | None = None,
        task_description: str | None = None,
        # Kiro does not currently route through the LiteLLM gateway, but
        # accept these kwargs so the manager can call all four runtimes
        # through a single signature without runtime-specific dispatch.
        gateway_url: str | None = None,
        gateway_api_key: str | None = None,
        run_as_user: dict[str, Any] | None = None,
        sandbox: AgentSandboxSpec | None = None,
    ) -> AgentHandle:
        agent_id_file = worktree_path / ".coral_agent_id"
        agent_id = (
            agent_id_file.read_text(encoding="utf-8").strip()
            if agent_id_file.exists()
            else "unknown"
        )

        if log_dir is None:
            log_dir = worktree_path / ".kiro" / "logs"
        log_dir.mkdir(parents=True, exist_ok=True)

        log_idx = len(list(log_dir.glob(f"{agent_id}*.log")))
        log_path = log_dir / f"{agent_id}.{log_idx}.log"

        if prompt is None:
            prompt = "Begin working on your task and iterating on the seed solution. There is no user in the loop — make decisions, run evals, accumulate knowledge, and iterate without waiting for input."

        cmd = [
            "kiro-cli",
            "chat",
            prompt,
            "--no-interactive",
            "-a",  # trust all tools
        ]

        if model and model != "default":
            cmd.extend(["--model", model])

        cmd = apply_sandbox(cmd, sandbox)

        logger.info(f"Starting Kiro agent {agent_id} in {worktree_path}")
        logger.info(f"Command: {' '.join(cmd)}")

        agent_env = _clean_env()

        apply_sandbox_env(agent_env, sandbox)

        # OS-user isolation: drop the agent subprocess to the unprivileged
        # user (no-op when run_as_user is None). Sets HOME so the CLI finds
        # its creds in the agent's home; returns Popen user=/group= kwargs.
        user_kwargs = apply_run_as_user(agent_env, run_as_user)

        log_file = open(log_path, "w", buffering=1, encoding="utf-8", errors="replace")

        # Per-agent stderr capture under public/diagnostics/<agent_id>/agent.err.
        err_path: Path | None = None
        err_file: Any = None
        stderr_target: Any = subprocess.STDOUT
        opened = open_agent_stderr_for_log_dir(log_dir, agent_id)
        if opened is not None:
            err_path, err_file = opened
            stderr_target = err_file

        write_coral_log_entry(
            log_file,
            prompt=prompt,
            source=prompt_source or "start",
            agent_id=agent_id,
            task_name=task_name,
            task_description=task_description,
        )

        if verbose:
            process = subprocess.Popen(
                cmd,
                cwd=str(worktree_path),
                stdout=subprocess.PIPE,
                stderr=stderr_target,
                start_new_session=True,
                env=agent_env,
                **user_kwargs,
            )

            def _tee_output(proc, log_f, agent):
                try:
                    if proc.stdout is None:
                        return
                    for line in iter(proc.stdout.readline, b""):
                        decoded = line.decode("utf-8", errors="replace")
                        sys.stdout.write(f"[{agent}] {decoded}")
                        sys.stdout.flush()
                        log_f.write(decoded)
                        log_f.flush()
                except Exception as e:
                    logger.error(f"Tee thread error: {e}")
                finally:
                    log_f.close()

            tee_thread = threading.Thread(
                target=_tee_output, args=(process, log_file, agent_id), daemon=True
            )
            tee_thread.start()
            log_file_ref = None
        else:
            process = subprocess.Popen(
                cmd,
                cwd=str(worktree_path),
                stdout=log_file,
                stderr=stderr_target,
                start_new_session=True,
                env=agent_env,
                **user_kwargs,
            )
            log_file_ref = log_file

        logger.info(f"Kiro agent {agent_id} started with PID {process.pid}")

        return AgentHandle(
            agent_id=agent_id,
            process=process,
            worktree_path=worktree_path,
            log_path=log_path,
            _log_file=log_file_ref,
            err_file=err_file,
            err_path=err_path,
        )
