import logging
import os
import json
import subprocess
from pathlib import Path

from fastapi import HTTPException

from .limristem_mail_backend import BackendCommandError, execute_command
from .settings import get_settings

settings = get_settings()
logger = logging.getLogger(__name__)


SCRIPT_COMMAND_ALIASES = {
    "manage-queue.sh": "queue",
    "manage-bans.sh": "bans",
    "manage-limits.sh": "limits",
    "manage-backups.sh": "backup",
    "manage-firewall.sh": "firewall",
    "manage-api-credentials.sh": "api",
    "manage-settings.sh": "settings",
    "manage-ssl.sh": "ssl",
}


def requires_privileged_script_path(script_name: str, *args: str) -> bool:
    if script_name == "manage-backups.sh":
        return not bool(args) or args[0] not in {"show", "list", "list-schedules", "list-storages"}
    if script_name == "manage-firewall.sh":
        return not bool(args) or args[0] != "show"
    if script_name == "manage-queue.sh":
        return not bool(args) or args[0] != "list"
    if script_name == "manage-ssl.sh":
        return not bool(args) or args[0] != "show"
    if script_name == "manage-settings.sh":
        return not bool(args) or args[0] not in {"show", "check-update"}
    return False


def normalize_privileged_operation_error(detail: str, script_name: str, returncode: int, args: tuple[str, ...] = ()) -> str:
    cleaned_detail = detail.strip()
    normalized = cleaned_detail.lower()
    if "sudo:" in normalized and (
        "a terminal is required" in normalized
        or "a password is required" in normalized
        or "askpass" in normalized
    ):
        return "Privileged system operations require the installed passwordless sudoers rule for the limristem-mail user."
    if "no new privileges" in normalized:
        return "Privileged system operations are unavailable in the current environment."
    if script_name == "manage-queue.sh" and cleaned_detail:
        return cleaned_detail
    if "read-only file system" in normalized:
        if "/etc/systemd/system" in cleaned_detail or "limristem-mail-backup-schedule-" in cleaned_detail:
            return "Backup settings were saved, but this environment cannot update system backup files because the filesystem is read-only."
        if "cannot create directory" in normalized or "/var/backups/" in cleaned_detail:
            return "The current environment cannot write backup data because the filesystem is read-only."
        return "The current environment cannot complete this operation because the filesystem is read-only."
    if "is not writable in the current environment" in normalized:
        if "/etc/systemd/system" in cleaned_detail or "limristem-mail-backup-schedule-" in cleaned_detail:
            return "Backup settings were saved, but this environment cannot update system backup files because the filesystem is read-only."
        if "backup destination" in normalized or "/var/backups/" in cleaned_detail:
            return "The current environment cannot write backup data because the filesystem is read-only."
        return "The current environment cannot complete this operation because required paths are not writable."
    if not cleaned_detail and script_name == "manage-backups.sh" and args and args[0] == "run":
        return (
            "Backup failed before reporting a specific error. "
            "Check the backup destination, storage settings, and required backup tools, then retry."
        )
    if returncode < 0 and not detail:
        return f"{script_name} terminated by signal {abs(returncode)}"
    return cleaned_detail or f"{script_name} failed with status {returncode}"


def build_root_command(script_name: str, *args: str) -> list[str]:
    script_path = settings.runtime_bin_dir / script_name
    if requires_privileged_script_path(script_name, *args):
        return ["sudo", "-n", str(script_path), *args]
    cli_path = settings.base_dir / "limristem-mail"
    cli_command = SCRIPT_COMMAND_ALIASES.get(script_name)
    if cli_command:
        return ["sudo", "-n", str(cli_path), cli_command, *args]
    return ["sudo", "-n", str(script_path), *args]


def run_root_script(script_name: str, *args: str) -> str:
    cli_command = SCRIPT_COMMAND_ALIASES.get(script_name)
    if cli_command and not requires_privileged_script_path(script_name, *args):
        try:
            return execute_command(cli_command, *args)
        except BackendCommandError as exc:
            detail = normalize_privileged_operation_error(exc.detail.strip(), script_name, exc.returncode, args)
            raise HTTPException(status_code=500, detail=detail) from exc
    command = build_root_command(script_name, *args)
    try:
        result = subprocess.run(command, capture_output=True, text=True, check=True)
    except subprocess.CalledProcessError as exc:
        detail = normalize_privileged_operation_error(
            (exc.stderr or exc.stdout or "").strip(),
            script_name,
            exc.returncode,
            args,
        )
        raise HTTPException(status_code=500, detail=detail) from exc
    return result.stdout


def load_script_json(script_name: str, *args: str, default):
    output = run_root_script(script_name, *args).strip()
    try:
        return json.loads(output or json.dumps(default))
    except json.JSONDecodeError as exc:
        raise HTTPException(status_code=500, detail=f"{script_name} returned invalid JSON output") from exc


def load_queue() -> list[dict]:
    return load_script_json("manage-queue.sh", "list", "--json", default=[])


def load_bans() -> list[dict]:
    return load_script_json("manage-bans.sh", "list", "--json", default=[])


def load_limits() -> dict[str, str]:
    return load_script_json("manage-limits.sh", "show", "--json", default={})


def load_backup_config() -> dict[str, str]:
    return load_script_json("manage-backups.sh", "show", "--json", default={})


def load_backup_runs() -> list[dict]:
    return load_script_json("manage-backups.sh", "list", "--json", default=[])


def load_backup_schedules() -> list[dict]:
    return load_script_json("manage-backups.sh", "list-schedules", "--json", default=[])


def load_backup_storages() -> list[dict]:
    return load_script_json("manage-backups.sh", "list-storages", "--json", default=[])


def save_backup_schedule(payload_json: str) -> dict:
    return load_script_json("manage-backups.sh", "save-schedule", payload_json, "--json", default={})


def save_backup_storage(payload_json: str) -> dict:
    return load_script_json("manage-backups.sh", "save-storage", payload_json, "--json", default={})


def test_backup_storage(storage_id: str) -> dict:
    return load_script_json("manage-backups.sh", "test-storage", storage_id, "--json", default={})


def load_firewall_config() -> dict[str, str]:
    return load_script_json("manage-firewall.sh", "show", "--json", default={})


def load_api_credentials() -> dict[str, str]:
    return load_script_json("manage-api-credentials.sh", "show", "--json", default={})


def load_ssl_config() -> dict[str, str]:
    try:
        payload = json.loads(execute_command("ssl", "show", "--json", allow_fallback=False))
        if isinstance(payload, dict) and payload:
            return payload
    except Exception as exc:
        logger.warning("Unable to load SSL system data via backend command: %s", exc)

    from .settings import get_settings as _get_settings

    current_settings = _get_settings()
    hostname = current_settings.hostname
    primary_domain = current_settings.primary_domain
    ssl_mode = (current_settings.ssl_mode or "selfsigned").strip().lower()
    if ssl_mode == "manual":
        cert_path = os.getenv("LIMRISTEM_MAIL_TLS_CERT_PATH", "/etc/ssl/limristem-mail/manual.crt")
        key_path = os.getenv("LIMRISTEM_MAIL_TLS_KEY_PATH", "/etc/ssl/limristem-mail/manual.key")
    elif ssl_mode == "letsencrypt":
        cert_path = f"/etc/letsencrypt/live/{hostname}/fullchain.pem"
        key_path = f"/etc/letsencrypt/live/{hostname}/privkey.pem"
    elif ssl_mode == "plain":
        cert_path = ""
        key_path = ""
    else:
        cert_path = "/etc/ssl/limristem-mail/limristem-mail.crt"
        key_path = "/etc/ssl/limristem-mail/limristem-mail.key"
    return {
        "mode": ssl_mode,
        "hostname": hostname,
        "le_email": os.getenv("LIMRISTEM_MAIL_LE_EMAIL", f"postmaster@{primary_domain}"),
        "cert_path": cert_path,
        "key_path": key_path,
        "cert_exists": bool(cert_path and Path(cert_path).is_file()),
        "key_exists": bool(key_path and Path(key_path).is_file()),
        "status": "ready"
        if cert_path and key_path and Path(cert_path).is_file() and Path(key_path).is_file()
        else ("plain" if ssl_mode == "plain" else "missing"),
        "nginx_enabled": os.getenv("LIMRISTEM_MAIL_ENABLE_NGINX", "no").strip().lower() in {"1", "true", "yes", "on"},
        "mta_sts_enabled": os.getenv("LIMRISTEM_MAIL_ENABLE_MTA_STS", "yes").strip().lower() in {"1", "true", "yes", "on"},
    }


def load_panel_settings() -> dict[str, object]:
    return load_script_json("manage-settings.sh", "show", "--json", default={})


def check_for_updates(*, apply_if_enabled: bool = False) -> dict[str, object]:
    command = ["check-update"]
    if apply_if_enabled:
        command.append("--apply-if-enabled")
    command.append("--json")
    return load_script_json("manage-settings.sh", *command, default={})


def apply_update_now() -> dict[str, object]:
    return load_script_json("manage-settings.sh", "apply-update", "--json", default={})


def regenerate_api_credentials() -> dict[str, str]:
    return load_script_json("manage-api-credentials.sh", "regenerate", "--json", default={})


def panel_template_dir() -> Path:
    installed_panel_dir = settings.base_dir / "bin" / "panel"
    if installed_panel_dir.is_dir():
        return installed_panel_dir
    return Path(__file__).resolve().parent / "templates"
