"""YakPanel - App Store / Software API"""
from __future__ import annotations

import asyncio
import os
import shlex
import shutil
import subprocess
from typing import Literal

from fastapi import APIRouter, Depends, HTTPException

from app.core.utils import exec_shell_sync
from app.api.auth import get_current_user
from app.models.user import User

router = APIRouter(prefix="/soft", tags=["soft"])

PmKind = Literal["apt", "dnf", "yum", "apk", "none"]

# Per distro: apt = Debian/Ubuntu, rpm = RHEL/Fedora/Alma/Rocky, apk = Alpine (best-effort)
SOFTWARE_LIST: list[dict[str, str]] = [
    {
        "id": "nginx",
        "name": "Nginx",
        "desc": "Web server",
        "apt": "nginx",
        "rpm": "nginx",
        "apk": "nginx",
    },
    {
        "id": "mysql-server",
        "name": "MySQL Server",
        "desc": "Database server",
        "apt": "mysql-server",
        "rpm": "mysql-server",
        "apk": "mysql",
    },
    {
        "id": "mariadb-server",
        "name": "MariaDB",
        "desc": "Database server",
        "apt": "mariadb-server",
        "rpm": "mariadb-server",
        "apk": "mariadb",
    },
    {
        "id": "php",
        "name": "PHP",
        "desc": "PHP runtime",
        "apt": "php",
        "rpm": "php",
        "apk": "php",
    },
    {
        "id": "php-fpm",
        "name": "PHP-FPM",
        "desc": "PHP FastCGI",
        "apt": "php-fpm",
        "rpm": "php-fpm",
        "apk": "php-fpm",
    },
    {
        "id": "redis-server",
        "name": "Redis",
        "desc": "In-memory cache",
        "apt": "redis-server",
        "rpm": "redis",
        "apk": "redis",
    },
    {
        "id": "postgresql",
        "name": "PostgreSQL",
        "desc": "Database server",
        "apt": "postgresql",
        "rpm": "postgresql-server",
        "apk": "postgresql",
    },
    {
        "id": "mongodb",
        "name": "MongoDB",
        "desc": "NoSQL database",
        "apt": "mongodb",
        "rpm": "mongodb-server",
        "apk": "mongodb",
    },
    {
        "id": "certbot",
        "name": "Certbot",
        "desc": "Let's Encrypt SSL",
        "apt": "certbot",
        "rpm": "certbot",
        "apk": "certbot",
    },
    {
        "id": "docker",
        "name": "Docker",
        "desc": "Container runtime",
        "apt": "docker.io",
        "rpm": "docker",
        "apk": "docker",
    },
    {
        "id": "nodejs",
        "name": "Node.js",
        "desc": "JavaScript runtime",
        "apt": "nodejs",
        "rpm": "nodejs",
        "apk": "nodejs",
    },
    {
        "id": "npm",
        "name": "npm",
        "desc": "Node package manager",
        "apt": "npm",
        "rpm": "npm",
        "apk": "npm",
    },
    {
        "id": "git",
        "name": "Git",
        "desc": "Version control",
        "apt": "git",
        "rpm": "git",
        "apk": "git",
    },
    {
        "id": "python3",
        "name": "Python 3",
        "desc": "Python runtime",
        "apt": "python3",
        "rpm": "python3",
        "apk": "python3",
    },
]


def _detect_package_manager() -> PmKind:
    if shutil.which("apt-get"):
        return "apt"
    if shutil.which("dnf"):
        return "dnf"
    if shutil.which("yum"):
        return "yum"
    if shutil.which("apk"):
        return "apk"
    return "none"


def _package_name(entry: dict[str, str], pm: PmKind) -> str:
    if pm == "apt":
        return entry["apt"]
    if pm in ("dnf", "yum"):
        return entry["rpm"]
    if pm == "apk":
        return entry.get("apk") or entry["apt"]
    return entry["apt"]


def _apt_env() -> dict[str, str]:
    env = os.environ.copy()
    env.setdefault("DEBIAN_FRONTEND", "noninteractive")
    env.setdefault("APT_LISTCHANGES_FRONTEND", "none")
    return env


def _run_shell(script: str, timeout: int, env: dict[str, str] | None = None) -> None:
    result = subprocess.run(
        script,
        shell=True,
        capture_output=True,
        text=True,
        timeout=timeout,
        env=env if env is not None else os.environ.copy(),
    )
    if result.returncode == 0:
        return
    out = (result.stdout or "").strip()
    err = (result.stderr or "").strip()
    msg = err or out or f"Command failed (exit {result.returncode})"
    raise HTTPException(status_code=500, detail=msg[:4000])


def _check_installed_apt(pkg: str) -> tuple[bool, str]:
    out, _ = exec_shell_sync(f"dpkg -l {shlex.quote(pkg)} 2>/dev/null | grep ^ii", timeout=5)
    if out.strip():
        parts = out.split()
        if len(parts) >= 3:
            return True, parts[2]
    return False, ""


def _check_installed_rpm(pkg: str) -> tuple[bool, str]:
    try:
        result = subprocess.run(
            ["rpm", "-q", "--queryformat", "%{EVR}", pkg],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode != 0:
            return False, ""
        ver = (result.stdout or "").strip()
        return (True, ver) if ver else (True, "")
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False, ""


def _check_installed_apk(pkg: str) -> tuple[bool, str]:
    try:
        r = subprocess.run(
            ["apk", "info", "-e", pkg],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if r.returncode != 0:
            return False, ""
        out = (r.stdout or "").strip()
        if not out:
            return True, ""
        first = out.split("\n")[0].strip()
        return True, first
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False, ""


def _check_installed(pm: PmKind, pkg: str) -> tuple[bool, str]:
    if pm == "apt":
        return _check_installed_apt(pkg)
    if pm in ("dnf", "yum"):
        return _check_installed_rpm(pkg)
    if pm == "apk":
        return _check_installed_apk(pkg)
    return False, ""


def _install_script(pm: PmKind, pkg: str) -> tuple[str, int, dict[str, str] | None]:
    q = shlex.quote(pkg)
    if pm == "apt":
        return f"apt-get update -qq && apt-get install -y {q}", 600, _apt_env()
    if pm == "dnf":
        return f"dnf install -y {q}", 600, None
    if pm == "yum":
        return f"yum install -y {q}", 600, None
    if pm == "apk":
        return f"apk update && apk add {q}", 600, None
    raise HTTPException(
        status_code=501,
        detail="No supported package manager found (need apt-get, dnf, yum, or apk).",
    )


def _uninstall_script(pm: PmKind, pkg: str) -> tuple[str, int, dict[str, str] | None]:
    q = shlex.quote(pkg)
    if pm == "apt":
        return f"apt-get remove -y {q}", 180, _apt_env()
    if pm == "dnf":
        return f"dnf remove -y {q}", 180, None
    if pm == "yum":
        return f"yum remove -y {q}", 180, None
    if pm == "apk":
        return f"apk del {q}", 120, None
    raise HTTPException(
        status_code=501,
        detail="No supported package manager found (need apt-get, dnf, yum, or apk).",
    )


@router.get("/list")
async def soft_list(current_user: User = Depends(get_current_user)):
    """List software with install status for this OS."""
    pm = _detect_package_manager()
    manager_label = pm if pm != "none" else "unknown"
    result: list[dict] = []
    for s in SOFTWARE_LIST:
        pkg = _package_name(s, pm) if pm != "none" else s["apt"]
        installed, version = _check_installed(pm, pkg) if pm != "none" else (False, "")
        result.append(
            {
                "id": s["id"],
                "name": s["name"],
                "desc": s["desc"],
                "pkg": pkg,
                "installed": installed,
                "version": version if installed else "",
                "package_manager": manager_label,
            }
        )
    return {"software": result, "package_manager": manager_label}


@router.post("/install/{pkg_id}")
async def soft_install(
    pkg_id: str,
    current_user: User = Depends(get_current_user),
):
    """Install package via system package manager (root privileges required)."""
    pm = _detect_package_manager()
    entry = next((s for s in SOFTWARE_LIST if s["id"] == pkg_id), None)
    if not entry:
        raise HTTPException(status_code=404, detail="Package not found")
    pkg = _package_name(entry, pm)
    script, timeout, env = _install_script(pm, pkg)
    await asyncio.to_thread(_run_shell, script, timeout, env)
    return {"status": True, "msg": "Installed"}


@router.post("/uninstall/{pkg_id}")
async def soft_uninstall(
    pkg_id: str,
    current_user: User = Depends(get_current_user),
):
    """Uninstall package via system package manager."""
    pm = _detect_package_manager()
    entry = next((s for s in SOFTWARE_LIST if s["id"] == pkg_id), None)
    if not entry:
        raise HTTPException(status_code=404, detail="Package not found")
    pkg = _package_name(entry, pm)
    script, timeout, env = _uninstall_script(pm, pkg)
    await asyncio.to_thread(_run_shell, script, timeout, env)
    return {"status": True, "msg": "Uninstalled"}
