"""Client MQTT pour publications et abonnements vision."""

import json
import logging
import time
from typing import Dict, Any, Callable, Optional
import paho.mqtt.client as mqtt

logger = logging.getLogger(__name__)


class MQTTClient:
    """Client MQTT pour le module vision."""

    def __init__(self, host: str = "127.0.0.1", port: int = 1883, keepalive: int = 60):
        self.host = host
        self.port = port
        self.keepalive = keepalive

        self.client = mqtt.Client()
        self.client.on_connect = self._on_connect
        self.client.on_disconnect = self._on_disconnect
        self.client.on_message = self._on_message

        self.connected = False
        self.message_callbacks: Dict[str, Callable] = {}

        # Stats
        self._publish_count = 0
        self._last_publish_time = 0.0

    def connect(self) -> bool:
        """Connecte au broker MQTT."""
        try:
            self.client.connect(self.host, self.port, self.keepalive)
            self.client.loop_start()

            # Attendre connexion (max 5s)
            start_time = time.time()
            while not self.connected and (time.time() - start_time) < 5.0:
                time.sleep(0.1)

            if self.connected:
                logger.info(f"Connecté au broker MQTT {self.host}:{self.port}")
                return True
            else:
                logger.error("Timeout connexion MQTT")
                return False

        except Exception as e:
            logger.error(f"Erreur connexion MQTT: {e}")
            return False

    def disconnect(self) -> None:
        """Déconnecte du broker MQTT."""
        try:
            self.client.loop_stop()
            self.client.disconnect()
            self.connected = False
            logger.info("Déconnecté du broker MQTT")
        except Exception as e:
            logger.error(f"Erreur déconnexion MQTT: {e}")

    def publish(self, topic: str, payload: str, retained: bool = False) -> bool:
        """Publie un message MQTT."""
        if not self.connected:
            logger.warning("MQTT non connecté, publication ignorée")
            return False

        try:
            result = self.client.publish(topic, payload, retain=retained)

            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                self._publish_count += 1
                self._last_publish_time = time.time()
                logger.debug(f"Publié {topic}: {payload[:100]}...")
                return True
            else:
                logger.error(f"Erreur publication {topic}: code {result.rc}")
                return False

        except Exception as e:
            logger.error(f"Exception publication {topic}: {e}")
            return False

    def subscribe(self, topic: str, callback: Callable[[str, str], None]) -> bool:
        """S'abonne à un topic avec callback."""
        if not self.connected:
            logger.warning("MQTT non connecté, abonnement différé")

        try:
            result = self.client.subscribe(topic)
            if result[0] == mqtt.MQTT_ERR_SUCCESS:
                self.message_callbacks[topic] = callback
                logger.info(f"Abonné à {topic}")
                return True
            else:
                logger.error(f"Erreur abonnement {topic}: code {result[0]}")
                return False

        except Exception as e:
            logger.error(f"Exception abonnement {topic}: {e}")
            return False

    def unsubscribe(self, topic: str) -> bool:
        """Se désabonne d'un topic."""
        try:
            result = self.client.unsubscribe(topic)
            if result[0] == mqtt.MQTT_ERR_SUCCESS:
                if topic in self.message_callbacks:
                    del self.message_callbacks[topic]
                logger.info(f"Désabonné de {topic}")
                return True
            else:
                logger.error(f"Erreur désabonnement {topic}: code {result[0]}")
                return False

        except Exception as e:
            logger.error(f"Exception désabonnement {topic}: {e}")
            return False

    def _on_connect(self, client, userdata, flags, rc) -> None:
        """Callback connexion MQTT."""
        if rc == 0:
            self.connected = True
            logger.info("MQTT connecté avec succès")
        else:
            self.connected = False
            logger.error(f"Échec connexion MQTT, code: {rc}")

    def _on_disconnect(self, client, userdata, rc) -> None:
        """Callback déconnexion MQTT."""
        self.connected = False
        if rc != 0:
            logger.warning(f"Déconnexion MQTT inattendue, code: {rc}")
        else:
            logger.info("Déconnexion MQTT normale")

    def _on_message(self, client, userdata, msg) -> None:
        """Callback réception message MQTT."""
        try:
            topic = msg.topic
            payload = msg.payload.decode("utf-8")

            logger.debug(f"Reçu {topic}: {payload}")

            # Chercher callback exact ou pattern
            callback = None
            if topic in self.message_callbacks:
                callback = self.message_callbacks[topic]
            else:
                # Chercher pattern match simple
                for pattern, cb in self.message_callbacks.items():
                    if self._topic_matches(topic, pattern):
                        callback = cb
                        break

            if callback:
                callback(topic, payload)
            else:
                logger.debug(f"Aucun callback pour {topic}")

        except Exception as e:
            logger.error(f"Erreur traitement message MQTT: {e}")

    def _topic_matches(self, topic: str, pattern: str) -> bool:
        """Vérification pattern simple pour topics MQTT."""
        if "+" in pattern or "#" in pattern:
            # Support basique wildcards MQTT
            pattern_parts = pattern.split("/")
            topic_parts = topic.split("/")

            if pattern.endswith("#"):
                return len(topic_parts) >= len(pattern_parts) - 1
            elif "+" in pattern:
                if len(pattern_parts) != len(topic_parts):
                    return False
                for p, t in zip(pattern_parts, topic_parts):
                    if p != "+" and p != t:
                        return False
                return True

        return topic == pattern

    @property
    def is_connected(self) -> bool:
        return self.connected

    def get_stats(self) -> Dict[str, Any]:
        """Retourne statistiques MQTT."""
        return {
            "connected": self.connected,
            "publish_count": self._publish_count,
            "last_publish_time": self._last_publish_time,
            "subscriptions": list(self.message_callbacks.keys()),
        }


class VisionMQTTPublisher:
    """Publisher spécialisé pour topics vision."""

    def __init__(self, mqtt_client: MQTTClient):
        self.mqtt_client = mqtt_client

    def publish_targets(self, targets_data: Dict[str, Any]) -> bool:
        """Publie vision/targets."""
        return self.mqtt_client.publish("vision/targets", json.dumps(targets_data))

    def publish_pose(self, pose_data: Dict[str, Any]) -> bool:
        """Publie vision/pose."""
        return self.mqtt_client.publish("vision/pose", json.dumps(pose_data))

    def publish_motion(self, motion_data: Dict[str, Any]) -> bool:
        """Publie vision/motion."""
        return self.mqtt_client.publish("vision/motion", json.dumps(motion_data))

    def publish_capabilities(self, capabilities_data: Dict[str, Any]) -> bool:
        """Publie vision/capabilities (retained)."""
        return self.mqtt_client.publish(
            "vision/capabilities", json.dumps(capabilities_data), retained=True
        )

    def publish_health(self, health_data: Dict[str, Any]) -> bool:
        """Publie vision/health (optionnel)."""
        return self.mqtt_client.publish("vision/health", json.dumps(health_data))


class ConfigSubscriber:
    """Subscriber pour configurations retained."""

    def __init__(self, mqtt_client: MQTTClient):
        self.mqtt_client = mqtt_client
        self.vision_config_callback: Optional[Callable] = None
        self.gaze_config_callback: Optional[Callable] = None

    def setup_subscriptions(
        self,
        vision_callback: Callable[[Dict[str, Any]], None],
        gaze_callback: Callable[[Dict[str, Any]], None],
    ) -> None:
        """Configure les abonnements aux configs."""
        self.vision_config_callback = vision_callback
        self.gaze_config_callback = gaze_callback

        # S'abonner aux topics de config
        self.mqtt_client.subscribe("skull/config/vision", self._on_vision_config)
        self.mqtt_client.subscribe("skull/config/gaze", self._on_gaze_config)

    def _on_vision_config(self, topic: str, payload: str) -> None:
        """Callback config vision."""
        try:
            config_data = json.loads(payload)
            if self.vision_config_callback:
                self.vision_config_callback(config_data)
            logger.info(f"Config vision mise à jour: {config_data}")
        except json.JSONDecodeError as e:
            logger.error(f"Erreur parsing config vision: {e}")
        except Exception as e:
            logger.error(f"Erreur traitement config vision: {e}")

    def _on_gaze_config(self, topic: str, payload: str) -> None:
        """Callback config gaze."""
        try:
            config_data = json.loads(payload)
            if self.gaze_config_callback:
                self.gaze_config_callback(config_data)
            logger.info(f"Config gaze mise à jour: {config_data}")
        except json.JSONDecodeError as e:
            logger.error(f"Erreur parsing config gaze: {e}")
        except Exception as e:
            logger.error(f"Erreur traitement config gaze: {e}")


class MockMQTTClient:
    """Client MQTT simulé pour tests."""

    def __init__(self, host: str = "127.0.0.1", port: int = 1883, keepalive: int = 60):
        self.host = host
        self.port = port
        self.connected = True
        self.published_messages: list = []
        self.message_callbacks: Dict[str, Callable] = {}

    def connect(self) -> bool:
        self.connected = True
        logger.info(f"Mock MQTT connecté à {self.host}:{self.port}")
        return True

    def disconnect(self) -> None:
        self.connected = False
        logger.info("Mock MQTT déconnecté")

    def publish(self, topic: str, payload: str, retained: bool = False) -> bool:
        if not self.connected:
            return False

        self.published_messages.append(
            {
                "topic": topic,
                "payload": payload,
                "retained": retained,
                "timestamp": time.time(),
            }
        )
        logger.debug(f"Mock publish {topic}: {payload[:50]}...")
        return True

    def subscribe(self, topic: str, callback: Callable) -> bool:
        self.message_callbacks[topic] = callback
        logger.debug(f"Mock subscribe {topic}")
        return True

    def unsubscribe(self, topic: str) -> bool:
        if topic in self.message_callbacks:
            del self.message_callbacks[topic]
        return True

    def inject_message(self, topic: str, payload: str) -> None:
        """Injecte message pour simulation."""
        if topic in self.message_callbacks:
            self.message_callbacks[topic](topic, payload)

    @property
    def is_connected(self) -> bool:
        return self.connected

    def get_stats(self) -> Dict[str, Any]:
        return {
            "connected": self.connected,
            "publish_count": len(self.published_messages),
            "last_publish_time": (
                self.published_messages[-1]["timestamp"]
                if self.published_messages
                else 0
            ),
            "subscriptions": list(self.message_callbacks.keys()),
        }

    def clear_messages(self) -> None:
        """Vide les messages pour tests."""
        self.published_messages.clear()
