"""
Machine d'états pour l'orchestrateur Skull Pi.
Gère les transitions entre états selon les événements MQTT.
"""

import asyncio
import json
import logging
from enum import Enum
from typing import Any, Dict, Optional

from orchestrator.config import Config
from orchestrator.mqtt import MqttClient
from orchestrator.services import ServiceManager


class State(Enum):
    """États possibles de la machine d'états."""

    IDLE = "Idle"
    TRACK = "Track"
    TALK = "Talk"
    SING = "Sing"
    LISTEN = "Listen"
    THINK = "Think"
    SPEAK = "Speak"
    SLEEP = "Sleep"


class Mode(Enum):
    """Modes de fonctionnement."""

    ACCUEIL = "Accueil"
    CHANSON = "Chanson"
    IA = "IA"
    FULL_AUTO = "FullAuto"


class StateMachine:
    """Machine d'états principale."""

    def __init__(
        self, mqtt_client: MqttClient, service_manager: ServiceManager, config: Config
    ):
        self.mqtt_client = mqtt_client
        self.service_manager = service_manager
        self.config = config
        self.logger = logging.getLogger("orchestrator.state_machine")

        self.current_state = State.IDLE
        self.current_mode = Mode.ACCUEIL
        self.running = False

        # Variables d'état
        self.motion_detected = False
        self.motion_score = 0.0
        self.vad_active = False
        self.asr_text: Optional[str] = None
        self.ai_response: Optional[str] = None
        self.voice_state = "idle"

        # Timers
        self.talk_timeout_task: Optional[asyncio.Task] = None
        self.listen_timeout_task: Optional[asyncio.Task] = None

    async def start(self) -> None:
        """Démarre la machine d'états."""
        self.running = True

        # S'abonner aux topics MQTT
        await self.mqtt_client.subscribe("vision/motion", self._on_vision_motion)
        await self.mqtt_client.subscribe("vision/pose", self._on_vision_pose)
        await self.mqtt_client.subscribe("audio/vad", self._on_audio_vad)
        await self.mqtt_client.subscribe("asr/text", self._on_asr_text)
        await self.mqtt_client.subscribe("ai/response", self._on_ai_response)
        await self.mqtt_client.subscribe("voice/state", self._on_voice_state)
        await self.mqtt_client.subscribe("skull/mode", self._on_mode_change)
        await self.mqtt_client.subscribe("motion/estop", self._on_estop)

        self.logger.info("Machine d'états démarrée")

    async def stop(self) -> None:
        """Arrête la machine d'états."""
        self.running = False

        # Annuler les timers
        if self.talk_timeout_task:
            self.talk_timeout_task.cancel()
        if self.listen_timeout_task:
            self.listen_timeout_task.cancel()

        self.logger.info("Machine d'états arrêtée")

    async def _transition_to(self, new_state: State) -> None:
        """Effectue une transition vers un nouvel état."""
        if new_state == self.current_state:
            return

        old_state = self.current_state
        self.current_state = new_state

        self.logger.info(f"Transition: {old_state.value} → {new_state.value}")

        # Publier le nouvel état
        await self.mqtt_client.publish(
            "orchestrator/state", new_state.value, retain=True
        )

        # Actions d'entrée dans l'état
        await self._on_enter_state(new_state)

    async def _on_enter_state(self, state: State) -> None:
        """Actions à effectuer lors de l'entrée dans un état."""
        if state == State.IDLE:
            # Centrer les moteurs
            await self.mqtt_client.publish(
                "motion/servo/pan", json.dumps({"position": 0})
            )
            await self.mqtt_client.publish(
                "motion/servo/tilt", json.dumps({"position": 0})
            )
            await self.mqtt_client.publish(
                "motion/servo/jaw", json.dumps({"position": 0})
            )

        elif state == State.TRACK:
            # Démarrer le suivi vision si pas déjà fait
            if not self.service_manager.is_service_running("skull-vision"):
                await self.service_manager.start_service("skull-vision")

        elif state == State.TALK:
            # Démarrer TTS d'accueil
            message = self.config.get("accueil", {}).get(
                "message", "Bonjour ! Je suis Skull Pi."
            )
            await self.mqtt_client.publish(
                "voice/speak", json.dumps({"text": message, "priority": "high"})
            )

            # Timeout pour revenir à IDLE
            self.talk_timeout_task = asyncio.create_task(self._talk_timeout())

        elif state == State.SING:
            # Jouer une chanson
            await self._start_song()

        elif state == State.LISTEN:
            # Démarrer ASR
            if not self.service_manager.is_service_running("skull-asr"):
                await self.service_manager.start_service("skull-asr")

            # Timeout d'écoute
            self.listen_timeout_task = asyncio.create_task(self._listen_timeout())

        elif state == State.THINK:
            # Envoyer à l'IA
            if self.asr_text:
                await self.mqtt_client.publish(
                    "ai/query",
                    json.dumps({"text": self.asr_text, "context": "conversation"}),
                )

        elif state == State.SPEAK:
            # TTS de la réponse IA
            if self.ai_response:
                await self.mqtt_client.publish(
                    "voice/speak",
                    json.dumps({"text": self.ai_response, "priority": "high"}),
                )

    async def _talk_timeout(self) -> None:
        """Timeout pour l'état TALK."""
        await asyncio.sleep(10)  # 10 secondes
        if self.current_state == State.TALK:
            await self._transition_to(State.IDLE)

    async def _listen_timeout(self) -> None:
        """Timeout pour l'état LISTEN."""
        await asyncio.sleep(5)  # 5 secondes
        if self.current_state == State.LISTEN:
            await self._transition_to(State.IDLE)

    async def _start_song(self) -> None:
        """Démarre la lecture d'une chanson."""
        song_config = self.config.get("song", {})
        song_file = song_config.get("current_file", "")

        if song_file:
            await self.mqtt_client.publish(
                "voice/play_file", json.dumps({"file": song_file, "sync_jaw": True})
            )
        else:
            self.logger.warning("Aucune chanson configurée")
            await self._transition_to(State.IDLE)

    # Event handlers
    async def _on_vision_motion(self, topic: str, payload: str) -> None:
        """Traite les événements de détection de mouvement."""
        try:
            data = json.loads(payload)
            self.motion_detected = data.get("detected", False)
            self.motion_score = data.get("score", 0.0)

            # Transitions selon le mode
            if self.current_mode == Mode.ACCUEIL:
                threshold = self.config.get("vision", {}).get("motion_threshold", 0.3)

                if (
                    self.motion_detected
                    and self.motion_score > threshold
                    and self.current_state == State.IDLE
                ):
                    await self._transition_to(State.TRACK)

        except Exception as e:
            self.logger.error(f"Erreur traitement motion: {e}")

    async def _on_vision_pose(self, topic: str, payload: str) -> None:
        """Traite les événements de détection de pose."""
        try:
            data = json.loads(payload)

            # Si on track et qu'on a une pose valide, passer en TALK
            if self.current_state == State.TRACK and data.get("face_detected", False):
                await asyncio.sleep(1)  # Attendre stabilisation
                if self.current_state == State.TRACK:
                    await self._transition_to(State.TALK)

        except Exception as e:
            self.logger.error(f"Erreur traitement pose: {e}")

    async def _on_audio_vad(self, topic: str, payload: str) -> None:
        """Traite les événements VAD."""
        try:
            data = json.loads(payload)
            self.vad_active = data.get("active", False)

            # En mode IA, démarrer l'écoute si VAD actif
            if (
                self.current_mode == Mode.IA
                and self.vad_active
                and self.current_state == State.IDLE
            ):
                await self._transition_to(State.LISTEN)

        except Exception as e:
            self.logger.error(f"Erreur traitement VAD: {e}")

    async def _on_asr_text(self, topic: str, payload: str) -> None:
        """Traite le texte ASR."""
        try:
            data = json.loads(payload)
            self.asr_text = data.get("text", "")

            # Si on écoute et qu'on a du texte, passer en THINK
            if (
                self.current_state == State.LISTEN
                and self.asr_text
                and len(self.asr_text.strip()) > 0
            ):
                await self._transition_to(State.THINK)

        except Exception as e:
            self.logger.error(f"Erreur traitement ASR: {e}")

    async def _on_ai_response(self, topic: str, payload: str) -> None:
        """Traite la réponse de l'IA."""
        try:
            data = json.loads(payload)
            self.ai_response = data.get("text", "")

            # Si on réfléchit et qu'on a une réponse, passer en SPEAK
            if self.current_state == State.THINK and self.ai_response:
                await self._transition_to(State.SPEAK)

        except Exception as e:
            self.logger.error(f"Erreur traitement AI: {e}")

    async def _on_voice_state(self, topic: str, payload: str) -> None:
        """Traite l'état du synthétiseur vocal."""
        try:
            data = json.loads(payload)
            self.voice_state = data.get("state", "idle")

            # Quand TTS termine, revenir à IDLE
            if self.voice_state == "idle" and self.current_state in [
                State.TALK,
                State.SPEAK,
                State.SING,
            ]:
                await self._transition_to(State.IDLE)

        except Exception as e:
            self.logger.error(f"Erreur traitement voice: {e}")

    async def _on_mode_change(self, topic: str, payload: str) -> None:
        """Traite le changement de mode."""
        try:
            if payload in [m.value for m in Mode]:
                new_mode = Mode(payload)
                if new_mode != self.current_mode:
                    old_mode = self.current_mode
                    self.current_mode = new_mode
                    self.logger.info(f"Mode: {old_mode.value} → {new_mode.value}")

                    # Actions spécifiques selon le mode
                    await self._on_mode_change_actions(new_mode)

        except Exception as e:
            self.logger.error(f"Erreur changement mode: {e}")

    async def _on_mode_change_actions(self, mode: Mode) -> None:
        """Actions lors du changement de mode."""
        if mode == Mode.CHANSON:
            await self._transition_to(State.SING)
        elif mode == Mode.ACCUEIL:
            # Réinitialiser en mode accueil
            await self._transition_to(State.IDLE)
        elif mode == Mode.IA:
            # S'assurer que les services IA sont démarrés
            services = ["skull-audioin", "skull-asr", "skull-ai"]
            for service in services:
                if not self.service_manager.is_service_running(service):
                    await self.service_manager.start_service(service)
            await self._transition_to(State.IDLE)

    async def _on_estop(self, topic: str, payload: str) -> None:
        """Traite l'arrêt d'urgence."""
        try:
            data = json.loads(payload)
            if data.get("active", False):
                self.logger.warning("E-STOP activé")
                await self._transition_to(State.IDLE)

        except Exception as e:
            self.logger.error(f"Erreur traitement E-STOP: {e}")

    # API publique
    async def set_mode(self, mode: str) -> bool:
        """Change le mode de fonctionnement."""
        try:
            new_mode = Mode(mode)
            await self.mqtt_client.publish("skull/mode", mode, retain=True)
            return True
        except ValueError:
            return False

    async def force_state(self, state: str) -> bool:
        """Force un changement d'état (pour tests)."""
        try:
            new_state = State(state)
            await self._transition_to(new_state)
            return True
        except ValueError:
            return False

    def get_status(self) -> Dict[str, Any]:
        """Retourne le status actuel."""
        return {
            "state": self.current_state.value,
            "mode": self.current_mode.value,
            "motion_detected": self.motion_detected,
            "motion_score": self.motion_score,
            "vad_active": self.vad_active,
            "voice_state": self.voice_state,
            "running": self.running,
        }
