from __future__ import annotations

import asyncio
import json
import shlex
from pathlib import Path
from typing import Any

from .config import BASE_DIR, Settings, expand_env


class SSHCommandRunner:
    def __init__(self, settings: Settings) -> None:
        self.settings = settings
        self.catalog = self._load_catalog()

    def _load_catalog(self) -> dict[str, Any]:
        path = Path(self.settings.allowed_commands_file)
        if not path.is_absolute():
            path = (BASE_DIR / path).resolve()
        data = json.loads(path.read_text(encoding="utf-8"))
        return self._expand_mapping(data)

    def _expand_mapping(self, value: Any) -> Any:
        if isinstance(value, dict):
            return {key: self._expand_mapping(item) for key, item in value.items()}
        if isinstance(value, list):
            return [self._expand_mapping(item) for item in value]
        return expand_env(value)

    def _profile(self, profile_name: str | None = None) -> dict[str, Any]:
        profile_key = profile_name or self.settings.default_ssh_profile
        profiles = self.catalog.get("profiles", {})
        profile = profiles.get(profile_key)
        if not profile:
            raise KeyError(f"Perfil SSH no encontrado: {profile_key}")
        return profile

    def list_allowed_commands(self, profile_name: str | None = None) -> dict[str, Any]:
        profile = self._profile(profile_name)
        commands = profile.get("commands", {})
        return {
            key: {
                "description": value.get("description", ""),
                "risk": value.get("risk", "medium"),
            }
            for key, value in commands.items()
            if isinstance(value, dict)
        }

    def get_command_spec(self, command_key: str, profile_name: str | None = None) -> dict[str, Any]:
        profile = self._profile(profile_name)
        commands = profile.get("commands", {})
        spec = commands.get(command_key)
        if not spec or not isinstance(spec, dict):
            raise KeyError(f"Command key no permitido: {command_key}")
        profile_copy = dict(profile)
        profile_copy["commands"] = commands
        return {
            "profile": profile_copy,
            "command_key": command_key,
            "description": spec.get("description", ""),
            "risk": spec.get("risk", "medium"),
            "command": spec.get("command", ""),
        }

    def render_command(self, command_key: str, profile_name: str | None = None) -> str:
        spec = self.get_command_spec(command_key, profile_name)
        profile = spec["profile"]
        repo_root = shlex.quote(profile.get("repo_root") or self.settings.target_repo_root)
        return spec["command"].format(repo_root=repo_root)

    async def execute(self, command_key: str, profile_name: str | None = None) -> str:
        spec = self.get_command_spec(command_key, profile_name)
        profile = spec["profile"]
        host = profile.get("host") or self.settings.ssh_host
        user = profile.get("user") or self.settings.ssh_user
        port = int(profile.get("port") or self.settings.ssh_port)
        key_path = profile.get("key_path") or self.settings.ssh_key_path
        use_agent = bool(profile.get("use_agent", self.settings.ssh_use_agent))
        if not host or not user:
            raise RuntimeError("Configuración SSH incompleta: host y user son obligatorios")
        if not use_agent and not key_path:
            raise RuntimeError("Configuración SSH incompleta: key_path es obligatorio cuando no usas ssh-agent")

        remote_command = self.render_command(command_key, profile_name)
        ssh_args = [
            "ssh",
            "-p",
            str(port),
            "-o",
            "BatchMode=yes",
            "-o",
            "NumberOfPasswordPrompts=0",
            "-o",
            "ConnectTimeout=12",
            "-o",
            "StrictHostKeyChecking=accept-new",
        ]
        if use_agent:
            ssh_args.extend(["-o", "IdentitiesOnly=no"])
        else:
            ssh_args.extend(["-i", str(key_path)])
        ssh_args.extend([f"{user}@{host}", remote_command])
        process = await asyncio.create_subprocess_exec(
            *ssh_args,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.STDOUT,
        )
        stdout, _ = await process.communicate()
        output = (stdout or b"").decode("utf-8", errors="replace").strip()
        if process.returncode != 0:
            raise RuntimeError(output or f"SSH devolvió código {process.returncode}")
        return output or "Comando ejecutado sin salida"
