"""
MQTT messaging core with OAuth2 authentication support.

This module provides the MessagingCore class that handles secure MQTT connections
with Auth0 authentication for distributed task processing.
"""

import logging
from typing import Callable, Optional

import paho.mqtt.client as mqtt
import requests

logger = logging.getLogger(__name__)


class MessagingCore:
    """
    Core messaging infrastructure for MQTT-based task distribution.

    This class manages the MQTT connection lifecycle, handles OAuth2 authentication
    via Auth0, and dispatches incoming messages to registered job callbacks.

    Attributes:
        host: MQTT broker hostname
        port: MQTT broker port
        client_id: OAuth2 client ID for authentication
        client_secret: OAuth2 client secret for authentication
        audience: OAuth2 audience identifier
        scope: OAuth2 scope string
        topic: MQTT topic to subscribe to
        auth0_token_url: Auth0 token endpoint URL
    """

    def __init__(
        self,
        host: str,
        port: int,
        client_id: str,
        client_secret: str,
        audience: str,
        topic: str = "video/ltx",
        scope: str = "",
        auth0_token_url: str = "https://roimarmier.eu.auth0.com/oauth/token",
    ):
        """
        Initialize the messaging core.

        Args:
            host: MQTT broker hostname
            port: MQTT broker port (typically 1883 for non-TLS, 8883 for TLS)
            client_id: OAuth2 client ID
            client_secret: OAuth2 client secret
            audience: OAuth2 audience identifier
            topic: MQTT topic to subscribe to (default: "video/ltx")
            scope: OAuth2 scope string (default: "")
            auth0_token_url: Auth0 token endpoint URL
        """
        self.host = host
        self.port = port
        self.client_id = client_id
        self.client_secret = client_secret
        self.audience = audience
        self.scope = scope
        self.topic = topic
        self.auth0_token_url = auth0_token_url

        self.token: Optional[str] = None
        self.client: Optional[mqtt.Client] = None
        self.on_job: Optional[Callable[[str], None]] = None

    def get_access_token(self) -> str:
        """
        Retrieve an OAuth2 access token from Auth0.

        Returns:
            The access token string

        Raises:
            requests.HTTPError: If the token request fails
        """
        data = {
            "grant_type": "client_credentials",
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "scope": self.scope,
            "audience": self.audience,
        }

        logger.info("Requesting access token from Auth0...")
        response = requests.post(self.auth0_token_url, data=data)
        response.raise_for_status()

        self.token = response.json()["access_token"]
        logger.info("Access token obtained successfully")
        return self.token

    def start(self, job_callback: Callable[[str], None]) -> None:
        """
        Start the messaging core and begin listening for jobs.

        This method:
        1. Obtains an access token from Auth0
        2. Establishes an MQTT connection
        3. Subscribes to the configured topic
        4. Enters the MQTT event loop

        Args:
            job_callback: Function to call when a job message is received.
                         The function should accept a single string parameter
                         containing the job JSON payload.

        Raises:
            requests.HTTPError: If authentication fails
            Exception: If MQTT connection fails
        """
        self.on_job = job_callback

        # Authenticate
        self.get_access_token()

        # Setup MQTT client
        self.client = mqtt.Client()
        self.client.username_pw_set("any", self.token)

        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message
        self.client.on_disconnect = self._on_disconnect

        # Connect and start event loop
        logger.info(f"Connecting to MQTT broker at {self.host}:{self.port}...")
        self.client.connect(self.host, self.port, 60)
        logger.info("Starting MQTT event loop...")
        self.client.loop_forever()

    def _on_connect(
        self, client: mqtt.Client, userdata: any, flags: dict, rc: int
    ) -> None:
        """
        Callback invoked when the MQTT client connects to the broker.

        Args:
            client: The MQTT client instance
            userdata: User data passed to the client
            flags: Response flags from the broker
            rc: Connection result code
        """
        if rc == 0:
            logger.info(f"Connected to MQTT broker successfully (code {rc})")
            logger.info(f"Subscribing to topic: {self.topic}")
            client.subscribe(self.topic)
        else:
            logger.error(f"Failed to connect to MQTT broker (code {rc})")

    def _on_message(
        self, client: mqtt.Client, userdata: any, msg: mqtt.MQTTMessage
    ) -> None:
        """
        Callback invoked when a message is received on a subscribed topic.

        Args:
            client: The MQTT client instance
            userdata: User data passed to the client
            msg: The received MQTT message
        """
        try:
            payload = msg.payload.decode("utf-8")
            logger.info(f"Received message on topic {msg.topic}")

            if self.on_job:
                self.on_job(payload)
            else:
                logger.warning("No job callback registered, message ignored")

        except Exception as e:
            logger.error(f"Error processing message: {e}", exc_info=True)

    def _on_disconnect(
        self, client: mqtt.Client, userdata: any, rc: int
    ) -> None:
        """
        Callback invoked when the MQTT client disconnects from the broker.

        Args:
            client: The MQTT client instance
            userdata: User data passed to the client
            rc: Disconnection result code
        """
        if rc != 0:
            logger.warning(f"Unexpected MQTT disconnection (code {rc})")
        else:
            logger.info("Disconnected from MQTT broker")
