"""
Tests unitaires pour la machine d'états de l'orchestrateur.
"""

import asyncio
import json
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from orchestrator.config import Config
from orchestrator.mqtt import MqttClient
from orchestrator.services import ServiceManager
from orchestrator.state_machine import State, Mode, StateMachine


class TestStateMachine:
    """Tests pour la machine d'états."""

    @pytest.fixture
    async def state_machine(self):
        """Fixture pour créer une machine d'états de test."""
        # Mock des dépendances
        config = MagicMock(spec=Config)
        config.get.return_value = {"motion_threshold": 0.3}

        mqtt_client = AsyncMock(spec=MqttClient)
        service_manager = AsyncMock(spec=ServiceManager)

        # Créer la machine d'états
        sm = StateMachine(mqtt_client, service_manager, config)
        await sm.start()

        yield sm

        await sm.stop()

    @pytest.mark.asyncio
    async def test_initial_state(self, state_machine):
        """Test l'état initial."""
        assert state_machine.current_state == State.IDLE
        assert state_machine.current_mode == Mode.ACCUEIL
        assert state_machine.running is True

    @pytest.mark.asyncio
    async def test_motion_detection_transition(self, state_machine):
        """Test transition Idle → Track → Talk sur détection mouvement."""
        # Simuler détection de mouvement
        motion_payload = json.dumps({"detected": True, "score": 0.8})

        await state_machine._on_vision_motion("vision/motion", motion_payload)

        # Vérifier transition vers TRACK
        assert state_machine.current_state == State.TRACK
        assert state_machine.motion_detected is True
        assert state_machine.motion_score == 0.8

    @pytest.mark.asyncio
    async def test_face_detection_transition(self, state_machine):
        """Test transition Track → Talk sur détection visage."""
        # Mettre en état TRACK
        await state_machine._transition_to(State.TRACK)

        # Simuler détection de visage
        pose_payload = json.dumps({"face_detected": True, "confidence": 0.9})

        await state_machine._on_vision_pose("vision/pose", pose_payload)

        # Attendre la transition (délai de 1s)
        await asyncio.sleep(1.1)

        assert state_machine.current_state == State.TALK

    @pytest.mark.asyncio
    async def test_vad_transition_ia_mode(self, state_machine):
        """Test transition Idle → Listen en mode IA avec VAD."""
        # Passer en mode IA
        await state_machine._on_mode_change("skull/mode", "IA")
        assert state_machine.current_mode == Mode.IA

        # Simuler VAD actif
        vad_payload = json.dumps({"active": True, "confidence": 0.8})

        await state_machine._on_audio_vad("audio/vad", vad_payload)

        assert state_machine.current_state == State.LISTEN
        assert state_machine.vad_active is True

    @pytest.mark.asyncio
    async def test_asr_to_think_transition(self, state_machine):
        """Test transition Listen → Think avec texte ASR."""
        # Mettre en état LISTEN
        await state_machine._transition_to(State.LISTEN)

        # Simuler texte ASR
        asr_payload = json.dumps({"text": "Bonjour Skull Pi", "confidence": 0.95})

        await state_machine._on_asr_text("asr/text", asr_payload)

        assert state_machine.current_state == State.THINK
        assert state_machine.asr_text == "Bonjour Skull Pi"

    @pytest.mark.asyncio
    async def test_ai_response_to_speak_transition(self, state_machine):
        """Test transition Think → Speak avec réponse IA."""
        # Mettre en état THINK
        await state_machine._transition_to(State.THINK)

        # Simuler réponse IA
        ai_payload = json.dumps(
            {"text": "Bonjour ! Comment allez-vous ?", "confidence": 0.9}
        )

        await state_machine._on_ai_response("ai/response", ai_payload)

        assert state_machine.current_state == State.SPEAK
        assert state_machine.ai_response == "Bonjour ! Comment allez-vous ?"

    @pytest.mark.asyncio
    async def test_voice_idle_transition(self, state_machine):
        """Test retour à Idle quand TTS termine."""
        # Mettre en état SPEAK
        await state_machine._transition_to(State.SPEAK)

        # Simuler fin TTS
        voice_payload = json.dumps({"state": "idle"})

        await state_machine._on_voice_state("voice/state", voice_payload)

        assert state_machine.current_state == State.IDLE

    @pytest.mark.asyncio
    async def test_mode_chanson_transition(self, state_machine):
        """Test passage en mode Chanson."""
        await state_machine._on_mode_change("skull/mode", "Chanson")

        assert state_machine.current_mode == Mode.CHANSON
        assert state_machine.current_state == State.SING

    @pytest.mark.asyncio
    async def test_estop_transition(self, state_machine):
        """Test arrêt d'urgence."""
        # Mettre dans un état quelconque
        await state_machine._transition_to(State.TRACK)

        # Simuler E-STOP
        estop_payload = json.dumps({"active": True})

        await state_machine._on_estop("motion/estop", estop_payload)

        assert state_machine.current_state == State.IDLE

    @pytest.mark.asyncio
    async def test_set_mode_api(self, state_machine):
        """Test API de changement de mode."""
        result = await state_machine.set_mode("IA")
        assert result is True

        # Vérifier que le message MQTT a été publié
        state_machine.mqtt_client.publish.assert_called_with(
            "skull/mode", "IA", retain=True
        )

    @pytest.mark.asyncio
    async def test_invalid_mode(self, state_machine):
        """Test mode invalide."""
        result = await state_machine.set_mode("ModeInexistant")
        assert result is False

    @pytest.mark.asyncio
    async def test_force_state_api(self, state_machine):
        """Test API de forçage d'état."""
        result = await state_machine.force_state("Track")
        assert result is True
        assert state_machine.current_state == State.TRACK

    @pytest.mark.asyncio
    async def test_get_status(self, state_machine):
        """Test récupération du statut."""
        status = state_machine.get_status()

        assert "state" in status
        assert "mode" in status
        assert "running" in status
        assert status["state"] == State.IDLE.value
        assert status["mode"] == Mode.ACCUEIL.value
        assert status["running"] is True

    @pytest.mark.asyncio
    async def test_talk_timeout(self, state_machine):
        """Test timeout de l'état TALK."""
        # Mettre en état TALK
        await state_machine._transition_to(State.TALK)

        # Attendre le timeout (simulé avec un délai court)
        with patch("asyncio.sleep", return_value=None):
            await state_machine._talk_timeout()

        assert state_machine.current_state == State.IDLE

    @pytest.mark.asyncio
    async def test_listen_timeout(self, state_machine):
        """Test timeout de l'état LISTEN."""
        await state_machine._transition_to(State.LISTEN)

        with patch("asyncio.sleep", return_value=None):
            await state_machine._listen_timeout()

        assert state_machine.current_state == State.IDLE

    @pytest.mark.asyncio
    async def test_full_ia_cycle(self, state_machine):
        """Test cycle complet IA: VAD → ASR → AI → TTS → Idle."""
        # Passer en mode IA
        await state_machine._on_mode_change("skull/mode", "IA")

        # 1. VAD actif → LISTEN
        vad_payload = json.dumps({"active": True})
        await state_machine._on_audio_vad("audio/vad", vad_payload)
        assert state_machine.current_state == State.LISTEN

        # 2. Texte ASR → THINK
        asr_payload = json.dumps({"text": "Test"})
        await state_machine._on_asr_text("asr/text", asr_payload)
        assert state_machine.current_state == State.THINK

        # 3. Réponse IA → SPEAK
        ai_payload = json.dumps({"text": "Réponse test"})
        await state_machine._on_ai_response("ai/response", ai_payload)
        assert state_machine.current_state == State.SPEAK

        # 4. Fin TTS → IDLE
        voice_payload = json.dumps({"state": "idle"})
        await state_machine._on_voice_state("voice/state", voice_payload)
        assert state_machine.current_state == State.IDLE

    @pytest.mark.asyncio
    async def test_motion_threshold_filtering(self, state_machine):
        """Test filtrage par seuil de mouvement."""
        # Mouvement faible (sous le seuil)
        motion_payload = json.dumps(
            {"detected": True, "score": 0.1}  # Sous le seuil de 0.3
        )

        await state_machine._on_vision_motion("vision/motion", motion_payload)

        # Doit rester en IDLE
        assert state_machine.current_state == State.IDLE

    @pytest.mark.asyncio
    async def test_empty_asr_text(self, state_machine):
        """Test texte ASR vide."""
        await state_machine._transition_to(State.LISTEN)

        # Texte vide
        asr_payload = json.dumps({"text": ""})
        await state_machine._on_asr_text("asr/text", asr_payload)

        # Ne doit pas passer en THINK
        assert state_machine.current_state == State.LISTEN
