"""Central ``App`` object that wires all common components together.

Pass ``None`` for any component parameter to skip its initialisation.

Usage::

    from brinkhaustools.common import App

    app = App(
        name="my-service",
        settings_path="data/settings/settings_main.json",
        fleet_config_path="fleetManagementData/config.json",
        version_file="helper/version.json",
    )

    app.settings.get(["expert", "timeout"], default=300)
    app.diagnosis.notify(code=1001, message="DB down", critical=True)
    app.shutdown.trigger()
"""

import logging
from typing import Optional

from brinkhaustools.common.shutdown import ShutdownHandler
from brinkhaustools.common.logging import LoggingHelper
from brinkhaustools.common.settings import Settings
from brinkhaustools.common.diagnosis import SelfDiagnosisEngine
from brinkhaustools.common.status import StatusEngine
from brinkhaustools.common.heartbeat import HeartbeatEngine
from brinkhaustools.common.version import VersionInformation
from brinkhaustools.common.fleet.monitor import StatusMonitor

_SENTINEL = object()  # distinguish "not provided" from explicit None


class App:
    """Central wiring point for all brinkhaustools components.

    Every parameter defaults to automatic initialisation.  Pass ``None``
    explicitly to skip a component entirely (the attribute will be ``None``).

    Parameters
    ----------
    name : str
        Application name, used as fleet *software* identifier.
    settings_path : str or None
        Path to settings JSON.  ``None`` → no settings.
    log_dir : str or None
        Directory for log files.  ``None`` → no file logging.
    version_file : str or None
        Path to ``version.json``.  ``None`` → no version tracking.
    fleet_config_path : str or None
        Path to fleet management config.  ``None`` → no fleet reporting.
    heartbeat_timeout_sec : int
        Default heartbeat watchdog timeout.
    install_signal_handlers : bool
        Whether the shutdown handler installs SIGTERM/SIGINT handlers.
    """

    _instance: Optional["App"] = None

    def __init__(
        self,
        name: str = "brinkhaustools-app",
        settings_path: object = _SENTINEL,
        log_dir: object = _SENTINEL,
        version_file: object = _SENTINEL,
        fleet_config_path: object = _SENTINEL,
        heartbeat_timeout_sec: int = 300,
        install_signal_handlers: bool = True,
    ) -> None:
        self.name = name

        # -- Shutdown (always created unless explicitly None) --
        self.shutdown: Optional[ShutdownHandler] = ShutdownHandler(
            install_signal_handlers=install_signal_handlers,
        )

        # -- Logging --
        if log_dir is _SENTINEL:
            log_dir = "data/logs/"
        if log_dir is not None:
            self.logging: Optional[LoggingHelper] = LoggingHelper(log_dir=log_dir)
        else:
            self.logging = None

        self.log = logging.getLogger(name)

        # -- Settings --
        if settings_path is _SENTINEL:
            settings_path = "data/settings/settings_main.json"
        if settings_path is not None:
            self.settings: Optional[Settings] = Settings(file_path=settings_path)
        else:
            self.settings = None

        # -- Diagnosis --
        self.diagnosis: Optional[SelfDiagnosisEngine] = SelfDiagnosisEngine()

        # -- Status engine --
        self.status: Optional[StatusEngine] = StatusEngine()
        if self.diagnosis:
            self.status.register_source(self.diagnosis)

        # -- Heartbeat --
        self.heartbeat: Optional[HeartbeatEngine] = HeartbeatEngine(
            default_timeout_sec=heartbeat_timeout_sec,
        )
        if self.status:
            self.status.register_source(self.heartbeat)

        # -- Version --
        if version_file is _SENTINEL:
            version_file = "helper/version.json"
        if version_file is not None:
            self.version: Optional[VersionInformation] = VersionInformation(
                file_path=version_file,
            )
            if self.status:
                self.status.register_source(self.version)
        else:
            self.version = None

        # -- Fleet monitor --
        if fleet_config_path is _SENTINEL:
            fleet_config_path = "fleetManagementData/config.json"
        if fleet_config_path is not None:
            version_str = self.version.version if self.version else None
            self.fleet: Optional[StatusMonitor] = StatusMonitor(
                config_path=fleet_config_path,
                diagnosis=self.diagnosis,
                software_name=name,
                version=version_str,
                shutdown_handler=self.shutdown,
            )
            if self.status:
                self.status.register_source(self.fleet)
        else:
            self.fleet = None

        # Store as singleton
        App._instance = self

    # ------------------------------------------------------------------
    # Singleton
    # ------------------------------------------------------------------
    @staticmethod
    def get_instance() -> Optional["App"]:
        return App._instance

    # ------------------------------------------------------------------
    # Lifecycle
    # ------------------------------------------------------------------
    def start(self) -> None:
        """Start all background workers (status collection, heartbeat, fleet)."""
        if self.status:
            self.status.start(shutdown_handler=self.shutdown)
        if self.heartbeat:
            self.heartbeat.start(shutdown_handler=self.shutdown)
        if self.fleet:
            self.fleet.start()
        self.log.info("App '%s' started", self.name)

    def stop(self) -> None:
        """Trigger shutdown and stop all workers."""
        if self.shutdown:
            self.shutdown.trigger()
        if self.fleet:
            self.fleet.stop()
        if self.heartbeat:
            self.heartbeat.stop()
        if self.status:
            self.status.stop()
        self.log.info("App '%s' stopped", self.name)

    def wait(self) -> None:
        """Block until shutdown is triggered."""
        if self.shutdown:
            self.shutdown.wait()
