"""Service de deploiement Qualys Cloud Agent via SSH"""
import os
import re
import logging
from datetime import datetime

log = logging.getLogger(__name__)

AGENTS_DIR = "/opt/patchcenter/agents"

try:
    import paramiko
    PARAMIKO_OK = True
except ImportError:
    PARAMIKO_OK = False


def _extract_version(filename):
    """Extrait la version depuis le nom du fichier"""
    m = re.search(r'(\d+\.\d+\.\d+[\.\-]\d+)', filename)
    return m.group(1) if m else "inconnue"


def list_packages():
    """Liste les packages disponibles dans /opt/patchcenter/agents/"""
    packages = {"deb": [], "rpm": []}
    if not os.path.isdir(AGENTS_DIR):
        return packages
    for f in sorted(os.listdir(AGENTS_DIR), reverse=True):
        path = os.path.join(AGENTS_DIR, f)
        size_mb = round(os.path.getsize(path) / 1024 / 1024, 1)
        version = _extract_version(f)
        entry = {"name": f, "path": path, "size": size_mb, "version": version}
        if f.endswith(".deb"):
            packages["deb"].append(entry)
        elif f.endswith(".rpm"):
            packages["rpm"].append(entry)
    return packages


def _get_ssh_client(hostname, ssh_user, ssh_key_path, ssh_port=22):
    """Crée un client SSH paramiko"""
    if not PARAMIKO_OK:
        return None, "paramiko non disponible"
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        for cls in [paramiko.Ed25519Key, paramiko.RSAKey, paramiko.ECDSAKey]:
            try:
                key = cls.from_private_key_file(ssh_key_path)
                client.connect(hostname, port=ssh_port, username=ssh_user, pkey=key,
                               timeout=15, look_for_keys=False, allow_agent=False)
                return client, None
            except Exception:
                continue
        return None, f"Impossible de charger la cle {ssh_key_path}"
    except Exception as e:
        return None, str(e)


def _run_cmd(client, cmd, sudo=False, timeout=120):
    """Execute une commande SSH"""
    if sudo:
        _, stdout_id, _ = client.exec_command("id -u", timeout=5)
        uid = stdout_id.read().decode().strip()
        if uid != "0":
            cmd = f"sudo {cmd}"
    _, stdout, stderr = client.exec_command(cmd, timeout=timeout)
    exit_code = stdout.channel.recv_exit_status()
    out = stdout.read().decode("utf-8", errors="replace")
    err = stderr.read().decode("utf-8", errors="replace")
    return exit_code, out, err


def check_agent(hostname, ssh_user, ssh_key_path, ssh_port=22):
    """Vérifie le statut de l'agent Qualys sur un serveur"""
    client, error = _get_ssh_client(hostname, ssh_user, ssh_key_path, ssh_port)
    if not client:
        return {"hostname": hostname, "status": "CONNECTION_FAILED", "detail": error}

    result = {"hostname": hostname}

    # Check if installed (rpm -q returns 0 only if installed, dpkg -s returns 0 only if installed)
    installed = False
    code, out, _ = _run_cmd(client, "rpm -q qualys-cloud-agent 2>/dev/null")
    if code == 0 and "not installed" not in out.lower() and "n'est pas install" not in out.lower():
        installed = True
    else:
        code, out, _ = _run_cmd(client, "dpkg -s qualys-cloud-agent 2>/dev/null")
        if code == 0 and "install ok installed" in out.lower():
            installed = True

    if not installed:
        result["status"] = "NOT_INSTALLED"
        result["detail"] = "Agent non installe"
        result["version"] = ""
        result["service_status"] = ""
        client.close()
        return result

    # Check if running
    code, out, _ = _run_cmd(client, "systemctl is-active qualys-cloud-agent 2>/dev/null")
    status = out.strip()
    result["service_status"] = status

    # Get version via package manager
    code, out, _ = _run_cmd(client, "rpm -q --qf '%{VERSION}-%{RELEASE}' qualys-cloud-agent 2>/dev/null || dpkg-query -W -f='${Version}' qualys-cloud-agent 2>/dev/null")
    version = out.strip()
    m = re.search(r'(\d+\.\d+\.\d+[\.\-]\d+)', version)
    result["version"] = m.group(1) if m else version[:50]

    # Get last checkin from log
    code, out, _ = _run_cmd(client, "tail -5 /var/log/qualys/qualys-cloud-agent.log 2>/dev/null | grep 'HTTP response code: 200' | tail -1 | awk '{print $1, $2}'")
    result["last_checkin"] = out.strip()[:25]

    if status == "active":
        result["status"] = "ACTIVE"
        result["detail"] = "Agent actif"
    elif status == "inactive" or status == "dead":
        result["status"] = "INACTIVE"
        result["detail"] = "Agent installe mais inactif"
    else:
        result["status"] = "UNKNOWN"
        result["detail"] = f"Statut: {status}"

    client.close()
    return result


def _compare_versions(v1, v2):
    """Compare deux versions. Retourne -1 (v1<v2), 0 (egales), 1 (v1>v2)"""
    def to_parts(v):
        return [int(x) for x in re.split(r'[.\-]', v) if x.isdigit()]
    p1, p2 = to_parts(v1 or ""), to_parts(v2 or "")
    for a, b in zip(p1, p2):
        if a < b: return -1
        if a > b: return 1
    if len(p1) < len(p2): return -1
    if len(p1) > len(p2): return 1
    return 0


def deploy_agent(hostname, ssh_user, ssh_key_path, ssh_port, os_family,
                 package_path, activation_id, customer_id, server_uri,
                 on_line=None, on_stage=None, force_downgrade=False):
    """Deploie l'agent Qualys sur un serveur (install, upgrade ou downgrade)"""

    def emit(msg):
        if on_line:
            on_line(msg)
        log.info(f"[{hostname}] {msg}")

    def stage(name, detail=""):
        if on_stage:
            on_stage(name, detail)

    stage("connecting", f"Connexion SSH {ssh_user}@{hostname}:{ssh_port}")
    emit(f"Connexion SSH {ssh_user}@{hostname}:{ssh_port}...")
    client, error = _get_ssh_client(hostname, ssh_user, ssh_key_path, ssh_port)
    if not client:
        emit(f"ERREUR connexion: {error}")
        stage("failed", error)
        return {"hostname": hostname, "status": "FAILED", "detail": error}

    result = {"hostname": hostname, "status": "PENDING"}
    pkg_name = os.path.basename(package_path)
    pkg_version = _extract_version(pkg_name)
    is_deb = pkg_name.endswith(".deb")

    try:
        # 1. Detect OS if not provided
        if not os_family:
            code, out, _ = _run_cmd(client, "cat /etc/os-release 2>/dev/null | head -3")
            os_family = "linux"
            if "debian" in out.lower() or "ubuntu" in out.lower():
                os_family = "debian"
            elif "red hat" in out.lower() or "centos" in out.lower() or "rocky" in out.lower():
                os_family = "rhel"
            emit(f"OS detecte: {os_family}")

        # 2. Check installed version
        stage("checking", "Verification agent existant")
        installed_version = None
        code, out, _ = _run_cmd(client, "rpm -q qualys-cloud-agent 2>/dev/null")
        if code == 0 and "not installed" not in out.lower() and "n'est pas install" not in out.lower():
            m = re.search(r'(\d+\.\d+\.\d+[\.\-]\d+)', out)
            installed_version = m.group(1) if m else None
        else:
            code, out, _ = _run_cmd(client, "dpkg-query -W -f='${Version}' qualys-cloud-agent 2>/dev/null")
            if code == 0 and out.strip():
                m = re.search(r'(\d+\.\d+\.\d+[\.\-]\d+)', out)
                installed_version = m.group(1) if m else None

        if installed_version:
            cmp = _compare_versions(pkg_version, installed_version)
            emit(f"Version installee: {installed_version}, package: {pkg_version}")
            if cmp == 0:
                # Meme version
                code, out, _ = _run_cmd(client, "systemctl is-active qualys-cloud-agent 2>/dev/null")
                if out.strip() == "active":
                    emit(f"Meme version ({installed_version}) et agent actif - skip")
                    stage("already_installed", f"v{installed_version} deja active")
                    result["status"] = "ALREADY_INSTALLED"
                    result["detail"] = f"v{installed_version} deja installee et active"
                    client.close()
                    return result
                else:
                    emit(f"Meme version mais service {out.strip()} - reinstallation")
            elif cmp < 0:
                # Downgrade
                if not force_downgrade:
                    emit(f"DOWNGRADE refuse: {installed_version} → {pkg_version}")
                    stage("downgrade_refused", f"v{installed_version} > v{pkg_version}")
                    result["status"] = "DOWNGRADE_REFUSED"
                    result["detail"] = f"Version installee ({installed_version}) plus recente que le package ({pkg_version}). Cochez 'Forcer le downgrade'."
                    client.close()
                    return result
                else:
                    emit(f"DOWNGRADE force: {installed_version} → {pkg_version}")
            else:
                emit(f"UPGRADE: {installed_version} → {pkg_version}")

        # 3. Copy package
        pkg_size = os.path.getsize(package_path) // 1024 // 1024
        stage("copying", f"Copie {pkg_name} ({pkg_size} Mo)")
        emit(f"Copie {pkg_name} ({pkg_size} Mo)...")

        sftp = client.open_sftp()
        sftp.put(package_path, f"/tmp/{pkg_name}")
        sftp.close()
        emit("Copie terminee")

        remote_path = f"/tmp/{pkg_name}"

        # 4. Install / Upgrade / Downgrade
        if is_deb:
            stage("installing", "Installation (dpkg)")
            emit("Installation (dpkg)...")
            code, out, err = _run_cmd(client, f"dpkg --install {remote_path}", sudo=True, timeout=120)
        else:
            if force_downgrade and installed_version:
                stage("installing", f"Downgrade (rpm) {installed_version} → {pkg_version}")
                emit(f"Downgrade (rpm --oldpackage)...")
                code, out, err = _run_cmd(client, f"rpm -Uvh --nosignature --oldpackage {remote_path}", sudo=True, timeout=120)
            else:
                stage("installing", "Installation/Upgrade (rpm)")
                emit("Installation/Upgrade (rpm -Uvh)...")
                code, out, err = _run_cmd(client, f"rpm -Uvh --nosignature {remote_path}", sudo=True, timeout=120)

        if code != 0 and "already installed" not in (out + err).lower():
            emit(f"ERREUR installation (code {code}): {err[:200]}")
            stage("failed", f"Installation echouee: {err[:100]}")
            result["status"] = "INSTALL_FAILED"
            result["detail"] = err[:200]
            client.close()
            return result
        emit("Installation OK")

        # 5. Activate
        stage("activating", "Activation de l'agent")
        emit("Activation de l'agent...")
        activate_cmd = (
            f"/usr/local/qualys/cloud-agent/bin/qualys-cloud-agent.sh "
            f"ActivationId={activation_id} "
            f"CustomerId={customer_id} "
            f"ServerUri={server_uri} "
            f"ProviderName=NONE"
        )
        code, out, err = _run_cmd(client, activate_cmd, sudo=True, timeout=60)
        if code != 0:
            emit(f"ERREUR activation (code {code}): {err[:200]}")
            stage("failed", f"Activation echouee: {err[:100]}")
            result["status"] = "ACTIVATE_FAILED"
            result["detail"] = err[:200]
            client.close()
            return result
        emit("Activation OK")

        # 6. Restart service
        stage("restarting", "Redemarrage du service")
        emit("Redemarrage du service...")
        _run_cmd(client, "systemctl restart qualys-cloud-agent", sudo=True)

        # 7. Verify
        stage("verifying", "Verification du service")
        code, out, _ = _run_cmd(client, "systemctl is-active qualys-cloud-agent")
        action = "Upgrade" if installed_version else "Install"
        if out.strip() == "active":
            emit(f"Agent {action.lower()} OK et actif !")
            stage("success", f"{action} OK — v{pkg_version} active")
            result["status"] = "SUCCESS"
            result["detail"] = f"{action} v{pkg_version} OK"
        else:
            emit(f"Agent installe mais statut: {out.strip()}")
            stage("partial", f"Service: {out.strip()}")
            result["status"] = "PARTIAL"
            result["detail"] = f"Installe, service: {out.strip()}"

        # 8. Cleanup
        _run_cmd(client, f"rm -f {remote_path}")

    except Exception as e:
        emit(f"ERREUR: {e}")
        stage("failed", str(e)[:100])
        result["status"] = "FAILED"
        result["detail"] = str(e)[:200]

    client.close()
    return result


# ═══════════════════════════════════════════════
# Background job manager
# ═══════════════════════════════════════════════
import threading
import uuid
import time

_deploy_jobs = {}  # job_id -> job dict


def start_deploy_job(servers, ssh_key, package_deb, package_rpm,
                     activation_id, customer_id, server_uri, force_downgrade=False):
    """Lance un job de deploiement en arriere-plan. Retourne le job_id."""
    job_id = str(uuid.uuid4())[:8]
    job = {
        "id": job_id,
        "started_at": time.time(),
        "total": len(servers),
        "done": 0,
        "servers": {},
        "log": [],
        "finished": False,
    }
    for srv in servers:
        job["servers"][srv["hostname"]] = {
            "hostname": srv["hostname"],
            "stage": "pending",
            "detail": "En attente",
            "status": None,
            "result": None,
        }
    _deploy_jobs[job_id] = job

    def _run():
        threads = []
        for srv in servers:
            t = threading.Thread(target=_deploy_one, args=(job, srv, ssh_key,
                package_deb, package_rpm, activation_id, customer_id, server_uri, force_downgrade))
            t.daemon = True
            threads.append(t)
            t.start()
        for t in threads:
            t.join()
        job["finished"] = True
        job["finished_at"] = time.time()

    master = threading.Thread(target=_run, daemon=True)
    master.start()
    return job_id


def _deploy_one(job, srv, ssh_key, package_deb, package_rpm,
                activation_id, customer_id, server_uri, force_downgrade=False):
    hostname = srv["hostname"]

    def on_stage(stage, detail=""):
        job["servers"][hostname]["stage"] = stage
        job["servers"][hostname]["detail"] = detail

    def on_line(msg):
        job["log"].append(f"[{hostname}] {msg}")

    osv = (srv.get("os_version") or "").lower()
    if any(k in osv for k in ("centos", "red hat", "rhel", "rocky", "oracle", "fedora", "alma")):
        pkg = package_rpm
    else:
        pkg = package_deb

    if not pkg or not os.path.exists(pkg):
        job["servers"][hostname]["stage"] = "failed"
        job["servers"][hostname]["detail"] = f"Package introuvable: {pkg}"
        job["servers"][hostname]["status"] = "FAILED"
        job["done"] += 1
        return

    result = deploy_agent(
        hostname=hostname, ssh_user=srv.get("ssh_user") or "root",
        ssh_key_path=ssh_key, ssh_port=srv.get("ssh_port") or 22,
        os_family=srv.get("os_family"), package_path=pkg,
        activation_id=activation_id, customer_id=customer_id,
        server_uri=server_uri, on_line=on_line, on_stage=on_stage,
        force_downgrade=force_downgrade,
    )
    job["servers"][hostname]["status"] = result["status"]
    job["servers"][hostname]["result"] = result
    job["done"] += 1


def get_deploy_job(job_id):
    """Retourne l'etat d'un job de deploiement."""
    return _deploy_jobs.get(job_id)


def list_deploy_jobs():
    """Liste les jobs recents (< 1h)."""
    now = time.time()
    return {jid: j for jid, j in _deploy_jobs.items() if now - j["started_at"] < 3600}
