"""engine_v2/cli_runner.py — asyncio.subprocess 기반 멀티엔진 CLI 래퍼."""

from __future__ import annotations

import asyncio
import os
import re
from dataclasses import dataclass

from engine_v2.engine_result import EngineRole

_CODEX_MODEL_FALLBACK: dict[str, str] = {
    "gpt-5.2-codex": "gpt-5.1-codex-mini",
    "gpt-5.1-codex": "gpt-5.1-codex-mini",
}


@dataclass
class CLIResult:
    """CLI 실행 결과."""

    stdout: str
    stderr: str
    returncode: int
    engine: EngineRole
    fallback_used: bool = False
    timed_out: bool = False


class CLIRunner:
    """멀티엔진 CLI 래퍼."""

    @staticmethod
    async def run_claude(
        prompt: str,
        *,
        timeout: int = 60,
        model: str = "sonnet",
        code_analysis: bool = False,
    ) -> CLIResult:
        """Claude CLI 호출."""
        cmd = [
            "/home/jay/.local/bin/claude",
            "-p",
            "--output-format",
            "text",
            "--model",
            model,
        ]
        if code_analysis:
            cmd.extend(["--allowedTools", "Read,Grep,Glob"])

        cwd = "/home/jay/workspace" if code_analysis else "/tmp"
        env = dict(os.environ)
        env.pop("CLAUDECODE", None)

        return await CLIRunner._exec(
            cmd=cmd,
            prompt=prompt,
            timeout=timeout,
            engine="claude",
            cwd=cwd,
            env=env,
        )

    @staticmethod
    async def run_codex(
        prompt: str,
        *,
        timeout: int = 60,
        model: str = "gpt-5.1-codex-mini",
    ) -> CLIResult:
        """Codex CLI 호출. free plan 제한 시 fallback 적용."""
        actual_model = _CODEX_MODEL_FALLBACK.get(model, model)
        fallback_used = actual_model != model

        cmd = [
            "codex",
            "exec",
            "--skip-git-repo-check",
            "-c",
            f'model="{actual_model}"',
            "-",
        ]

        result = await CLIRunner._exec(
            cmd=cmd,
            prompt=prompt,
            timeout=timeout,
            engine="codex",
            cwd="/home/jay/workspace",
        )
        result.fallback_used = fallback_used
        return result

    @staticmethod
    async def run_gemini(
        prompt: str,
        *,
        timeout: int = 60,
        model: str = "gemini-2.5-pro",
        output_format: str = "text",
    ) -> CLIResult:
        """Gemini CLI 호출."""
        cmd = [
            "gemini",
            "-m",
            model,
            "--output-format",
            output_format,
        ]

        return await CLIRunner._exec(
            cmd=cmd,
            prompt=prompt,
            timeout=timeout,
            engine="gemini",
            cwd="/home/jay/workspace",
        )

    @staticmethod
    async def check_gemini_version(min_version: str = "0.31.0") -> bool:
        """Gemini CLI 버전 체크. min_version 이상이면 True."""
        try:
            proc = await asyncio.create_subprocess_exec(
                "gemini",
                "--version",
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=10)
            version_str = stdout.decode().strip()
            match = re.search(r"(\d+\.\d+\.\d+)", version_str)
            if not match:
                return False
            parts = [int(x) for x in match.group(1).split(".")]
            min_parts = [int(x) for x in min_version.split(".")]
            return parts >= min_parts
        except (FileNotFoundError, asyncio.TimeoutError):
            return False

    @staticmethod
    async def _exec(
        *,
        cmd: list[str],
        prompt: str,
        timeout: int,
        engine: EngineRole,
        cwd: str = "/tmp",
        env: dict[str, str] | None = None,
    ) -> CLIResult:
        """공통 subprocess 실행."""
        try:
            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdin=asyncio.subprocess.PIPE,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
                cwd=cwd,
                env=env,
            )
            try:
                stdout_bytes, stderr_bytes = await asyncio.wait_for(
                    proc.communicate(input=prompt.encode("utf-8")),
                    timeout=timeout,
                )
            except asyncio.TimeoutError:
                try:
                    proc.kill()
                except ProcessLookupError:
                    pass
                return CLIResult(
                    stdout="",
                    stderr=f"Timeout after {timeout}s",
                    returncode=-1,
                    engine=engine,
                    timed_out=True,
                )

            return CLIResult(
                stdout=stdout_bytes.decode("utf-8", errors="replace").strip(),
                stderr=stderr_bytes.decode("utf-8", errors="replace"),
                returncode=proc.returncode or 0,
                engine=engine,
            )
        except FileNotFoundError:
            return CLIResult(
                stdout="",
                stderr=f"{cmd[0]} not found in PATH",
                returncode=-1,
                engine=engine,
            )
        except Exception as e:  # noqa: BLE001
            return CLIResult(
                stdout="",
                stderr=str(e),
                returncode=-1,
                engine=engine,
            )
