import asyncio
import threading
from typing import Any

from kafka import KafkaConsumer, KafkaProducer

from kurier.configurations.models.kafka import KafkaConfig
from kurier.core.basekurier import BaseKurier
from kurier.core.bus_types import BusType
from kurier.core.station import Station
from kurier.hermes import Hermes
from kurier.utilities.compatibility import override
from kurier.utilities.serializers import Serializer


class KafkaLine(BaseKurier):
    """
    A Kurier line implementation for Apache Kafka.
    Manages Kafka producer and consumer clients and dispatches
    incoming passengers via the Station.
    """

    bus_type = BusType.KAFKA

    def __init__(self, config: KafkaConfig, serializer: Serializer | None = None):
        self._config: KafkaConfig = config

        super().__init__(self._config.name, serializer=serializer)

        self._producer: KafkaProducer | None = None
        self._consumer: KafkaConsumer | None = None
        self._running: bool = False
        self._consumer_thread: threading.Thread | None = None
        self._consumer_loop: asyncio.AbstractEventLoop | None = None

        self._producer = self._create_producer()

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

    def _create_producer(self) -> KafkaProducer:
        """Creates and returns a KafkaProducer instance."""
        producer = KafkaProducer(
            bootstrap_servers=self._config.bootstrap_servers,
            value_serializer=self._kafka_value_serializer,
            **self._config.producer_properties,
        )

        Hermes.info(f"KafkaLine '{self.name}': Producer created for {self._config.bootstrap_servers}.")
        return producer

    def _create_consumer(self) -> KafkaConsumer:
        """Creates and returns a KafkaConsumer instance."""

        consumer = KafkaConsumer(
            self._config.topic_name,
            bootstrap_servers=self._config.bootstrap_servers,
            group_id=self._config.group_id,
            auto_offset_reset=self._config.auto_offset_reset,
            enable_auto_commit=self._config.enable_auto_commit,
            auto_commit_interval_ms=self._config.auto_commit_interval_ms,
            value_deserializer=self._kafka_value_deserializer,
            **self._config.consumer_properties,
        )
        Hermes.info(f"KafkaLine '{self.name}': Consumer created for topic '{self._config.topic_name}', group '{self._config.group_id}'.")
        return consumer

    @staticmethod
    def _kafka_value_serializer(obj: Any) -> bytes:
        """Serializes the passenger envelope string to bytes for Kafka."""
        return obj.encode("utf-8")

    @staticmethod
    def _kafka_value_deserializer(b: bytes) -> str:
        """Deserializes Kafka passenger bytes to a string envelope."""
        return b.decode("utf-8")

    @override
    async def _publish_to_bus(self, json_message: str, meta_data: dict[str, Any]):
        """
        Publishes a passenger to the configured Kafka topic.
        """
        if not self._producer:
            Hermes.error(f"KafkaLine '{self.name}': Producer not initialized. Cannot publish passenger.")
            return

        try:
            future = self._producer.send(self._config.topic_name, json_message)

            record_metadata = await asyncio.to_thread(future.get, timeout=self._config.publish_timeout_seconds)

            Hermes.info(f"KafkaLine '{self.name}': Published passenger to topic {record_metadata.topic} partition {record_metadata.partition} offset {record_metadata.offset}")
        except Exception as e:
            Hermes.error(f"KafkaLine '{self.name}': Error publishing passenger: {e}")

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

        if not self._consumer:
            Hermes.error(f"KafkaLine '{self.name}': Consumer not available, cannot start consumer loop.")
            return

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

        Hermes.info(f"KafkaLine '{self.name}': Consumer loop started for topic {self._config.topic_name}.")
        try:
            for message in self._consumer:
                if not self._running:
                    break

                envelope_str = message.value
                loop.run_until_complete(Station.dispatch(self, envelope_str))

        except Exception as e:
            Hermes.fatal(f"KafkaLine '{self.name}': An unhandled error occurred in consumer loop: {e}")

        finally:
            if self._consumer:
                self._consumer.close()
            if self._consumer_loop and not self._consumer_loop.is_closed():
                self._consumer_loop.close()
            Hermes.info(f"KafkaLine '{self.name}': Consumer loop stopped and clients closed.")

    @classmethod
    @override
    def start_consumer(cls, name: str | None = None) -> bool:
        """
        Starts consumers for specific or all KafkaLines.
        Refactored to use RegistryMixin logic.
        """
        lines: list[KafkaLine]

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

        if not lines and name:
            Hermes.warning(f"No KafkaLine found with name '{name}' to start.")
            return False

        for line in lines:
            try:
                line._consumer = line._create_consumer()
            except Exception as e:
                Hermes.fatal(f"Failed to start KafkaLine '{line.name}' consumer: {e}")
                continue

            if not line._consumer:
                Hermes.error(f"KafkaLine '{line.name}': Consumer not initialized, consumer thread not started.")
                continue

            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"KafkaLine '{line.name}': Consumer thread started.")

        return True

    @override
    async def close(self):
        """
        Gracefully closes the KafkaLine, stopping the consumer thread and
        closing producer and consumer clients.
        """

        Hermes.info(f"KafkaLine '{self.name}': Initiating shutdown...")
        self._running = False

        if self._consumer:
            try:
                self._consumer.unsubscribe()
            except Exception:
                pass

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

        if self._producer:
            try:
                self._producer.flush(timeout=5)
                self._producer.close()
                Hermes.info(f"KafkaLine '{self.name}': Producer closed.")
            except Exception as e:
                Hermes.error(f"KafkaLine '{self.name}': Error closing producer: {e}")

        if self._consumer:
            try:
                self._consumer.close()
                Hermes.info(f"KafkaLine '{self.name}': Consumer client closed.")
            except Exception as e:
                Hermes.error(f"KafkaLine '{self.name}': Error closing consumer: {e}")

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