"""
API REST FastAPI pour l'orchestrateur Skull Pi.
Expose tous les endpoints de contrôle et configuration.
"""

import json
import logging
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from orchestrator.config import Config
from orchestrator.scheduler import FullAutoScheduler
from orchestrator.services import ServiceManager
from orchestrator.state_machine import StateMachine


# Modèles Pydantic
class ModeRequest(BaseModel):
    state: str


class ServoMoveRequest(BaseModel):
    position: float
    speed: Optional[int] = None


class ServoLimitsRequest(BaseModel):
    min_position: float
    max_position: float


class ConfigUpdateRequest(BaseModel):
    config: Dict[str, Any]


class IAQueryRequest(BaseModel):
    text: str
    context: Optional[str] = "conversation"


class SystemPromptRequest(BaseModel):
    prompt: str


class FullAutoConfigRequest(BaseModel):
    enabled: bool
    interval_song_ms: int
    accueil_after_song: bool
    ia_after_accueil: bool


def create_app(
    state_machine: StateMachine,
    service_manager: ServiceManager,
    config: Config,
    scheduler: FullAutoScheduler,
) -> FastAPI:
    """Crée l'application FastAPI."""

    app = FastAPI(
        title="Skull Pi Orchestrator API",
        description="API de contrôle central pour Skull Pi",
        version="1.0.0",
        docs_url="/docs",
        redoc_url="/redoc",
    )

    logger = logging.getLogger("orchestrator.rest")
    start_time = time.time()

    # Middleware de logging
    @app.middleware("http")
    async def log_requests(request, call_next):
        start = time.time()
        response = await call_next(request)
        duration = time.time() - start

        logger.info(
            f"API: {request.method} {request.url.path} - {response.status_code} ({duration:.3f}s)"
        )
        return response

    # Status global
    @app.get("/status")
    async def get_status():
        """Retourne le statut global du système."""
        uptime = int(time.time() - start_time)

        # Statut des services
        services_status = await service_manager.get_all_services_status()

        # Statut de la machine d'états
        state_status = state_machine.get_status()

        return {
            "timestamp": datetime.now().isoformat(),
            "uptime_seconds": uptime,
            "orchestrator": {
                "running": state_machine.running,
                "state": state_status["state"],
                "mode": state_status["mode"],
            },
            "services": services_status,
            "state_machine": state_status,
            "scheduler": {
                "full_auto_active": scheduler.is_active(),
                "next_song": scheduler.get_next_song_time(),
            },
        }

    # Gestion des modes
    @app.get("/mode")
    async def get_mode():
        """Récupère le mode actuel."""
        status = state_machine.get_status()
        return {"mode": status["mode"]}

    @app.post("/mode")
    async def set_mode(request: ModeRequest):
        """Change le mode de fonctionnement."""
        success = await state_machine.set_mode(request.state)
        if success:
            return {"success": True, "mode": request.state}
        else:
            raise HTTPException(status_code=400, detail="Mode invalide")

    # Configuration
    @app.get("/config")
    async def get_all_config():
        """Récupère toute la configuration."""
        return config.get_all()

    @app.get("/config/{section}")
    async def get_config_section(section: str):
        """Récupère une section de configuration."""
        section_config = config.get(section)
        if section_config is None:
            raise HTTPException(status_code=404, detail="Section non trouvée")
        return {section: section_config}

    @app.post("/config/{section}")
    async def update_config_section(section: str, request: ConfigUpdateRequest):
        """Met à jour une section de configuration."""
        # Valider la configuration
        valid, errors = config.validate_section(section, request.config)
        if not valid:
            raise HTTPException(status_code=400, detail={"errors": errors})

        success = config.update_section(section, request.config)
        if success:
            return {"success": True, "section": section}
        else:
            raise HTTPException(status_code=500, detail="Erreur de sauvegarde")

    @app.delete("/config/{section}")
    async def reset_config_section(section: str):
        """Remet une section aux valeurs par défaut."""
        success = config.reset_section(section)
        if success:
            return {"success": True, "section": section}
        else:
            raise HTTPException(status_code=404, detail="Section non trouvée")

    # Contrôle servos
    @app.post("/servo/{servo_name}/move")
    async def move_servo(servo_name: str, request: ServoMoveRequest):
        """Déplace un servo-moteur."""
        if servo_name not in ["pan", "tilt", "jaw"]:
            raise HTTPException(status_code=400, detail="Servo invalide")

        # Publier via MQTT
        command = {"position": request.position}
        if request.speed:
            command["speed"] = request.speed

        topic = f"motion/servo/{servo_name}"
        success = await state_machine.mqtt_client.publish_json(topic, command)

        if success:
            return {"success": True, "servo": servo_name, "position": request.position}
        else:
            raise HTTPException(status_code=500, detail="Erreur envoi commande")

    @app.get("/servo/{servo_name}/limits")
    async def get_servo_limits(servo_name: str):
        """Récupère les limites d'un servo."""
        if servo_name not in ["pan", "tilt", "jaw"]:
            raise HTTPException(status_code=400, detail="Servo invalide")

        motion_config = config.get("motion", {})
        limits_key = f"{servo_name}_limits"
        limits = motion_config.get(limits_key, [-90, 90])

        return {
            "servo": servo_name,
            "min_position": limits[0],
            "max_position": limits[1],
        }

    @app.post("/servo/{servo_name}/limits")
    async def set_servo_limits(servo_name: str, request: ServoLimitsRequest):
        """Configure les limites d'un servo."""
        if servo_name not in ["pan", "tilt", "jaw"]:
            raise HTTPException(status_code=400, detail="Servo invalide")

        limits = [request.min_position, request.max_position]
        limits_key = f"{servo_name}_limits"

        success = config.set("motion", limits_key, limits)
        if success:
            return {"success": True, "servo": servo_name, "limits": limits}
        else:
            raise HTTPException(status_code=500, detail="Erreur sauvegarde")

    @app.post("/estop")
    async def emergency_stop():
        """Arrêt d'urgence - centre tous les moteurs."""
        # Publier E-STOP
        await state_machine.mqtt_client.publish_json("motion/estop", {"active": True})
        return {"success": True, "message": "E-STOP activé"}

    # Gestion chansons
    @app.get("/song/list")
    async def list_songs():
        """Liste les chansons disponibles."""
        songs_dir = Path(config.get("song", {}).get("directory", "/opt/Skull/songs"))
        songs = []

        if songs_dir.exists():
            for song_file in songs_dir.glob("*.mp3"):
                songs.append(
                    {
                        "name": song_file.stem,
                        "file": str(song_file),
                        "size": song_file.stat().st_size,
                    }
                )

        return {"songs": songs}

    @app.post("/song/upload")
    async def upload_song(file: UploadFile = File(...)):
        """Upload une nouvelle chanson."""
        if not file.filename.endswith(".mp3"):
            raise HTTPException(
                status_code=400, detail="Seuls les fichiers MP3 sont acceptés"
            )

        songs_dir = Path(config.get("song", {}).get("directory", "/opt/Skull/songs"))
        songs_dir.mkdir(parents=True, exist_ok=True)

        file_path = songs_dir / file.filename

        try:
            with open(file_path, "wb") as f:
                content = await file.read()
                f.write(content)

            return {
                "success": True,
                "filename": file.filename,
                "path": str(file_path),
                "size": len(content),
            }

        except Exception as e:
            raise HTTPException(status_code=500, detail=f"Erreur sauvegarde: {str(e)}")

    @app.post("/song/play")
    async def play_song(filename: str = None):
        """Lance la lecture d'une chanson."""
        if filename:
            # Mettre à jour le fichier courant
            config.set("song", "current_file", filename)

        # Passer en mode Chanson
        await state_machine.set_mode("Chanson")
        return {"success": True, "message": "Chanson démarrée"}

    @app.post("/song/pause")
    async def pause_song():
        """Met en pause la chanson."""
        await state_machine.mqtt_client.publish_json("voice/pause", {})
        return {"success": True, "message": "Chanson en pause"}

    @app.post("/song/stop")
    async def stop_song():
        """Arrête la chanson."""
        await state_machine.mqtt_client.publish_json("voice/stop", {})
        return {"success": True, "message": "Chanson arrêtée"}

    # Intelligence Artificielle
    @app.get("/ia/config")
    async def get_ia_config():
        """Récupère la configuration IA."""
        return {"ia": config.get("ia", {})}

    @app.post("/ia/config")
    async def update_ia_config(request: ConfigUpdateRequest):
        """Met à jour la configuration IA."""
        valid, errors = config.validate_section("ia", request.config)
        if not valid:
            raise HTTPException(status_code=400, detail={"errors": errors})

        success = config.update_section("ia", request.config)
        if success:
            return {"success": True}
        else:
            raise HTTPException(status_code=500, detail="Erreur sauvegarde")

    @app.get("/ia/system-prompt")
    async def get_system_prompt():
        """Récupère le prompt système."""
        prompt = config.get("ia", {}).get("system_prompt", "")
        return {"system_prompt": prompt}

    @app.post("/ia/system-prompt")
    async def set_system_prompt(request: SystemPromptRequest):
        """Configure le prompt système."""
        success = config.set("ia", "system_prompt", request.prompt)
        if success:
            return {"success": True, "system_prompt": request.prompt}
        else:
            raise HTTPException(status_code=500, detail="Erreur sauvegarde")

    @app.post("/ia/query")
    async def query_ia(request: IAQueryRequest):
        """Envoie une requête à l'IA."""
        # Publier la requête
        success = await state_machine.mqtt_client.publish_json(
            "ai/query", {"text": request.text, "context": request.context}
        )

        if success:
            return {"success": True, "message": "Requête envoyée à l'IA"}
        else:
            raise HTTPException(status_code=500, detail="Erreur envoi requête")

    # Mode Full Auto
    @app.get("/fullauto/config")
    async def get_fullauto_config():
        """Récupère la configuration Full Auto."""
        return {"fullauto": config.get("fullauto", {})}

    @app.post("/fullauto/config")
    async def update_fullauto_config(request: FullAutoConfigRequest):
        """Met à jour la configuration Full Auto."""
        fullauto_config = {
            "enabled": request.enabled,
            "interval_song_ms": request.interval_song_ms,
            "accueil_after_song": request.accueil_after_song,
            "ia_after_accueil": request.ia_after_accueil,
        }

        # Validation
        valid, errors = config.validate_section("fullauto", fullauto_config)
        if not valid:
            raise HTTPException(status_code=400, detail={"errors": errors})

        success = config.update_section("fullauto", fullauto_config)
        if success:
            # Mettre à jour le scheduler
            await scheduler.update_config()
            return {"success": True}
        else:
            raise HTTPException(status_code=500, detail="Erreur sauvegarde")

    @app.post("/fullauto/start")
    async def start_fullauto():
        """Démarre le mode Full Auto."""
        success = await scheduler.start()
        if success:
            await state_machine.set_mode("FullAuto")
            return {"success": True, "message": "Full Auto démarré"}
        else:
            raise HTTPException(status_code=500, detail="Erreur démarrage Full Auto")

    @app.post("/fullauto/stop")
    async def stop_fullauto():
        """Arrête le mode Full Auto."""
        await scheduler.stop()
        await state_machine.set_mode("Idle")
        return {"success": True, "message": "Full Auto arrêté"}

    @app.post("/fullauto/test-once")
    async def test_fullauto_once():
        """Lance un cycle Full Auto de test."""
        await scheduler.trigger_cycle()
        return {"success": True, "message": "Cycle de test lancé"}

    # Gestion des services
    @app.get("/services")
    async def get_services_status():
        """Récupère le statut de tous les services."""
        return await service_manager.get_all_services_status()

    @app.get("/services/{service_name}")
    async def get_service_status(service_name: str):
        """Récupère le statut d'un service."""
        if service_name not in service_manager.get_managed_services():
            raise HTTPException(status_code=404, detail="Service non trouvé")

        status = await service_manager.get_service_status(service_name)
        return {service_name: status}

    @app.post("/services/{service_name}/start")
    async def start_service(service_name: str):
        """Démarre un service."""
        if service_name not in service_manager.get_managed_services():
            raise HTTPException(status_code=404, detail="Service non trouvé")

        success = await service_manager.start_service(service_name)
        if success:
            return {"success": True, "service": service_name, "action": "started"}
        else:
            raise HTTPException(status_code=500, detail="Erreur démarrage service")

    @app.post("/services/{service_name}/stop")
    async def stop_service(service_name: str):
        """Arrête un service."""
        if service_name not in service_manager.get_managed_services():
            raise HTTPException(status_code=404, detail="Service non trouvé")

        success = await service_manager.stop_service(service_name)
        if success:
            return {"success": True, "service": service_name, "action": "stopped"}
        else:
            raise HTTPException(status_code=500, detail="Erreur arrêt service")

    @app.post("/services/{service_name}/restart")
    async def restart_service(service_name: str):
        """Redémarre un service."""
        if service_name not in service_manager.get_managed_services():
            raise HTTPException(status_code=404, detail="Service non trouvé")

        success = await service_manager.restart_service(service_name)
        if success:
            return {"success": True, "service": service_name, "action": "restarted"}
        else:
            raise HTTPException(status_code=500, detail="Erreur redémarrage service")

    # Debug et tests
    @app.get("/debug/state")
    async def get_debug_state():
        """Informations de debug de la machine d'états."""
        return state_machine.get_status()

    @app.post("/debug/force-state")
    async def force_state_debug(state: str):
        """Force un changement d'état (debug uniquement)."""
        success = await state_machine.force_state(state)
        if success:
            return {"success": True, "forced_state": state}
        else:
            raise HTTPException(status_code=400, detail="État invalide")

    @app.get("/debug/mqtt-topics")
    async def get_mqtt_topics():
        """Liste les topics MQTT de configuration."""
        return config.get_mqtt_topics()

    # Health check
    @app.get("/health")
    async def health_check():
        """Check de santé simple."""
        return {
            "status": "healthy",
            "timestamp": datetime.now().isoformat(),
            "uptime": int(time.time() - start_time),
        }

    return app
