"""Systemeinstellungen von TESM.

Netzwerk, Webserver/TLS und Verzeichnisdienst liegen im Kern
(``tesm_core.views.hostsettings``) -- sie sind in beiden Anwendungen identisch.
Hier steht nur, was TESM eigen ist: Ueberwachung, Konsole, Protokolle, Host.
"""

from __future__ import annotations

import threading
import time
from typing import Any

from flask import Blueprint, flash, redirect, render_template, request, url_for
from tesm_core import audit
from tesm_core.auth.models import current_user, permission_required
from tesm_core.auth.service import client_ip
from tesm_core.extension import core, db, transaction
from tesm_core.licensing_client import LicenseError

bp = Blueprint("settings", __name__, url_prefix="/einstellungen")


def _audit(action: str, target: str = "", detail: str = "", severity: str = "info") -> None:
    with transaction() as conn:
        audit.log(
            conn,
            actor=current_user.username,
            actor_id=current_user.id,
            action=action,
            target=target,
            detail=detail,
            ip=client_ip(),
            severity=severity,
        )


def _license_hint(feature: str) -> str:
    manager = core().license
    if manager is None:
        return ""
    try:
        manager.require(feature)
    except LicenseError as exc:
        return str(exc)
    return ""


def _current_timezone() -> str:
    sysops = core().sysops
    if sysops is None:
        return ""
    return sysops.read_command(
        ["timedatectl", "show", "--property=Timezone", "--value"]
    ).stdout.strip()


def _timezone_choices() -> list[str]:
    try:
        from zoneinfo import available_timezones

        return sorted(available_timezones())
    except Exception:  # noqa: BLE001
        return ["Europe/Berlin", "UTC"]


@bp.get("/system")
@permission_required("settings_system.view")
def system():
    extension = core()
    conn = db()
    sysops = extension.sysops
    return render_template(
        "tesm/settings_system.html",
        values=extension.settings.get_many(
            conn,
            [
                "monitor_interval_seconds",
                "monitor_failures_before_restart",
                "monitor_auto_restart",
                "dashboard_public",
                "terminal_enabled",
                "terminal_idle_timeout",
                "trash_retention_days",
                "log_rotate_interval",
                "log_rotate_keep",
            ],
        ),
        specs={spec.key: spec for spec in extension.settings.specs()},
        hostname=(sysops.read_command(["hostname"]).stdout.strip() if sysops else ""),
        timezone=_current_timezone(),
        timezones=_timezone_choices(),
        nav_items=extension.nav.items,
        nav_order=extension.settings.get(conn, "nav_order") or extension.nav.default_order(),
        license_hint=_license_hint("monitor_interval"),
        sysops_available=bool(sysops and sysops.available),
    )


@bp.post("/system")
@permission_required("settings_system.edit")
def system_save():
    extension = core()
    conn = db()
    form = request.form

    values: dict[str, Any] = {}
    for key in (
        "monitor_failures_before_restart",
        "monitor_auto_restart",
        "dashboard_public",
        "terminal_enabled",
        "terminal_idle_timeout",
        "trash_retention_days",
        "log_rotate_interval",
        "log_rotate_keep",
    ):
        spec = extension.settings.spec(key)
        if spec.kind == "bool":
            # Nur beruecksichtigen, wenn das Formular diesen Bereich ueberhaupt
            # enthaelt -- sonst wuerde ein Teilformular fremde Schalter loeschen.
            if f"{key}__present" in form or key in form:
                values[key] = form.get(key) == "1"
        elif key in form:
            values[key] = form.get(key)

    interval_changed = False
    if "monitor_interval_seconds" in form:
        problem = _license_hint("monitor_interval")
        current = str(extension.settings.get(conn, "monitor_interval_seconds") or 300)
        wanted = str(form.get("monitor_interval_seconds"))
        if current != wanted:
            if problem:
                flash(problem, "danger")
            else:
                values["monitor_interval_seconds"] = wanted
                interval_changed = True

    with transaction():
        changed = extension.settings.set_many(conn, values, actor=current_user.username)
        if changed:
            audit.log(
                conn,
                actor=current_user.username,
                actor_id=current_user.id,
                action="settings.system_saved",
                detail=", ".join(changed),
                ip=client_ip(),
                severity="notice",
            )
    if changed:
        message = "Einstellungen gespeichert."
        if interval_changed:
            message += " Das Pruefintervall greift beim naechsten Durchlauf."
        flash(message, "success")
    else:
        flash("Keine Aenderungen.", "info")
    return redirect(url_for("settings.system"))


@bp.post("/system/hostname")
@permission_required("settings_system.execute")
def set_hostname():
    extension = core()
    sysops = extension.sysops
    hostname = (request.form.get("hostname") or "").strip()
    if not hostname or len(hostname) > 63 or not hostname.replace("-", "").isalnum():
        flash("Der Hostname ist ungueltig.", "danger")
        return redirect(url_for("settings.system"))
    if sysops is None or not sysops.available:
        flash("Systemaktionen sind in dieser Umgebung nicht verfuegbar.", "warning")
        return redirect(url_for("settings.system"))
    result = sysops.set_hostname(hostname)
    if result.ok:
        with transaction() as conn:
            extension.settings.set(conn, "hostname", hostname, actor=current_user.username)
        _audit("settings.hostname_changed", hostname, severity="notice")
        flash(f"Hostname auf {hostname} gesetzt.", "success")
    else:
        flash(f"Hostname konnte nicht gesetzt werden: {result.message}", "danger")
    return redirect(url_for("settings.system"))


@bp.post("/system/zeitzone")
@permission_required("settings_system.execute")
def set_timezone():
    extension = core()
    sysops = extension.sysops
    timezone = (request.form.get("timezone") or "").strip()
    if timezone not in _timezone_choices():
        flash("Unbekannte Zeitzone.", "danger")
        return redirect(url_for("settings.system"))
    if sysops is None or not sysops.available:
        flash("Systemaktionen sind in dieser Umgebung nicht verfuegbar.", "warning")
        return redirect(url_for("settings.system"))
    result = sysops.set_timezone(timezone)
    if result.ok:
        # Sofort auf den laufenden Prozess wirken lassen -- sonst zeigt die
        # Oberflaeche bis zum naechsten Neustart die alte Zeit.
        try:
            time.tzset()  # type: ignore[attr-defined]
        except AttributeError:  # pragma: no cover - Windows
            pass
        with transaction() as conn:
            extension.settings.set(conn, "timezone", timezone, actor=current_user.username)
        _audit("settings.timezone_changed", timezone, severity="notice")
        flash(f"Zeitzone auf {timezone} gesetzt.", "success")
    else:
        flash(f"Zeitzone konnte nicht gesetzt werden: {result.message}", "danger")
    return redirect(url_for("settings.system"))


@bp.post("/system/pruefung-jetzt")
@permission_required("settings_system.execute")
def run_check_now():
    """Startet einen Ueberwachungsdurchlauf sofort, im Hintergrund."""
    extension = core()

    def worker() -> None:
        from ..services import inventory, monitor

        allowed = extension.license is None or extension.license.feature_allowed(
            "auto_poe_restart"
        )
        monitor.run_sweep(
            database=extension.database,
            live=extension.registry["live_log"],
            keystore=extension.keystore,
            license_allows_auto_restart=allowed,
        )

    threading.Thread(target=worker, name="manual-sweep", daemon=True).start()
    _audit("monitor.manual_sweep")
    flash("Durchlauf gestartet. Das Ergebnis erscheint im laufenden Protokoll.", "success")
    return redirect(request.referrer or url_for("settings.system"))
