from __future__ import annotations

import os
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Mapping

from app.core.config import SERVER_DIR


@dataclass(frozen=True, slots=True)
class HermesCliResult:
    response_text: str
    session_id: str = ""
    command: tuple[str, ...] = ()


@dataclass(frozen=True, slots=True)
class HermesProcessHandle:
    pid: int
    command: tuple[str, ...] = ()
    stdout_path: str = ""
    stderr_path: str = ""


class SystemHermesService:
    def __init__(self) -> None:
        configured_bin = str(os.getenv("HERMES_BIN", "")).strip()
        self.hermes_bin = configured_bin or shutil.which("hermes") or "/usr/local/bin/hermes"

    def is_available(self) -> bool:
        return Path(self.hermes_bin).exists()

    def run_query(
        self,
        query: str,
        *,
        source: str = "tool",
        max_turns: int = 1,
        timeout_seconds: int = 180,
        skills: tuple[str, ...] = (),
        env_overrides: Mapping[str, str] | None = None,
        yolo: bool = False,
    ) -> HermesCliResult:
        if not self.is_available():
            raise RuntimeError(f"未找到系统 Hermes CLI：{self.hermes_bin}")

        command = self._build_command(
            query,
            source=source,
            max_turns=max_turns,
            skills=skills,
            yolo=yolo,
        )
        env = os.environ.copy()
        if env_overrides:
            env.update({str(key): str(value) for key, value in env_overrides.items()})
        completed = subprocess.run(
            command,
            capture_output=True,
            text=True,
            timeout=timeout_seconds,
            check=False,
            env=env,
        )
        if completed.returncode != 0:
            detail = (completed.stderr or completed.stdout or "").strip()
            raise RuntimeError(detail or "Hermes CLI 返回非 0 状态码。")

        return self._parse_output(completed.stdout, command=command)

    def start_query_background(
        self,
        query: str,
        *,
        source: str = "tool",
        max_turns: int = 1,
        skills: tuple[str, ...] = (),
        env_overrides: Mapping[str, str] | None = None,
        log_prefix: str = "hermes",
        yolo: bool = False,
    ) -> HermesProcessHandle:
        if not self.is_available():
            raise RuntimeError(f"未找到系统 Hermes CLI：{self.hermes_bin}")

        command = self._build_command(
            query,
            source=source,
            max_turns=max_turns,
            skills=skills,
            yolo=yolo,
        )
        env = os.environ.copy()
        if env_overrides:
            env.update({str(key): str(value) for key, value in env_overrides.items()})
        log_dir = SERVER_DIR / "logs"
        log_dir.mkdir(parents=True, exist_ok=True)
        safe_prefix = "".join(ch if ch.isalnum() or ch in {"-", "_"} else "-" for ch in log_prefix)
        stdout_path = log_dir / f"{safe_prefix}.out.log"
        stderr_path = log_dir / f"{safe_prefix}.err.log"
        stdout_file = stdout_path.open("ab")
        stderr_file = stderr_path.open("ab")
        process = subprocess.Popen(
            command,
            stdout=stdout_file,
            stderr=stderr_file,
            env=env,
            start_new_session=True,
        )
        stdout_file.close()
        stderr_file.close()
        return HermesProcessHandle(
            pid=process.pid,
            command=command,
            stdout_path=str(stdout_path),
            stderr_path=str(stderr_path),
        )

    def _build_command(
        self,
        query: str,
        *,
        source: str,
        max_turns: int,
        skills: tuple[str, ...],
        yolo: bool,
    ) -> tuple[str, ...]:
        command_parts = [
            self.hermes_bin,
            "chat",
            "-Q",
            "--source",
            source,
            "--max-turns",
            str(max_turns),
        ]
        for skill in skills:
            normalized_skill = str(skill or "").strip()
            if normalized_skill:
                command_parts.extend(["--skills", normalized_skill])
        if yolo:
            command_parts.append("--yolo")
        command_parts.extend(["-q", query])
        return tuple(command_parts)

    @staticmethod
    def _parse_output(stdout: str, *, command: tuple[str, ...]) -> HermesCliResult:
        lines = [line.rstrip() for line in str(stdout or "").splitlines()]
        session_id = ""
        response_lines: list[str] = []

        for line in lines:
            if line.startswith("session_id:"):
                session_id = line.split(":", 1)[1].strip()
                continue
            response_lines.append(line)

        response_text = "\n".join(line for line in response_lines if line.strip()).strip()
        return HermesCliResult(
            response_text=response_text,
            session_id=session_id,
            command=command,
        )
