"""
Wizard Iniziale per Limristem eMail.
Gestione dello stato di primo avvio, verifica conformita europea (GDPR),
pre-check DNS per Let's Encrypt, download chiave pubblica di firma backup
e meccanismo di snooze (promemoria a 7 giorni).
"""

from __future__ import annotations

import json
import logging
import os
import re
import subprocess
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from .alerts import get_public_ip, get_public_ipv6
from .settings import get_settings

logger = logging.getLogger(__name__)

WIZARD_STATE_FILENAME = "wizard-state.json"


def _state_file_path() -> Path:
    settings = get_settings()
    state_dir = Path(getattr(settings, "state_dir", "/var/lib/limristem-mail/state"))
    try:
        state_dir.mkdir(parents=True, exist_ok=True)
    except (OSError, PermissionError) as exc:
        logger.debug("Unable to create state dir %s: %s", state_dir, exc)
    return state_dir / WIZARD_STATE_FILENAME


def get_wizard_state() -> Dict[str, Any]:
    """Carica lo stato attuale del wizard dal file persistente."""
    path = _state_file_path()
    default_state = {
        "completed": False,
        "completed_at": None,
        "snoozed_until": None,
        "step": 1,
        "ssl_configured": False,
        "backup_configured": False,
        "mfa_configured": False,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    if not path.is_file():
        return default_state

    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        if isinstance(data, dict):
            # Assicura la presenza di tutte le chiavi predefinite
            for k, v in default_state.items():
                if k not in data:
                    data[k] = v
            return data
    except (OSError, json.JSONDecodeError) as exc:
        logger.warning("get_wizard_state: failed to read %s: %s", path, exc)

    return default_state


def save_wizard_state(state: Dict[str, Any]) -> bool:
    """Salva lo stato del wizard su disco."""
    path = _state_file_path()
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(state, indent=2), encoding="utf-8")
        try:
            os.chmod(path, 0o644)
        except OSError:
            pass
        return True
    except (OSError, PermissionError) as exc:
        logger.warning("save_wizard_state: failed to write %s: %s", path, exc)
        return False


def should_show_wizard() -> bool:
    """
    Determina se il wizard iniziale deve essere mostrato all'amministratore.
    - Se completato: False
    - Se posticipato (snooze di 7 giorni) e la data e futura: False
    - Altrimenti: True
    """
    state = get_wizard_state()
    if state.get("completed"):
        return False

    snoozed_until_str = state.get("snoozed_until")
    if snoozed_until_str:
        try:
            snoozed_until = datetime.fromisoformat(snoozed_until_str)
            if datetime.now(timezone.utc) < snoozed_until:
                return False
        except (ValueError, TypeError) as exc:
            logger.debug("should_show_wizard: invalid snoozed_until date: %s", exc)

    return True


def snooze_wizard(days: int = 7) -> Dict[str, Any]:
    """Posticipa la comparsa del wizard per il numero di giorni specificato (default: 7)."""
    state = get_wizard_state()
    until = datetime.now(timezone.utc) + timedelta(days=days)
    state["snoozed_until"] = until.isoformat()
    save_wizard_state(state)
    logger.info("Wizard snoozed until %s", state["snoozed_until"])
    return state


def complete_wizard() -> Dict[str, Any]:
    """Segna il wizard come completato."""
    state = get_wizard_state()
    state["completed"] = True
    state["completed_at"] = datetime.now(timezone.utc).isoformat()
    state["snoozed_until"] = None
    save_wizard_state(state)
    logger.info("Wizard completed at %s", state["completed_at"])
    return state


def reset_wizard() -> Dict[str, Any]:
    """Riavvia il wizard per consentire all'amministratore di ripercorrere la procedura guidata."""
    state = get_wizard_state()
    state["completed"] = False
    state["completed_at"] = None
    state["snoozed_until"] = None
    state["step"] = 1
    save_wizard_state(state)
    logger.info("Wizard state reset by administrator")
    return state


def verify_dns_for_hostname(hostname: Optional[str] = None) -> Dict[str, Any]:
    """
    Verifica live dei record DNS (A e AAAA) per l'hostname in vista della richiesta
    di un certificato Let's Encrypt.
    """
    settings = get_settings()
    host = (hostname or getattr(settings, "hostname", "")).strip().rstrip(".").lower()
    public_ip = (str(getattr(settings, "public_ip", "") or os.getenv("LIMRISTEM_MAIL_PUBLIC_IP", "")).strip() or get_public_ip())
    public_ipv6 = (str(getattr(settings, "public_ipv6", "") or os.getenv("LIMRISTEM_MAIL_PUBLIC_IPV6", "")).strip() or get_public_ipv6())

    result = {
        "hostname": host,
        "public_ip": public_ip,
        "public_ipv6": public_ipv6,
        "a_record": "",
        "aaaa_record": "",
        "a_matches": False,
        "aaaa_matches": False,
        "ready_for_letsencrypt": False,
        "errors": [],
        "warnings": [],
        "recommendations": [],
    }

    if not host or host == "localhost":
        result["errors"].append("Hostname non valido o impostato su localhost.")
        return result

    # 1. Verifica Record A IPv4
    for resolver in ("@1.1.1.1", "@8.8.8.8"):
        try:
            res = subprocess.run(["dig", "+short", host, "A", resolver], capture_output=True, text=True, timeout=3)
            out = res.stdout.strip()
            if out:
                lines = [line.strip() for line in out.splitlines() if line.strip() and not line.startswith(";")]
                if lines:
                    result["a_record"] = lines[0]
                    if public_ip and public_ip in lines:
                        result["a_matches"] = True
                    break
        except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
            logger.debug("verify_dns_for_hostname dig A failed: %s", exc)
        except Exception as exc:
            logger.warning("verify_dns_for_hostname: suppressed %s: %s", type(exc).__name__, exc)

    # 2. Verifica Record AAAA IPv6
    for resolver in ("@1.1.1.1", "@8.8.8.8"):
        try:
            res = subprocess.run(["dig", "+short", host, "AAAA", resolver], capture_output=True, text=True, timeout=3)
            out = res.stdout.strip()
            if out:
                lines = [line.strip() for line in out.splitlines() if line.strip() and not line.startswith(";")]
                if lines:
                    result["aaaa_record"] = lines[0]
                    if public_ipv6 and public_ipv6.lower() in [l.lower() for l in lines]:
                        result["aaaa_matches"] = True
                    break
        except (subprocess.CalledProcessError, FileNotFoundError, OSError) as exc:
            logger.debug("verify_dns_for_hostname dig AAAA failed: %s", exc)
        except Exception as exc:
            logger.warning("verify_dns_for_hostname: suppressed %s: %s", type(exc).__name__, exc)

    # Valutazione finale
    if not result["a_record"]:
        result["errors"].append(
            f"Nessun record DNS 'A' rilevato per '{host}'. "
            f"Crea un record A presso il tuo DNS provider: {host} -> {public_ip or '<IP pubblico>'}"
        )
    elif public_ip and not result["a_matches"]:
        result["errors"].append(
            f"Il record DNS 'A' per '{host}' punta a '{result['a_record']}', ma l'IP del server e '{public_ip}'. "
            f"Let's Encrypt fallira finche il record A non puntera a {public_ip}."
        )
    else:
        result["a_matches"] = True

    if public_ipv6 and result["aaaa_record"] and not result["aaaa_matches"]:
        result["warnings"].append(
            f"Il record DNS 'AAAA' punta a '{result['aaaa_record']}', mentre l'IPv6 locale e '{public_ipv6}'."
        )

    result["ready_for_letsencrypt"] = result["a_matches"] and bool(result["a_record"])
    return result


def get_backup_signing_key_content() -> Optional[str]:
    """Recupera il contenuto della chiave pubblica di firma dei backup."""
    try:
        from .backup_key import signing_public_key, escrow_copy_paths
        key = signing_public_key()
        if key:
            return key.strip()
        for p in escrow_copy_paths():
            if p.is_file():
                try:
                    content = p.read_text(encoding="utf-8").strip()
                    if content:
                        return content
                except (OSError, PermissionError):
                    pass
    except Exception as exc:
        logger.debug("get_backup_signing_key_content: backup_key lookup error: %s", exc)

    candidates = [
        Path("/opt/limristem-mail/limristem-mail-backup-signing.pub"),
        Path("/opt/limristem-mail/config/backup-signing.pub"),
        Path("/root/limristem-mail-backup-signing.pub"),
        Path("/etc/limristem-mail/limristem-mail-backup-signing.pub"),
    ]
    for p in candidates:
        if p.is_file():
            try:
                content = p.read_text(encoding="utf-8").strip()
                if content:
                    return content
            except (OSError, PermissionError) as exc:
                logger.debug("Unable to read backup pubkey from %s: %s", p, exc)
    return None
