"""Ueberwachungsprozess: prueft Geraete, schaltet PoE, raeumt auf.

Laeuft als eigene systemd-Unit (``tesm-monitor.service``), getrennt vom
Webprozess. Ein Absturz der Weboberflaeche stoppt die Ueberwachung damit nicht --
und umgekehrt.

Ersetzt ``poe.sh`` + ``poe_wrapper.py`` + ``generate_ips.py`` + den
``tesm-check-restart``-Timer des Vorgaengers. Den Timer braucht es nicht mehr:
das Intervall wird bei **jedem** Durchlauf frisch aus der Datenbank gelesen,
eine Aenderung greift also beim naechsten Lauf statt erst nach einem
naechtlichen Neustart des Dienstes.
"""

from __future__ import annotations

import logging
import signal
import sys
import time
from types import FrameType

from tesm_core.extension import core

from . import create_app
from .services import inventory, monitor

logger = logging.getLogger("tesm.monitor")

MIN_INTERVAL = 30


class MonitorDaemon:
    def __init__(self) -> None:
        self.app = create_app()
        self.extension = core(self.app)
        self.running = True

    def stop(self, signum: int, _frame: FrameType | None) -> None:
        logger.info("Signal %s empfangen -- beende nach dem laufenden Durchlauf.", signum)
        self.running = False

    # ------------------------------------------------------------------ #

    def _interval(self) -> int:
        with self.extension.database.session() as conn:
            value = int(
                self.extension.settings.get(conn, "monitor_interval_seconds") or 300
            )
        return max(MIN_INTERVAL, value)

    def _auto_restart_allowed(self) -> bool:
        settings_on = True
        with self.extension.database.session() as conn:
            settings_on = bool(self.extension.settings.get(conn, "monitor_auto_restart"))
            if self.extension.license is not None:
                self.extension.license.reload(conn)
        if not settings_on:
            return False
        if self.extension.license is None:
            return True
        return self.extension.license.feature_allowed("auto_poe_restart")

    def _failures_before_restart(self) -> int:
        with self.extension.database.session() as conn:
            return int(
                self.extension.settings.get(conn, "monitor_failures_before_restart") or 2
            )

    def run(self) -> int:
        live = self.extension.registry["live_log"]
        live.info("monitor", "Ueberwachungsprozess gestartet")
        logger.info("Ueberwachung laeuft, Datenbank: %s", self.extension.config.db_path)

        while self.running:
            interval = self._interval()
            started = time.monotonic()
            try:
                with self.app.app_context():
                    report = monitor.run_sweep(
                        database=self.extension.database,
                        live=live,
                        keystore=self.extension.keystore,
                        license_allows_auto_restart=self._auto_restart_allowed(),
                        failures_before_restart=self._failures_before_restart(),
                    )
                logger.info(
                    "Durchlauf: %s geprueft, %s online, %s offline, %s Neustarts (%s erfolgreich), "
                    "%s ohne Lizenz uebersprungen, %sms",
                    report.checked,
                    report.online,
                    report.offline,
                    report.restarts_triggered,
                    report.restarts_ok,
                    report.skipped_unlicensed,
                    report.duration_ms,
                )
            except Exception:  # noqa: BLE001 - der Dienst darf nie sterben
                logger.exception("Durchlauf fehlgeschlagen")
                live.error("monitor", "Durchlauf fehlgeschlagen -- siehe app.log")

            # Das Aufraeumen abgelaufener Freigabe-Einbindungen laeuft **nicht**
            # hier, sondern im Webprozess: eingebunden wird in dessen
            # Mount-Namensraum, und dieser Dienst sieht diese Einbindungen nicht
            # einmal. Ein umount von hier waere ein Aufruf ins Leere -- die
            # Datenbankzeile als geloest markiert, die Freigabe aber weiter
            # eingebunden. Siehe tesm/__init__.py::_start_fileshare_sweep.

            elapsed = time.monotonic() - started
            remaining = max(1.0, interval - elapsed)
            # In Sekundenschritten warten, damit ein Stoppsignal schnell greift.
            while remaining > 0 and self.running:
                time.sleep(min(1.0, remaining))
                remaining -= 1.0

        live.info("monitor", "Ueberwachungsprozess beendet")
        return 0


def main() -> int:
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)-7s %(name)s: %(message)s",
        stream=sys.stderr,
    )
    daemon = MonitorDaemon()
    signal.signal(signal.SIGTERM, daemon.stop)
    signal.signal(signal.SIGINT, daemon.stop)
    return daemon.run()


if __name__ == "__main__":  # pragma: no cover
    raise SystemExit(main())
