from __future__ import annotations

import shlex
import subprocess
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml

from armorer import docker_runner


def load_compose_services(install_dir: Path) -> Dict[str, Any]:
    for name in ("docker-compose.yml", "docker-compose.yaml"):
        compose_path = install_dir / name
        if not compose_path.exists():
            continue
        try:
            data = yaml.safe_load(compose_path.read_text()) or {}
        except Exception:
            return {}
        services = data.get("services", {})
        return services if isinstance(services, dict) else {}
    return {}


def is_status_like_command(run_args: List[str]) -> bool:
    tokens = [str(arg).strip().lower() for arg in (run_args or []) if str(arg).strip()]
    if not tokens:
        return False
    if tokens[0] in {"status", "doctor", "health", "probe"}:
        return True
    if len(tokens) >= 2 and tokens[0] in {"gateway", "channels"} and tokens[1] in {"status", "probe"}:
        return True
    return False


def is_setup_like_command(run_args: List[str]) -> bool:
    tokens = [str(arg).strip().lower() for arg in (run_args or []) if str(arg).strip()]
    if not tokens:
        return False
    first = tokens[0]
    if first in {"configure", "config", "setup", "onboard", "init"}:
        return True
    if len(tokens) >= 2 and first in {"config", "channels", "models", "devices", "pairing"}:
        if tokens[1] in {"configure", "setup", "login", "bind", "connect", "init"}:
            return True
    return False


def compose_service_is_running(install_dir: Path, service_name: str) -> bool:
    result = docker_runner.compose_ps(
        cwd=install_dir,
        args=["--status", "running", "-q", service_name],
    )
    return bool(result.stdout.strip())


def resolve_exec_delegate_service(
    install_dir: Path,
    services_data: Dict[str, Any],
    service_name: str,
    run_args: List[str],
    *,
    allow_any_command: bool = False,
) -> Optional[Dict[str, Any]]:
    if not allow_any_command and not is_status_like_command(run_args):
        return None
    svc_cfg = services_data.get(service_name, {})
    if not isinstance(svc_cfg, dict):
        return None
    network_mode = str(svc_cfg.get("network_mode", "") or "").strip()
    if not network_mode.lower().startswith("service:"):
        return None
    delegate = network_mode.split(":", 1)[1].strip()
    if not delegate or delegate == service_name:
        return None
    entrypoint = svc_cfg.get("entrypoint") or []
    if isinstance(entrypoint, str):
        entrypoint = shlex.split(entrypoint)
    if not isinstance(entrypoint, list) or not entrypoint:
        return None
    if not compose_service_is_running(install_dir, delegate):
        return None
    return {
        "service": delegate,
        "command": [str(token) for token in entrypoint] + list(run_args),
    }


def resolve_setup_delegate_run(
    services_data: Dict[str, Any],
    service_name: str,
    run_args: List[str],
) -> Optional[Dict[str, Any]]:
    if not is_setup_like_command(run_args):
        return None
    svc_cfg = services_data.get(service_name, {})
    if not isinstance(svc_cfg, dict):
        return None
    network_mode = str(svc_cfg.get("network_mode", "") or "").strip()
    if not network_mode.lower().startswith("service:"):
        return None
    delegate = network_mode.split(":", 1)[1].strip()
    if not delegate or delegate == service_name:
        return None
    delegate_cfg = services_data.get(delegate, {})
    if not isinstance(delegate_cfg, dict):
        return None
    svc_image = str(svc_cfg.get("image", "") or "").strip()
    delegate_image = str(delegate_cfg.get("image", "") or "").strip()
    if svc_image and delegate_image and svc_image != delegate_image:
        return None
    entrypoint = svc_cfg.get("entrypoint") or []
    if isinstance(entrypoint, str):
        entrypoint = shlex.split(entrypoint)
    if not isinstance(entrypoint, list) or not entrypoint:
        return None
    return {
        "service": delegate,
        "entrypoint": [str(token) for token in entrypoint],
        "command": list(run_args),
    }


def build_compose_service_command(
    install_dir: Path,
    services_data: Dict[str, Any],
    service_name: str,
    run_args: List[str],
    *,
    use_service_ports: bool = True,
    prefer_exec_delegate: bool = True,
    allow_any_exec_delegate: bool = False,
    no_deps_for_setup: bool = True,
) -> Dict[str, Any]:
    setup_delegate = resolve_setup_delegate_run(services_data, service_name, run_args)
    if setup_delegate:
        return {
            "delegate": None,
            "cmd": [
                "docker",
                "compose",
                "run",
                "--rm",
                "--no-deps",
                "--entrypoint",
                shlex.join(setup_delegate["entrypoint"]),
                setup_delegate["service"],
                *setup_delegate["command"],
            ],
        }
    delegate = None
    if prefer_exec_delegate:
        delegate = resolve_exec_delegate_service(
            install_dir,
            services_data,
            service_name,
            run_args,
            allow_any_command=allow_any_exec_delegate,
        )
    if delegate:
        return {
            "delegate": delegate,
            "cmd": ["docker", "compose", "exec", "-T", delegate["service"], *delegate["command"]],
        }
    cmd = ["docker", "compose", "run", "--rm"]
    if no_deps_for_setup and is_setup_like_command(run_args):
        cmd.append("--no-deps")
    if use_service_ports:
        cmd.append("--service-ports")
    cmd.extend([service_name, *run_args])
    return {"delegate": None, "cmd": cmd}


def recreate_compose_service(install_dir: Path, service_name: str) -> subprocess.CompletedProcess[str]:
    res = docker_runner.compose_generic(
        cwd=install_dir,
        subcommand=["up", "-d", "--force-recreate", service_name],
        check=False,
    )
    return subprocess.CompletedProcess(
        args=list(res.args),
        returncode=res.returncode,
        stdout=res.stdout,
        stderr=res.stderr,
    )


def wait_for_compose_service_ready(
    install_dir: Path,
    service_name: str,
    *,
    timeout_seconds: int = 120,
    poll_interval_seconds: float = 2.0,
) -> Dict[str, Any]:
    deadline = time.time() + max(timeout_seconds, 1)
    last_status = "unknown"
    last_health = ""
    last_restart_count = 0

    while time.time() < deadline:
        ps_res = docker_runner.compose_ps(
            cwd=install_dir,
            args=["-q", service_name],
        )
        container_id = str(ps_res.stdout or "").strip()
        if not container_id:
            time.sleep(poll_interval_seconds)
            continue

        inspect_res = docker_runner.docker_run(
            [
                "inspect",
                "-f",
                "{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}|{{.RestartCount}}",
                container_id,
            ],
            cwd=install_dir,
        )
        raw = str(inspect_res.stdout or "").strip()
        if raw:
            parts = raw.split("|")
            last_status = parts[0] if len(parts) > 0 else "unknown"
            last_health = parts[1] if len(parts) > 1 else ""
            try:
                last_restart_count = int(parts[2]) if len(parts) > 2 else 0
            except Exception:
                last_restart_count = 0
            if last_status == "running" and last_health in {"", "none", "healthy"}:
                return {
                    "ok": True,
                    "status": last_status,
                    "health": last_health,
                    "restart_count": last_restart_count,
                }
        time.sleep(poll_interval_seconds)

    ps_text = ""
    logs_text = ""
    try:
        ps_dump = docker_runner.compose_ps(
            cwd=install_dir,
            args=["--format", "json"],
        )
        ps_text = str(ps_dump.stdout or "").strip()
    except Exception:
        ps_text = ""
    try:
        logs_dump = docker_runner.compose_generic(
            cwd=install_dir,
            subcommand=["logs", "--tail", "40", service_name],
            check=False,
        )
        logs_text = ((logs_dump.stdout or "") + "\n" + (logs_dump.stderr or "")).strip()
    except Exception:
        logs_text = ""

    return {
        "ok": False,
        "status": last_status,
        "health": last_health,
        "restart_count": last_restart_count,
        "compose_ps": ps_text,
        "recent_logs": logs_text,
    }
