#!/usr/bin/env python3
"""
Point d'entrée principal de l'orchestrateur Skull Pi.
Démarre FastAPI et la boucle de machine d'états.
"""

import asyncio
import logging
import signal
import sys
import traceback
from pathlib import Path
from typing import Any

import uvicorn
from fastapi import FastAPI

from orchestrator.config import Config
from orchestrator.health import HealthMonitor
from orchestrator.mqtt import MqttClient
from orchestrator.rest import create_app
from orchestrator.scheduler import FullAutoScheduler
from orchestrator.services import ServiceManager
from orchestrator.state_machine import StateMachine


class OrchestratorApp:
    """Application principale orchestrateur."""

    def __init__(self):
        self.config = Config()
        self.logger = self._setup_logging()
        self.mqtt_client: MqttClient | None = None
        self.state_machine: StateMachine | None = None
        self.service_manager: ServiceManager | None = None
        self.health_monitor: HealthMonitor | None = None
        self.scheduler: FullAutoScheduler | None = None
        self.rest_app: FastAPI | None = None
        self.running = False

    def _setup_logging(self) -> logging.Logger:
        """Configure le logging JSON vers fichier."""
        log_path = Path("/opt/Skull/logs")
        log_path.mkdir(parents=True, exist_ok=True)

        logger = logging.getLogger("orchestrator")
        logger.setLevel(logging.INFO)

        # Handler fichier avec format JSON
        file_handler = logging.FileHandler(log_path / "orchestrator.log")
        formatter = logging.Formatter(
            '{"timestamp":"%(asctime)s","level":"%(levelname)s",'
            '"module":"%(name)s","message":"%(message)s"}'
        )
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)

        # Handler console
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(
            logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
        )
        logger.addHandler(console_handler)

        return logger

    def _get_api_port(self) -> int:
        """Récupère et normalise le port API (force int)."""
        try:
            api_cfg = self.config.get("api", {})
            port = api_cfg.get("port", 8080)
            # Certains formats de config peuvent mettre un dict du type {"value": 8080}
            if isinstance(port, dict):
                # essaie clés usuelles
                for k in ("value", "port", "default", "val"):
                    if k in port:
                        port = port[k]
                        break
            return int(port)
        except Exception as e:
            self.logger.warning(
                f"Port API invalide dans la config, fallback 8080 ({e})"
            )
            return 8080

    async def initialize(self) -> None:
        """Initialise tous les composants."""
        self.logger.info("Initialisation de l'orchestrateur...")

        # Initialiser les composants (avec logs précis étape par étape)
        try:
            self.service_manager = ServiceManager()
            self.logger.info("ServiceManager initialisé")
        except Exception:
            self.logger.error("Échec init ServiceManager:\n" + traceback.format_exc())
            raise

        try:
            self.mqtt_client = MqttClient(self.config)
            self.logger.info("MqttClient initialisé")
        except Exception:
            self.logger.error("Échec init MqttClient:\n" + traceback.format_exc())
            raise

        try:
            self.state_machine = StateMachine(
                mqtt_client=self.mqtt_client,
                service_manager=self.service_manager,
                config=self.config,
            )
            self.logger.info("StateMachine initialisée")
        except Exception:
            self.logger.error("Échec init StateMachine:\n" + traceback.format_exc())
            raise

        try:
            self.health_monitor = HealthMonitor(
                service_manager=self.service_manager,
                mqtt_client=self.mqtt_client,
                logger=self.logger,
            )
            self.logger.info("HealthMonitor initialisé")
        except Exception:
            self.logger.error("Échec init HealthMonitor:\n" + traceback.format_exc())
            raise

        try:
            self.scheduler = FullAutoScheduler(
                state_machine=self.state_machine, config=self.config, logger=self.logger
            )
            self.logger.info("FullAutoScheduler initialisé")
        except Exception:
            self.logger.error(
                "Échec init FullAutoScheduler:\n" + traceback.format_exc()
            )
            raise

        try:
            self.rest_app = create_app(
                state_machine=self.state_machine,
                service_manager=self.service_manager,
                config=self.config,
                scheduler=self.scheduler,
            )
            if not isinstance(self.rest_app, FastAPI):
                self.logger.warning(
                    f"create_app n'a pas retourné un FastAPI: {type(self.rest_app)}"
                )
            self.logger.info("FastAPI app créée")
        except Exception:
            self.logger.error("Échec création FastAPI app:\n" + traceback.format_exc())
            raise

        # Connecter MQTT
        try:
            await self.mqtt_client.connect()  # type: ignore[union-attr]
            self.logger.info("MQTT connecté")
        except Exception:
            self.logger.error("Échec connexion MQTT:\n" + traceback.format_exc())
            raise

        # Démarrer la machine d'états
        try:
            await self.state_machine.start()  # type: ignore[union-attr]
            self.logger.info("StateMachine démarrée")
        except Exception:
            self.logger.error(
                "Échec démarrage StateMachine:\n" + traceback.format_exc()
            )
            raise

        # Démarrer health monitor
        try:
            await self.health_monitor.start()  # type: ignore[union-attr]
            self.logger.info("HealthMonitor démarré")
        except Exception:
            self.logger.error(
                "Échec démarrage HealthMonitor:\n" + traceback.format_exc()
            )
            raise

        # Publier état initial
        try:
            await self.mqtt_client.publish("skull/mode", "Idle", retain=True)  # type: ignore[union-attr]
            await self.mqtt_client.publish("orchestrator/state", "Idle", retain=True)  # type: ignore[union-attr]
        except Exception:
            self.logger.error(
                "Échec publication MQTT initiale:\n" + traceback.format_exc()
            )
            raise

        self.logger.info("Orchestrateur initialisé avec succès")

    async def run(self) -> None:
        """Lance l'application principale."""
        await self.initialize()
        self.running = True

        # Configuration signal handlers
        def signal_handler(signum: int, frame: Any) -> None:
            self.logger.info(f"Signal {signum} reçu, arrêt en cours...")
            self.running = False

        signal.signal(signal.SIGTERM, signal_handler)
        signal.signal(signal.SIGINT, signal_handler)

        # Démarrer le serveur FastAPI en arrière-plan
        api_port = self._get_api_port()
        self.logger.info(f"Démarrage API REST sur 0.0.0.0:{api_port}")

        config = uvicorn.Config(
            app=self.rest_app,  # ASGI app
            host="0.0.0.0",
            port=api_port,  # ⟵ forcé en int par _get_api_port()
            log_level="info",
        )
        server = uvicorn.Server(config)

        # Tâches principales
        tasks = [
            asyncio.create_task(server.serve(), name="uvicorn"),
            asyncio.create_task(self._main_loop(), name="main_loop"),
        ]

        try:
            await asyncio.gather(*tasks)
        except Exception:
            self.logger.error(
                "Erreur dans la boucle principale:\n" + traceback.format_exc()
            )
        finally:
            await self.cleanup()

    async def _main_loop(self) -> None:
        """Boucle principale de traitement."""
        while self.running:
            try:
                # Traiter les messages MQTT
                if self.mqtt_client:
                    await self.mqtt_client.process_messages()

                # Vérifier scheduler Full Auto
                if self.scheduler:
                    await self.scheduler.check()

                await asyncio.sleep(0.1)  # 10Hz

            except Exception:
                self.logger.error(
                    "Erreur dans la boucle principale (iteration):\n"
                    + traceback.format_exc()
                )
                await asyncio.sleep(1)

    async def cleanup(self) -> None:
        """Nettoyage avant arrêt."""
        self.logger.info("Nettoyage de l'orchestrateur...")

        try:
            if self.health_monitor:
                await self.health_monitor.stop()
        except Exception:
            self.logger.warning(
                "Erreur à l'arrêt HealthMonitor:\n" + traceback.format_exc()
            )

        try:
            if self.state_machine:
                await self.state_machine.stop()
        except Exception:
            self.logger.warning(
                "Erreur à l'arrêt StateMachine:\n" + traceback.format_exc()
            )

        try:
            if self.mqtt_client:
                await self.mqtt_client.disconnect()
        except Exception:
            self.logger.warning(
                "Erreur à la déconnexion MQTT:\n" + traceback.format_exc()
            )

        self.logger.info("Orchestrateur arrêté")


async def main() -> None:
    """Point d'entrée principal."""
    app = OrchestratorApp()
    try:
        await app.run()
    except KeyboardInterrupt:
        print("\nArrêt demandé par l'utilisateur")
    except Exception:
        # Affiche le traceback complet en console pour un diag rapide
        traceback.print_exc()
        print("Erreur fatale (voir traceback ci-dessus).", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    asyncio.run(main())
