import asyncio
import threading
from typing import Any

from azure.servicebus import ServiceBusMessage
from azure.servicebus.aio import (
    ServiceBusClient,
    ServiceBusReceiver,
    ServiceBusSender,
)
from azure.servicebus.exceptions import (
    ServiceBusConnectionError,
    ServiceBusError,
)

from kurier.configurations.models.azureservicebus import AzureServiceBusConfig

from ...core.basekurier import BaseKurier
from ...core.bus_types import BusType
from ...core.station import Station
from ...hermes import Hermes
from ...utilities.compatibility import override
from ...utilities.serializers import Serializer


class AzureLine(BaseKurier):
    """
    A Kurier line implementation for Azure Service Bus.
    Manages connections for sending to queues/topics and receiving from queues/subscriptions,
    dispatching incoming passengers via the Station.
    """

    bus_type = BusType.AZURE

    def __init__(self, config: AzureServiceBusConfig, serializer: Serializer | None = None):
        self._config: AzureServiceBusConfig = config
        super().__init__(self._config.name, serializer=serializer)

        self._service_bus_client: ServiceBusClient = self._create_client()
        self._sender: ServiceBusSender | None = None
        self._receiver: ServiceBusReceiver | None = None

        self._running: bool = False
        self._consumer_thread: threading.Thread | None = None
        self._consumer_loop: asyncio.AbstractEventLoop | None = None

        self._create_sender()

        self._register()
        Hermes.info(f"AzureLine '{self.name}' initialized and registered.")

    def _create_client(self) -> ServiceBusClient:
        """Create a new async Service Bus client from the configured connection string."""
        return ServiceBusClient.from_connection_string(
            self._config.connection_string,
            **self._config.client_properties,
        )

    def _create_sender(self):
        """
        Creates the ServiceBusSender based on config.
        This method is called during initialization or on reconnect.
        """
        if not self._service_bus_client:
            Hermes.error(f"AzureLine '{self.name}': ServiceBusClient not initialized. Cannot create sender.")
            return

        if self._config.queue_name:
            self._sender = self._service_bus_client.get_queue_sender(queue_name=self._config.queue_name)
            Hermes.info(f"AzureLine '{self.name}': Sender created for queue '{self._config.queue_name}'.")
        elif self._config.topic_name:
            self._sender = self._service_bus_client.get_topic_sender(self._config.topic_name)
            Hermes.info(f"AzureLine '{self.name}': Sender created for topic '{self._config.topic_name}'.")
        else:
            Hermes.fatal(f"AzureLine '{self.name}': No queue_name or topic_name provided for sender configuration.")

    def _create_receiver(self):
        """
        Creates the ServiceBusReceiver based on config.
        This method is called during initialization or on reconnect.
        """
        if not self._service_bus_client:
            Hermes.error(f"AzureLine '{self.name}': ServiceBusClient not initialized. Cannot create receiver.")
            return

        if self._config.queue_name and not self._config.topic_name:
            self._receiver = self._service_bus_client.get_queue_receiver(
                queue_name=self._config.queue_name,
                prefetch_count=self._config.prefetch_count,
                receive_mode=self._config.receive_mode,
            )
            Hermes.info(f"AzureLine '{self.name}': Receiver created for queue '{self._config.queue_name}'.")
        elif self._config.topic_name and self._config.subscription_name:
            self._receiver = self._service_bus_client.get_subscription_receiver(
                topic_name=self._config.topic_name,
                subscription_name=self._config.subscription_name,
                prefetch_count=self._config.prefetch_count,
                receive_mode=self._config.receive_mode,
            )
            Hermes.info(f"AzureLine '{self.name}': Receiver created for topic '{self._config.topic_name}' subscription '{self._config.subscription_name}'.")
        else:
            Hermes.fatal(f"AzureLine '{self.name}': No valid queue_name or topic/subscription pair for receiver configuration.")

    def _create_sender_and_receiver(self):
        """
        Creates both the ServiceBusSender and ServiceBusReceiver based on config.
        This method orchestrates the calls to _create_sender and _create_receiver.
        """
        self._create_sender()
        self._create_receiver()

    @override
    async def _publish_to_bus(self, json_message: str, meta_data: dict[str, Any]):
        """
        Publishes a passenger to the configured Azure Service Bus queue or topic.

        Args:
            json_message (str): The JSON string of the passenger to be published.
            meta_data (dict[str, Any]): Metadata associated with the passenger.
        """
        if not self._sender:
            Hermes.error(f"AzureLine '{self.name}': Sender not initialized. Cannot publish passenger.")
            return

        try:
            sb_message = ServiceBusMessage(json_message, **self._config.message_properties)

            await self._sender.send_messages(sb_message)
            Hermes.info(f"AzureLine '{self.name}': Published passenger.")
        except ServiceBusConnectionError as e:
            Hermes.warning(f"AzureLine '{self.name}': Connection error during publish: {e}. Attempting to re-create sender.")

            if self._service_bus_client:
                await self._service_bus_client.close()
            self._service_bus_client = self._create_client()
            # Re-create both sender and receiver on connection error
            self._create_sender_and_receiver()

        except ServiceBusError as e:
            Hermes.error(f"AzureLine '{self.name}': Azure Service Bus specific error during publish: {e}")
        except Exception as e:
            Hermes.fatal(f"AzureLine '{self.name}': An unexpected error occurred during publish: {e}")
            raise

    @override
    def _run_consumer_loop(self):
        """
        The main loop for the Azure Service Bus consumer thread.
        It continuously receives passengers and dispatches them via the Station.
        """

        if not self._receiver:
            Hermes.error(f"AzureLine '{self.name}': Receiver not available, cannot start consumer loop.")
            return

        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        self._consumer_loop = loop

        Hermes.info(f"AzureLine '{self.name}': Consumer loop started for receiver.")
        try:

            async def _receive_and_dispatch():
                async with self._receiver:
                    while self._running:
                        try:
                            received_messages = await self._receiver.receive_messages(max_wait_time=5)
                            for sb_message in received_messages:
                                envelope_str = str(sb_message.body)

                                await Station.dispatch(self, envelope_str)

                                if self._config.receive_mode == "PEEK_LOCK":
                                    await self._receiver.complete_message(sb_message)

                        except ServiceBusConnectionError as e:
                            Hermes.warning(f"AzureLine '{self.name}': Connection error in consumer loop: {e}. Attempting to re-create client/receiver.")
                            if self._service_bus_client:
                                await self._service_bus_client.close()
                            self._service_bus_client = self._create_client()
                            # Re-create both sender and receiver on connection error
                            self._create_sender_and_receiver()
                            await asyncio.sleep(5)

                        except ServiceBusError as e:
                            Hermes.error(f"AzureLine '{self.name}': Service Bus specific error in consumer loop: {e}")

                            if self._config.receive_mode == "PEEK_LOCK" and "sb_message" in locals():
                                await self._receiver.abandon_message(sb_message)
                            await asyncio.sleep(2)
                        except Exception as e:
                            Hermes.fatal(f"AzureLine '{self.name}': An unhandled error occurred in consumer loop: {e}")
                            await asyncio.sleep(1)

            loop.run_until_complete(_receive_and_dispatch())

        except Exception as e:
            Hermes.fatal(f"AzureLine '{self.name}': Error starting consumer loop: {e}")
        finally:
            if self._receiver:
                pass  # The async with self._receiver block handles closing the receiver
            if self._service_bus_client:
                loop.run_until_complete(self._service_bus_client.close())
            if self._consumer_loop and self._consumer_loop.is_running():
                self._consumer_loop.stop()
            if self._consumer_loop and not self._consumer_loop.is_closed():
                self._consumer_loop.close()
            Hermes.info(f"AzureLine '{self.name}': Consumer loop stopped and clients closed.")

    @classmethod
    @override
    def start_consumer(cls, name: str) -> bool:
        lines: list[AzureLine]

        if not name:
            lines = [line for line in cls.get_all_items() if isinstance(line, AzureLine)]
        else:
            item = cls.get_item(name)
            lines = [item] if isinstance(item, AzureLine) else []

        for line in lines:
            if line is None:
                continue

            try:
                Hermes.info(f"AzureLine '{line.name}': ServiceBusClient initialized.")
                line._create_receiver()

                if line._receiver:
                    line._running = True
                    line._consumer_thread = threading.Thread(target=line._run_consumer_loop)
                    line._consumer_thread.daemon = True
                    line._consumer_thread.start()
                    Hermes.info(f"AzureLine '{line.name}': Consumer thread started.")
                else:
                    Hermes.error(f"AzureLine '{line.name}': No receiver configured (no queue_name or topic/subscription), consumer thread not started.")

            except ServiceBusConnectionError as e:
                Hermes.fatal(f"AzureLine '{line.name}': Connection error during initialization: {e}")
                line._service_bus_client = None
                raise

            except Exception as e:
                Hermes.fatal(f"AzureLine '{line.name}': Unhandled error during initialization: {e}")
                line._service_bus_client = None
                raise

        return True

    @override
    async def close(self):
        """
        Gracefully closes the AzureLine, stopping the consumer thread and
        closing sender and client connections.
        """
        Hermes.info(f"AzureLine '{self.name}': Initiating shutdown...")
        self._running = False  # Signal the consumer loop to stop

        if self._consumer_thread and self._consumer_thread.is_alive():
            self._consumer_thread.join(timeout=15)
            if self._consumer_thread.is_alive():
                Hermes.warning(f"AzureLine '{self.name}': Warning: Consumer thread did not terminate gracefully within timeout.")

        if self._sender:
            try:
                await self._sender.close()
                Hermes.info(f"AzureLine '{self.name}': Sender closed.")
            except Exception as e:
                Hermes.error(f"AzureLine '{self.name}': Error closing sender: {e}")

        # The receiver is closed by the 'async with self._receiver' block in _run_consumer_loop
        # when the loop finishes. So, no explicit close here for the receiver.

        if self._service_bus_client:
            try:
                await self._service_bus_client.close()
                Hermes.info(f"AzureLine '{self.name}': ServiceBusClient closed.")
            except Exception as e:
                Hermes.error(f"AzureLine '{self.name}': Error closing ServiceBusClient: {e}")

        Hermes.info(f"AzureLine '{self.name}': Closed.")
