"""
Tests unitaires pour l'API REST de l'orchestrateur.
"""

import json
import pytest
from fastapi.testclient import TestClient
from unittest.mock import AsyncMock, MagicMock

from orchestrator.config import Config
from orchestrator.rest import create_app
from orchestrator.scheduler import FullAutoScheduler
from orchestrator.services import ServiceManager
from orchestrator.state_machine import StateMachine


class TestRestAPI:
    """Tests pour l'API REST."""

    @pytest.fixture
    def mock_dependencies(self):
        """Fixture pour créer les mocks des dépendances."""
        state_machine = AsyncMock(spec=StateMachine)
        state_machine.get_status.return_value = {
            "state": "Idle",
            "mode": "Accueil",
            "running": True,
            "motion_detected": False,
            "motion_score": 0.0,
            "vad_active": False,
            "voice_state": "idle",
        }
        state_machine.set_mode.return_value = True
        state_machine.force_state.return_value = True

        service_manager = AsyncMock(spec=ServiceManager)
        service_manager.get_all_services_status.return_value = {
            "skull-motion": {"active": True, "name": "skull-motion"},
            "skull-vision": {"active": True, "name": "skull-vision"},
        }
        service_manager.get_managed_services.return_value = [
            "skull-motion",
            "skull-vision",
            "skull-voice",
        ]

        config = MagicMock(spec=Config)
        config.get_all.return_value = {"test": "config"}
        config.get.return_value = {"motion_threshold": 0.3}
        config.update_section.return_value = True
        config.validate_section.return_value = (True, [])
        config.reset_section.return_value = True

        scheduler = AsyncMock(spec=FullAutoScheduler)
        scheduler.is_active.return_value = False
        scheduler.get_next_song_time.return_value = None

        return state_machine, service_manager, config, scheduler

    @pytest.fixture
    def client(self, mock_dependencies):
        """Fixture pour créer le client de test."""
        state_machine, service_manager, config, scheduler = mock_dependencies

        app = create_app(state_machine, service_manager, config, scheduler)
        return TestClient(app)

    def test_get_status(self, client, mock_dependencies):
        """Test endpoint /status."""
        response = client.get("/status")

        assert response.status_code == 200
        data = response.json()

        assert "timestamp" in data
        assert "uptime_seconds" in data
        assert "orchestrator" in data
        assert "services" in data
        assert "state_machine" in data
        assert "scheduler" in data

        assert data["orchestrator"]["state"] == "Idle"
        assert data["orchestrator"]["mode"] == "Accueil"

    def test_get_mode(self, client):
        """Test endpoint GET /mode."""
        response = client.get("/mode")

        assert response.status_code == 200
        data = response.json()

        assert "mode" in data
        assert data["mode"] == "Accueil"

    def test_set_mode_valid(self, client, mock_dependencies):
        """Test endpoint POST /mode avec mode valide."""
        state_machine, _, _, _ = mock_dependencies

        response = client.post("/mode", json={"state": "IA"})

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["mode"] == "IA"

        # Vérifier que la méthode a été appelée
        state_machine.set_mode.assert_called_with("IA")

    def test_set_mode_invalid(self, client, mock_dependencies):
        """Test endpoint POST /mode avec mode invalide."""
        state_machine, _, _, _ = mock_dependencies
        state_machine.set_mode.return_value = False

        response = client.post("/mode", json={"state": "ModeInvalide"})

        assert response.status_code == 400
        assert "Mode invalide" in response.json()["detail"]

    def test_get_all_config(self, client):
        """Test endpoint GET /config."""
        response = client.get("/config")

        assert response.status_code == 200
        data = response.json()

        assert "test" in data
        assert data["test"] == "config"

    def test_get_config_section(self, client, mock_dependencies):
        """Test endpoint GET /config/{section}."""
        _, _, config, _ = mock_dependencies
        config.get.return_value = {"motion_threshold": 0.3}

        response = client.get("/config/motion")

        assert response.status_code == 200
        data = response.json()

        assert "motion" in data
        assert data["motion"]["motion_threshold"] == 0.3

    def test_get_config_section_not_found(self, client, mock_dependencies):
        """Test endpoint GET /config/{section} avec section inexistante."""
        _, _, config, _ = mock_dependencies
        config.get.return_value = None

        response = client.get("/config/inexistant")

        assert response.status_code == 404
        assert "Section non trouvée" in response.json()["detail"]

    def test_update_config_section(self, client, mock_dependencies):
        """Test endpoint POST /config/{section}."""
        _, _, config, _ = mock_dependencies

        new_config = {"motion_threshold": 0.5}
        response = client.post("/config/motion", json={"config": new_config})

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["section"] == "motion"

        config.validate_section.assert_called_with("motion", new_config)
        config.update_section.assert_called_with("motion", new_config)

    def test_update_config_invalid(self, client, mock_dependencies):
        """Test endpoint POST /config/{section} avec config invalide."""
        _, _, config, _ = mock_dependencies
        config.validate_section.return_value = (False, ["Erreur de validation"])

        response = client.post("/config/motion", json={"config": {"invalid": "config"}})

        assert response.status_code == 400
        assert "errors" in response.json()["detail"]

    def test_reset_config_section(self, client, mock_dependencies):
        """Test endpoint DELETE /config/{section}."""
        _, _, config, _ = mock_dependencies

        response = client.delete("/config/motion")

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["section"] == "motion"

        config.reset_section.assert_called_with("motion")

    def test_move_servo_valid(self, client, mock_dependencies):
        """Test endpoint POST /servo/{servo}/move."""
        state_machine, _, _, _ = mock_dependencies
        state_machine.mqtt_client = AsyncMock()
        state_machine.mqtt_client.publish_json.return_value = True

        response = client.post("/servo/pan/move", json={"position": 45.0, "speed": 50})

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["servo"] == "pan"
        assert data["position"] == 45.0

    def test_move_servo_invalid(self, client):
        """Test endpoint POST /servo/{servo}/move avec servo invalide."""
        response = client.post("/servo/invalid/move", json={"position": 45.0})

        assert response.status_code == 400
        assert "Servo invalide" in response.json()["detail"]

    def test_get_servo_limits(self, client, mock_dependencies):
        """Test endpoint GET /servo/{servo}/limits."""
        _, _, config, _ = mock_dependencies
        config.get.return_value = {"pan_limits": [-90, 90]}

        response = client.get("/servo/pan/limits")

        assert response.status_code == 200
        data = response.json()

        assert data["servo"] == "pan"
        assert data["min_position"] == -90
        assert data["max_position"] == 90

    def test_set_servo_limits(self, client, mock_dependencies):
        """Test endpoint POST /servo/{servo}/limits."""
        _, _, config, _ = mock_dependencies

        response = client.post(
            "/servo/pan/limits", json={"min_position": -120.0, "max_position": 120.0}
        )

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["servo"] == "pan"
        assert data["limits"] == [-120.0, 120.0]

    def test_emergency_stop(self, client, mock_dependencies):
        """Test endpoint POST /estop."""
        state_machine, _, _, _ = mock_dependencies
        state_machine.mqtt_client = AsyncMock()
        state_machine.mqtt_client.publish_json.return_value = True

        response = client.post("/estop")

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert "E-STOP" in data["message"]

    def test_list_songs(self, client, mock_dependencies):
        """Test endpoint GET /song/list."""
        _, _, config, _ = mock_dependencies
        config.get.return_value = {"directory": "/tmp/test_songs"}

        # Mock pathlib pour simuler des fichiers
        with pytest.MonkeyPatch().context() as m:
            mock_path = MagicMock()
            mock_path.exists.return_value = True
            mock_path.glob.return_value = []
            m.setattr("orchestrator.rest.Path", lambda x: mock_path)

            response = client.get("/song/list")

        assert response.status_code == 200
        data = response.json()

        assert "songs" in data
        assert isinstance(data["songs"], list)

    def test_play_song(self, client, mock_dependencies):
        """Test endpoint POST /song/play."""
        state_machine, _, _, _ = mock_dependencies

        response = client.post("/song/play?filename=test.mp3")

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert "Chanson démarrée" in data["message"]

    def test_get_services_status(self, client):
        """Test endpoint GET /services."""
        response = client.get("/services")

        assert response.status_code == 200
        data = response.json()

        assert "skull-motion" in data
        assert "skull-vision" in data

    def test_start_service(self, client, mock_dependencies):
        """Test endpoint POST /services/{service}/start."""
        _, service_manager, _, _ = mock_dependencies
        service_manager.start_service.return_value = True

        response = client.post("/services/skull-motion/start")

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["service"] == "skull-motion"
        assert data["action"] == "started"

    def test_start_service_unknown(self, client, mock_dependencies):
        """Test endpoint POST /services/{service}/start avec service inconnu."""
        response = client.post("/services/skull-unknown/start")

        assert response.status_code == 404
        assert "Service non trouvé" in response.json()["detail"]

    def test_health_check(self, client):
        """Test endpoint GET /health."""
        response = client.get("/health")

        assert response.status_code == 200
        data = response.json()

        assert data["status"] == "healthy"
        assert "timestamp" in data
        assert "uptime" in data

    def test_debug_force_state(self, client, mock_dependencies):
        """Test endpoint POST /debug/force-state."""
        state_machine, _, _, _ = mock_dependencies

        response = client.post("/debug/force-state?state=Track")

        assert response.status_code == 200
        data = response.json()

        assert data["success"] is True
        assert data["forced_state"] == "Track"

        state_machine.force_state.assert_called_with("Track")

    def test_fullauto_config(self, client, mock_dependencies):
        """Test endpoints Full Auto."""
        _, _, config, scheduler = mock_dependencies

        # GET config
        response = client.get("/fullauto/config")
        assert response.status_code == 200

        # POST config
        fullauto_config = {
            "enabled": True,
            "interval_song_ms": 120000,
            "accueil_after_song": True,
            "ia_after_accueil": False,
        }

        response = client.post("/fullauto/config", json=fullauto_config)
        assert response.status_code == 200

        # Start/Stop
        scheduler.start.return_value = True
        response = client.post("/fullauto/start")
        assert response.status_code == 200

        response = client.post("/fullauto/stop")
        assert response.status_code == 200
