"""Worker task executors."""

from __future__ import annotations

from dataclasses import dataclass
import os
import shutil
import subprocess

from a2a_agent.config import AgentConfig


OPENCODE_TIMEOUT_SECONDS = 120
CURSOR_TIMEOUT_SECONDS = 180
GROK_TIMEOUT_SECONDS = 180


@dataclass(frozen=True)
class ExecutorResult:
    ok: bool
    text: str


def selected_executor(config: AgentConfig) -> str:
    return os.environ.get("A2A_EXECUTOR", config.executor).strip().lower() or "echo"


def execute_text(text: str, config: AgentConfig) -> ExecutorResult:
    executor = selected_executor(config)
    if executor == "echo":
        return ExecutorResult(ok=True, text=text)
    if executor == "opencode":
        return _run_opencode(text)
    if executor == "cursor":
        return _run_cursor(text)
    if executor == "grok":
        return _run_grok(text)
    return ExecutorResult(ok=False, text=f"unknown executor: {executor}")


def _build_command(program: str, args: list[str]) -> list[str] | None:
    """PATH에서 program 실제 경로를 찾아 실행 커맨드를 만든다.

    윈도우 npm/설치본은 program.cmd 같은 셸 스크립트라 bare 이름으로는
    파이썬 subprocess가 실행 파일을 못 찾는다. shutil.which는 PATHEXT를 존중해
    .cmd를 찾아주고, .cmd/.bat는 cmd.exe를 통해 실행해야 한다.
    """
    executable = shutil.which(program)
    if executable is None:
        return None
    if os.name == "nt" and executable.lower().endswith((".cmd", ".bat")):
        return ["cmd", "/c", executable, *args]
    return [executable, *args]


def _run_external(
    program: str, args: list[str], timeout: int, label: str
) -> ExecutorResult:
    command = _build_command(program, args)
    if command is None:
        return ExecutorResult(
            ok=False,
            text=(
                f"{program}을(를) PATH에서 찾지 못했습니다. {program} 설치 후 "
                "새 터미널에서 워커를 다시 실행하세요."
            ),
        )
    try:
        completed = subprocess.run(
            command,
            check=False,
            capture_output=True,
            text=True,
            # 윈도우 한국어 로케일은 기본 코덱이 cp949라 UTF-8 출력을 못 읽고 깨진다.
            # 외부 AI CLI 출력은 UTF-8이므로 명시적으로 UTF-8로 디코딩한다.
            encoding="utf-8",
            errors="replace",
            timeout=timeout,
        )
    except FileNotFoundError:
        return ExecutorResult(ok=False, text=f"{program} executable was not found")
    except subprocess.TimeoutExpired:
        return ExecutorResult(
            ok=False,
            text=f"{label} timed out after {timeout} seconds",
        )

    stdout = completed.stdout.strip()
    stderr = completed.stderr.strip()
    if completed.returncode == 0:
        return ExecutorResult(ok=True, text=stdout or f"({label} returned no output)")

    detail = stderr or stdout or "no output"
    return ExecutorResult(
        ok=False,
        text=f"{label} failed with exit code {completed.returncode}: {detail}",
    )


def _run_opencode(prompt: str) -> ExecutorResult:
    return _run_external(
        "opencode",
        ["run", "-m", "opencode/big-pickle", prompt],
        OPENCODE_TIMEOUT_SECONDS,
        "opencode",
    )


def _run_cursor(prompt: str) -> ExecutorResult:
    return _run_external(
        "cursor-agent",
        ["-p", "--yolo", "--trust", prompt],
        CURSOR_TIMEOUT_SECONDS,
        "cursor-agent",
    )


def _run_grok(prompt: str) -> ExecutorResult:
    # 일회용 호출: -c(대화 지속) 없이. --always-approve로 자동 승인.
    return _run_external(
        "grok",
        ["--always-approve", "-p", prompt],
        GROK_TIMEOUT_SECONDS,
        "grok",
    )
