"""System health, diagnostics, and warning alerts collector."""

import json
import logging
import os
import re
import socket
import subprocess
import time
from typing import Any, Dict, List
from urllib import request as urllib_request

from .settings import get_settings

logger = logging.getLogger(__name__)

_ALERTS_CACHE: Dict[str, Any] = {"timestamp": 0, "data": None}
CACHE_TTL = 60


def get_public_ip() -> str:
    settings = get_settings()
    if getattr(settings, "public_ip", None):
        val = str(settings.public_ip).strip()
        if val:
            return val
    env_ip = os.getenv("LIMRISTEM_MAIL_PUBLIC_IP", "").strip()
    if env_ip:
        return env_ip
    try:
        res = subprocess.run(["curl", "-4", "-s", "-m", "3", "https://ifconfig.me"], capture_output=True, text=True)
        ip = res.stdout.strip()
        if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", ip):
            return ip
    except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
        logger.debug("get_public_ip: curl failed: %s", exc)
    except Exception as exc:
        logger.warning("get_public_ip: suppressed %s: %s", type(exc).__name__, exc)
    return ""


def get_public_ipv6() -> str:
    settings = get_settings()
    if getattr(settings, "public_ipv6", None):
        val = str(settings.public_ipv6).strip()
        if val:
            return val
    env_ip = os.getenv("LIMRISTEM_MAIL_PUBLIC_IPV6", "").strip()
    if env_ip:
        return env_ip
    try:
        res = subprocess.run(["curl", "-6", "-s", "-m", "3", "https://ifconfig.me"], capture_output=True, text=True)
        ip = res.stdout.strip()
        if ":" in ip:
            return ip
    except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
        logger.debug("get_public_ipv6: curl failed: %s", exc)
    except Exception as exc:
        logger.warning("get_public_ipv6: suppressed %s: %s", type(exc).__name__, exc)
    return ""


def check_apt_updates() -> Dict[str, Any]:
    try:
        from .operations import get_apt_job_state

        job = get_apt_job_state()
        if job.get("running"):
            return {
                "id": "apt_updates",
                "ok": False,
                "level": "warning",
                "title": "Aggiornamento APT in Corso (In Queue)",
                "message": f"L'aggiornamento pacchetti di sistema (apt update && upgrade -y) è in corso in background dall'ora {job.get('started_at')}.",
            }
    except Exception as exc:
        logger.warning("check_apt_updates: suppressed %s: %s", type(exc).__name__, exc)

    try:
        res = subprocess.run(["apt-get", "-s", "upgrade"], capture_output=True, text=True, timeout=4)
        match = re.search(r"^(\d+)\s+upgraded", res.stdout, re.MULTILINE)
        if match:
            count = int(match.group(1))
            if count > 0:
                return {
                    "id": "apt_updates",
                    "ok": False,
                    "level": "warning",
                    "title": "Aggiornamenti APT Pendenti",
                    "message": f"Ci sono {count} pacchetti di sistema in attesa di aggiornamento tramite apt.",
                }
    except Exception as exc:
        logger.debug(f"apt updates check error: {exc}")

    return {
        "id": "apt_updates",
        "ok": True,
        "level": "success",
        "title": "Sistema APT Aggiornato",
        "message": "Nessun aggiornamento pendente rilevato per i pacchetti di sistema.",
    }


def check_limristem_version() -> Dict[str, Any]:
    settings = get_settings()
    current_version = getattr(settings, "app_version", "0.1.14")
    latest_version = current_version

    channel_url = getattr(settings, "version_manifest_url", None) or "https://nightly.limristem.eu/mail/version.json"
    try:
        req = urllib_request.Request(channel_url, headers={"User-Agent": "LimristemMailCheck/1.0"})
        with urllib_request.urlopen(req, timeout=2.5) as resp:
            if resp.status == 200:
                data = json.loads(resp.read().decode("utf-8"))
                if isinstance(data, list) and data:
                    remote_v = str(data[0].get("version", "")).strip()
                    if remote_v:
                        latest_version = remote_v
    except Exception as exc:
        logger.debug(f"Remote version check error: {exc}")

    def parse_ver(v_str: str) -> tuple[int, ...]:
        return tuple(int(p) for p in re.findall(r"\d+", v_str))

    is_outdated = False
    try:
        if parse_ver(latest_version) > parse_ver(current_version):
            is_outdated = True
    except Exception as exc:
        logger.warning("check_limristem_version: suppressed %s: %s", type(exc).__name__, exc)
        if latest_version != current_version:
            is_outdated = True

    if is_outdated:
        return {
            "id": "app_version",
            "ok": False,
            "level": "warning",
            "title": f"Nuova Versione Limristem eMail Disponibile (v{latest_version})",
            "message": f"È disponibile una nuova versione (v{latest_version}). Versione attualmente installata sul server: v{current_version}.",
        }

    return {
        "id": "app_version",
        "ok": True,
        "level": "success",
        "title": f"Versione Limristem eMail (v{current_version})",
        "message": f"Il server è aggiornato all'ultima versione disponibile ({current_version}).",
    }


def check_dns_and_ptr(hostname: str) -> List[Dict[str, Any]]:
    results = []
    public_ip = get_public_ip()
    public_ipv6 = get_public_ipv6()
    target_hostname = hostname.strip().rstrip(".").lower()

    # 1. PTR Record Check (IPv4 and IPv6)
    ptr_v4_val = ""
    if public_ip:
        for dns_server in ("@1.1.1.1", "@8.8.8.8"):
            try:
                res = subprocess.run(["dig", "+short", "-x", public_ip, dns_server], capture_output=True, text=True, timeout=3)
                out = res.stdout.strip()
                if out:
                    lines = [line.strip().rstrip(".").lower() for line in out.splitlines() if line.strip()]
                    ptr_v4_val = target_hostname if target_hostname in lines else (lines[0] if lines else "")
                    if ptr_v4_val:
                        break
            except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
                logger.debug("check_dns_and_ptr dig ipv4 failed: %s", exc)
            except Exception as exc:
                logger.warning("check_dns_and_ptr: suppressed %s: %s", type(exc).__name__, exc)

    ptr_v6_val = ""
    if public_ipv6:
        for dns_server in ("@1.1.1.1", "@8.8.8.8"):
            try:
                res = subprocess.run(["dig", "+short", "-x", public_ipv6, dns_server], capture_output=True, text=True, timeout=3)
                out = res.stdout.strip()
                if out:
                    lines = [line.strip().rstrip(".").lower() for line in out.splitlines() if line.strip()]
                    ptr_v6_val = target_hostname if target_hostname in lines else (lines[0] if lines else "")
                    if ptr_v6_val:
                        break
            except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
                logger.debug("check_dns_and_ptr dig ipv6 failed: %s", exc)
            except Exception as exc:
                logger.warning("check_dns_and_ptr: suppressed %s: %s", type(exc).__name__, exc)

    v4_ok = (ptr_v4_val == target_hostname) if public_ip else False
    v6_ok = (ptr_v6_val == target_hostname) if public_ipv6 else False

    if public_ip and v4_ok and (not public_ipv6 or v6_ok):
        msg = f"Record PTR impostato per IPv4 {public_ip} -> {ptr_v4_val}."
        if public_ipv6 and ptr_v6_val:
            msg += f" Anche per IPv6 {public_ipv6} -> {ptr_v6_val}."
        results.append({
            "id": "ptr_record",
            "ok": True,
            "level": "success",
            "title": "Record PTR Reverse DNS",
            "message": msg,
        })
    elif public_ip and ptr_v4_val:
        results.append({
            "id": "ptr_record",
            "ok": False,
            "level": "warning",
            "title": "Record PTR Non Coincidente",
            "message": f"Il record PTR per {public_ip} punta a '{ptr_v4_val}', ma dovrebbe puntare all'hostname del server '{target_hostname}'. Questo potrebbe abbassare il punteggio di deliverability.",
        })
    elif public_ipv6 and v6_ok and not public_ip:
        results.append({
            "id": "ptr_record",
            "ok": True,
            "level": "success",
            "title": "Record PTR Reverse DNS (IPv6)",
            "message": f"Record PTR impostato per IPv6 {public_ipv6} -> {ptr_v6_val}.",
        })
    else:
        results.append({
            "id": "ptr_record",
            "ok": False,
            "level": "danger",
            "title": "Record PTR Mancante / Non Impostato",
            "message": f"Il record Reverse DNS (PTR) per l'IP pubblico {public_ip or public_ipv6 or 'del server'} non risulta configurato. Potrebbe causare il rifiuto delle e-mail inviate dai principali provider (Gmail, Outlook).",
        })

    # 2. Hostname A Record Check
    a_ok = False
    resolved_ip = ""
    if target_hostname:
        for dns_server in ("@1.1.1.1", "@8.8.8.8"):
            try:
                res = subprocess.run(["dig", "+short", target_hostname, dns_server], capture_output=True, text=True, timeout=3)
                resolved_ip = res.stdout.strip()
                if resolved_ip:
                    ips = [line.strip() for line in resolved_ip.splitlines() if line.strip()]
                    if not public_ip or public_ip in ips:
                        a_ok = True
                        resolved_ip = public_ip if public_ip in ips else ips[0]
                        break
            except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
                logger.debug("check_dns_and_ptr dig A failed: %s", exc)
            except Exception as exc:
                logger.warning("check_dns_and_ptr: suppressed %s: %s", type(exc).__name__, exc)

    if a_ok:
        results.append({
            "id": "hostname_a_record",
            "ok": True,
            "level": "success",
            "title": f"Record A Hostname ({target_hostname})",
            "message": f"Il record DNS A per {target_hostname} risolve correttamente sull'IP {resolved_ip}.",
        })
    else:
        results.append({
            "id": "hostname_a_record",
            "ok": False,
            "level": "danger",
            "title": f"Record A Hostname ({target_hostname}) Non Corretto",
            "message": f"Il record DNS A per l'hostname {target_hostname} non punta all'IP del server ({public_ip or 'sconosciuto'}). Rilevato: {resolved_ip or 'Nessuna risoluzione'}.",
        })

    # 3. MTA-STS A Record & SSL Cert Check
    mta_sts_host = f"mta-sts.{hostname}" if hostname and hostname != "localhost" else ""
    if mta_sts_host:
        mta_sts_a_ok = False
        mta_sts_ip = ""
        try:
            res = subprocess.run(["dig", "+short", mta_sts_host, "@8.8.8.8"], capture_output=True, text=True, timeout=3)
            mta_sts_ip = res.stdout.strip()
            if mta_sts_ip:
                mta_sts_a_ok = True
        except Exception as exc:
            logger.warning("check_dns_and_ptr: suppressed %s: %s", type(exc).__name__, exc)

        mta_sts_ssl_ok = False
        if mta_sts_a_ok:
            try:
                import ssl
                ctx = ssl.create_default_context()
                ctx.check_hostname = False
                ctx.verify_mode = ssl.CERT_NONE
                with socket.create_connection((mta_sts_host, 443), timeout=3) as sock:
                    with ctx.wrap_socket(sock, server_hostname=mta_sts_host):
                        mta_sts_ssl_ok = True
            except Exception as exc:
                logger.warning("check_dns_and_ptr: suppressed %s: %s", type(exc).__name__, exc)

        if mta_sts_a_ok and mta_sts_ssl_ok:
            results.append({
                "id": "mta_sts",
                "ok": True,
                "level": "success",
                "title": f"MTA-STS ({mta_sts_host})",
                "message": f"Record A ed SSL Let's Encrypt attivi e funzionanti per {mta_sts_host}.",
            })
        elif mta_sts_a_ok:
            results.append({
                "id": "mta_sts",
                "ok": False,
                "level": "warning",
                "title": f"Certificato Let's Encrypt Mancante per {mta_sts_host}",
                "message": f"Il record A per {mta_sts_host} esiste ({mta_sts_ip}) ma il certificato SSL HTTPS/Let's Encrypt non risulta attivo.",
            })
        else:
            results.append({
                "id": "mta_sts",
                "ok": False,
                "level": "warning",
                "title": f"Record MTA-STS Mancante ({mta_sts_host})",
                "message": f"Il sottodominio {mta_sts_host} per la sicurezza MTA-STS non possiede un record DNS A configurato.",
            })

    return results


def check_core_services() -> Dict[str, Any]:
    services = ["postfix", "dovecot", "rspamd"]
    down = []
    for s in services:
        try:
            res = subprocess.run(["systemctl", "is-active", s], capture_output=True, text=True, timeout=2)
            if res.stdout.strip() != "active":
                down.append(s)
        except Exception as exc:
            logger.warning("check_core_services: suppressed %s: %s", type(exc).__name__, exc)
            down.append(s)

    if down:
        return {
            "id": "core_services",
            "ok": False,
            "level": "danger",
            "title": "Servizi Core Disattivi / Errore",
            "message": f"I seguenti servizi mail non sono attivi: {', '.join(down)}. Postfix, Dovecot e Rspamd devono essere tutti online.",
        }

    return {
        "id": "core_services",
        "ok": True,
        "level": "success",
        "title": "Servizi Core Online",
        "message": "Postfix, Dovecot e Rspamd sono attivi ed in esecuzione.",
    }


def check_firewall_useful_ports() -> Dict[str, Any]:
    try:
        try:
            from .admin_ops import load_firewall_config
        except (ImportError, ValueError):
            from admin_ops import load_firewall_config
        cfg = load_firewall_config()
    except Exception as exc:
        logger.debug(f"Failed to load firewall config for alerts: {exc}")
        cfg = {}

    raw_enabled = cfg.get("firewall-enabled", cfg.get("firewall", "yes"))
    is_enabled = str(raw_enabled).lower() in {"yes", "1", "true", "enabled"}

    if not is_enabled:
        return {
            "id": "firewall_ports",
            "ok": False,
            "level": "danger",
            "title": "Firewall Disattivato",
            "message": "Il firewall di sistema (nftables) risulta disattivato. Attivare il firewall nella sezione Security per proteggere il server di posta.",
        }

    rules = cfg.get("firewall-rules", [])
    allowed_ports = set()

    for r in rules:
        if isinstance(r, dict) and str(r.get("enabled", "")).lower() in {"yes", "1", "true"}:
            ports_str = str(r.get("ports", ""))
            for p in re.split(r"[\s,]+", ports_str):
                p = p.strip()
                if p:
                    if "-" in p:
                        try:
                            start, end = map(int, p.split("-", 1))
                            for port_num in range(start, end + 1):
                                allowed_ports.add(str(port_num))
                        except ValueError:
                            pass
                    else:
                        allowed_ports.add(p)

    if not allowed_ports and cfg.get("firewall-allowed-tcp-ports"):
        for p in re.split(r"[\s,]+", str(cfg["firewall-allowed-tcp-ports"])):
            if p.strip():
                allowed_ports.add(p.strip())

    essential_ports = [
        ("25", "SMTP Inbound/Outbound"),
        ("80", "HTTP / Certificati Let's Encrypt"),
        ("443", "HTTPS / Admin Panel"),
        ("465", "SMTPS Submission"),
        ("587", "SMTP Submission"),
        ("993", "IMAPS / Dovecot"),
        ("995", "POP3S / Dovecot"),
    ]

    blocked = [f"Porta {p} ({label})" for p, label in essential_ports if p not in allowed_ports]

    if blocked:
        return {
            "id": "firewall_ports",
            "ok": False,
            "level": "danger",
            "title": "Porte Mail Essenziali Chiuse o Inaccessibili",
            "message": f"Il firewall è attivo ma le seguenti porte essenziali per l'invio, la ricezione e l'accesso mail risultano chiuse nelle regole: {', '.join(blocked)}.",
        }

    return {
        "id": "firewall_ports",
        "ok": True,
        "level": "success",
        "title": "Firewall Attivo e Porte Configurate",
        "message": "Il firewall nftables è attivo e le porte essenziali per il funzionamento della posta (25, 80, 443, 465, 587, 993, 995) sono aperte ed accessibili.",
    }


def collect_system_alerts(force_refresh: bool = False) -> Dict[str, Any]:
    global _ALERTS_CACHE
    now = time.time()
    if not force_refresh and (now - _ALERTS_CACHE["timestamp"] < CACHE_TTL) and _ALERTS_CACHE.get("data"):
        return _ALERTS_CACHE["data"]

    settings = get_settings()
    hostname = settings.hostname or "localhost"

    alerts = []
    alerts.append(check_apt_updates())
    alerts.append(check_limristem_version())
    alerts.extend(check_dns_and_ptr(hostname))
    alerts.append(check_core_services())
    alerts.append(check_firewall_useful_ports())

    active_issues = [a for a in alerts if not a.get("ok")]
    has_issues = len(active_issues) > 0

    data = {
        "has_issues": has_issues,
        "issue_count": len(active_issues),
        "alerts": alerts,
        "active_issues": active_issues,
    }

    _ALERTS_CACHE = {"timestamp": now, "data": data}
    return data
