"""High-level status monitor that bridges SelfDiagnosisEngine to FleetManager.

Reads fleet configuration from a JSON file and periodically sends
diagnostics and status to the central fleet HTTPS API.
"""

import json
import logging
import os
import threading
from typing import Any, Dict, List, Optional, Tuple

from brinkhaustools.common.fleet.client import FleetMonitorClient
from brinkhaustools.common.status import StatusSource

log = logging.getLogger(__name__)

_DIAG_CONFIG_MISSING = 7001
_DIAG_CONNECTION_ERROR = 7002


class StatusMonitor(StatusSource):
    """Bridges self-diagnosis messages to the BrinkhausFleetManager.

    Parameters
    ----------
    config_path : str or None
        Path to ``config.json`` with fleet settings.  *None* disables
        config-file loading (all params must be given explicitly).
    diagnosis : object, optional
        A ``SelfDiagnosisEngine`` instance (or any object with
        ``get_status()``, ``notify()``, ``clear()``).
    software_name : str
        Identifier for this software in the fleet hierarchy.
    version : str, optional
        Product version string.
    shutdown_handler : object, optional
        A ``ShutdownHandler`` for interruptible sleeping.
    """

    def __init__(
        self,
        config_path: Optional[str] = "fleetManagementData/config.json",
        diagnosis=None,
        software_name: str = "unknown",
        version: Optional[str] = None,
        shutdown_handler=None,
        status_engine=None,
    ) -> None:
        super().__init__(refresh_time_sec=60)
        self._diagnosis = diagnosis
        self._shutdown = shutdown_handler
        self._software_name = software_name
        self._status_engine = status_engine
        self._config_path = config_path

        fleet_cfg = self._read_config(config_path) if config_path else {}
        self._config_loaded = bool(fleet_cfg)

        # Derive base_url: prefer explicit base_url, fall back to broker
        base_url: Optional[str] = fleet_cfg.get("base_url")
        if not base_url:
            broker = str(fleet_cfg.get("broker", "fleet.brinkhaus-gmbh.de"))
            base_url = f"https://{broker}"

        customer = os.environ.get(
            "STATUS_MONITOR_CUSTOMERID",
            str(fleet_cfg.get("customer_name", "testCustomer")),
        )
        machine = os.environ.get(
            "STATUS_MONITOR_MACHINEID",
            str(fleet_cfg.get("machine", "testMachine")),
        )
        heartbeat_interval = int(fleet_cfg.get("heartbeat_interval_sec", 60))
        token: Optional[str] = fleet_cfg.get("token") or None

        self._server_display = base_url

        self._client = FleetMonitorClient(
            base_url=base_url,
            customer=customer,
            machine=machine,
            software=software_name,
            version=version,
            heartbeat_interval=heartbeat_interval,
            token=token,
        )

        self._client._on_connection_change = self._on_connection_change

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------
    def start(self) -> None:
        self._client.start()

        if self._shutdown:
            self._shutdown.register_hook(self.stop)

        self._controller = threading.Thread(
            target=self._controller_loop,
            name="FleetManagerController",
            daemon=True,
        )
        self._controller.start()

    def stop(self) -> None:
        self._client.stop()

    def get_status(self) -> Dict[str, Any]:
        result: Dict[str, Any] = {
            "Connected": self._client.is_connected,
            "Server": self._server_display,
        }
        if self._client._last_send_attempt:
            result["Last send attempt"] = self._client._last_send_attempt
        if self._client._last_send_success:
            result["Last send success"] = self._client._last_send_success
        if not self._client.is_connected and self._client._last_connect_error:
            result["Error"] = self._client._last_connect_error
        return result

    def get_name(self) -> str:
        return "FleetManager"

    # ------------------------------------------------------------------
    # Internals
    # ------------------------------------------------------------------
    def _read_config(self, path: str) -> Dict[str, Any]:
        try:
            with open(path) as f:
                cfg = json.load(f)
            if self._diagnosis:
                self._diagnosis.clear(_DIAG_CONFIG_MISSING)
            return dict(cfg.get("fleetmanagement", {}))
        except Exception:
            log.warning("Could not read %s, using defaults", path)
            if self._diagnosis:
                self._diagnosis.notify(
                    _DIAG_CONFIG_MISSING,
                    f"FleetManager config not found: {path}",
                    information=True,
                )
            return {}

    def _try_reload_config(self) -> None:
        """Retry reading the config file if the initial load failed."""
        if self._config_loaded or not self._config_path:
            return
        fleet_cfg = self._read_config(self._config_path)
        if not fleet_cfg:
            return
        self._config_loaded = True
        log.info("Fleet config loaded on retry from %s", self._config_path)

        base_url: Optional[str] = fleet_cfg.get("base_url")
        if not base_url:
            broker = str(fleet_cfg.get("broker", "fleet.brinkhaus-gmbh.de"))
            base_url = f"https://{broker}"

        customer = os.environ.get(
            "STATUS_MONITOR_CUSTOMERID",
            str(fleet_cfg.get("customer_name", "testCustomer")),
        )
        machine = os.environ.get(
            "STATUS_MONITOR_MACHINEID",
            str(fleet_cfg.get("machine", "testMachine")),
        )
        token: Optional[str] = fleet_cfg.get("token") or None

        self._server_display = base_url
        self._client.reconfigure(
            base_url=base_url,
            customer=customer,
            machine=machine,
            token=token,
        )

    def _controller_loop(self) -> None:
        # Initial delay before first diagnostics run
        if self._shutdown:
            self._shutdown.sleep(10)
        else:
            import time
            time.sleep(10)

        while True:
            if self._shutdown and self._shutdown.is_shutdown():
                break
            try:
                self._try_reload_config()
                self._update_diagnosis()
                diagnostics = self._map_diagnosis()
                self._client.send_diagnostics(diagnostics)

                # Status ableiten
                status_str = "running"
                message = "All OK"
                if self._diagnosis:
                    diag_status = self._diagnosis.get_status()
                    if not diag_status.get("System Status", True):
                        status_str = "degraded"
                        message = "Issues detected"

                # Snapshot senden wenn StatusEngine verfügbar, sonst einfacher Status
                if self._status_engine:
                    snapshot = self._status_engine.get_last_status()
                    self._client.send_snapshot(snapshot, status=status_str, message=message)
                else:
                    self._client.send_status(status_str, message)
            except Exception as exc:
                log.error("Error in FleetManager controller: %s", exc)

            if self._shutdown:
                self._shutdown.sleep(60)
            else:
                import time
                time.sleep(60)

    def _on_connection_change(self, connected: bool) -> None:
        self._update_diagnosis()

    def _update_diagnosis(self) -> None:
        if not self._diagnosis:
            return
        if self._client.is_connected:
            self._diagnosis.clear(_DIAG_CONNECTION_ERROR)
        else:
            error = self._client._last_connect_error or "Not connected"
            self._diagnosis.notify(
                _DIAG_CONNECTION_ERROR,
                f"FleetManager: {error}",
                critical=True,
            )

    def _map_diagnosis(self) -> List[Tuple[int, str, int]]:
        if not self._diagnosis:
            return [(0, "All self tests passed", 0)]

        status = self._diagnosis.get_status()
        messages = status.get("messages", [])
        if not messages:
            return [(0, "All self tests passed", 0)]

        result: List[Tuple[int, str, int]] = []
        for msg in messages:
            code = msg.get("code", 0)
            text = msg.get("messageForUI", "Unknown")
            critical = msg.get("critical", False)
            information = msg.get("information", False)
            severity = 2 if critical else (0 if information else 1)
            result.append((code, text, severity))
        return result
