"""
Transport layer for sending events to external systems
"""

import json
import threading
import queue
import time
import asyncio
from abc import ABC, abstractmethod
from typing import List, Optional
from kafka import KafkaProducer
from kafka.errors import KafkaError

from ..models.captured_models import ErrorEvent, Severity
from ..models.pydantic_compat import get_model_dump
from ..config.settings import get_settings
from ..utils.logger_config import get_logger

logger = get_logger()


class Transport(ABC):
    """Base class for event transports"""

    @abstractmethod
    def send(self, event: ErrorEvent) -> bool:
        """Send event to transport destination."""
        pass
    
    def send_async(self, event: ErrorEvent) -> Optional[asyncio.Future]:
        """
        Async send method - default implementation returns None (use sync send).
        Override this for true async transports.
        """
        return None

    @abstractmethod
    def close(self) -> None:
        """Cleanup transport resources"""
        pass


class ConsoleTransport(Transport):
    """Transport that logs events to console"""
    
    def send(self, event: ErrorEvent) -> bool:
        """Log event to console"""
        try:
            log_method = {
                Severity.CRITICAL: logger.critical,
                Severity.HIGH: logger.error,
                Severity.MEDIUM: logger.warning,
                Severity.LOW: logger.info,
                Severity.WARNING: logger.warning,
            }.get(event.severity, logger.info)

            message = (
                f"[{event.component}] {event.message} "
                f"(severity={event.severity}, type={event.error_type})"
            )

            if event.function_name:
                message += f" in {event.function_name}()"

            log_method(message)
            return True

        except Exception as e:
            logger.error(f"ConsoleTransport failed: {e}")
            return False
        
    def close(self) -> None:
        """No cleanup needed for console transport"""
        pass


class KafkaTransport(Transport):
    """Transport that sends events to Kafka"""

    def __init__(
        self,
        bootstrap_servers: Optional[str] = None,
        event_topic: Optional[str] = None,
    ):
        self.settings = get_settings()
        self.bootstrap_servers = bootstrap_servers or self.settings.KAFKA_BOOTSTRAP_SERVERS_RCA
        self.event_topic = event_topic or self.settings.KAFKA_EVENT_TOPIC_RCA
        
        self.producer: Optional[KafkaProducer] = None
        self._initialized = False
        self._lock = threading.Lock()
    
    def _initialize(self) -> None:
        """Initialize Kafka producer (thread-safe)"""
        with self._lock:
            if self._initialized:
                return
            
            try:
                self.producer = KafkaProducer(
                    bootstrap_servers=self.bootstrap_servers,
                    value_serializer=lambda v: json.dumps(v).encode('utf-8'),
                    acks='all',
                    retries=3,
                    compression_type='gzip',
                )
                self._initialized = True
                logger.info(f"KafkaTransport initialized for {self.bootstrap_servers}")
            except Exception as e:
                logger.error(f"Failed to initialize Kafka producer: {e}")
                self.producer = None
                self._initialized = False
                raise
    
    def send(self, event: ErrorEvent) -> bool:
        """Send event to Kafka topic"""
        try:
            if not self._initialized:
                logger.debug(f"Initializing Kafka producer for event {event.id}")
                self._initialize()

            if not self.producer:
                logger.error(f"❌ Kafka producer not available for event {event.id}")
                return False

            # Convert event to dict using compatibility function
            event_dict = get_model_dump(event, mode='json')  # Works for both v1 and v2

            # Send to Kafka (synchronous)
            logger.debug(f"📤 Sending event {event.id} to Kafka topic '{self.event_topic}'")
            future = self.producer.send(self.event_topic, value=event_dict)
            future.get(timeout=10)

            logger.success(f"✅ Event {event.id} sent to Kafka topic: {self.event_topic}")
            return True

        except KafkaError as e:
            logger.error(
                f"❌ Kafka error sending event {event.id} to topic '{self.event_topic}': "
                f"{type(e).__name__}: {e}"
            )
            logger.error(f"   Kafka brokers: {self.bootstrap_servers}")
            logger.error(f"   Event type: {event.error_type}, Component: {event.component}")
            return False
        except Exception as e:
            logger.error(
                f"❌ KafkaTransport exception for event {event.id}: "
                f"{type(e).__name__}: {e}"
            )
            import traceback
            traceback.print_exc()
            return False

    def close(self) -> None:
        """Close Kafka producer"""
        if self.producer:
            try:
                self.producer.flush()
                self.producer.close()
                logger.info("KafkaTransport closed")
            except Exception as e:
                logger.error(f"Error closing Kafka producer: {e}")


class MultiTransport(Transport):
    """Transport that sends events to multiple destinations"""

    def __init__(self, transports: Optional[List[Transport]] = None):
        self.transports = transports or []

    def add_transport(self, transport: Transport) -> None:
        """Add a transport to the list"""
        self.transports.append(transport)

    def send(self, event: ErrorEvent) -> bool:
        """Send event to all transports"""
        results = []
        for transport in self.transports:
            try:
                result = transport.send(event)
                results.append(result)
            except Exception as e:
                logger.error(f"Transport {type(transport).__name__} failed: {e}")
                results.append(False)

        # Return True if at least one transport succeeded
        return any(results)
    
    def send_async(self, event: ErrorEvent) -> Optional[asyncio.Future]:
        """Try async send on all transports"""
        # Just use sync for MultiTransport
        return None

    def close(self) -> None:
        """Close all transports"""
        for transport in self.transports:
            try:
                transport.close()
            except Exception as e:
                logger.error(f"Error closing transport {type(transport).__name__}: {e}")


class BackgroundTransport(Transport):
    """Transport that sends events in the background using a thread and queue."""
    
    def __init__(
        self, 
        base_transport: Transport, 
        max_queue_size: int = 1000,
    ):
        self.base_transport = base_transport
        self.queue = queue.Queue(maxsize=max_queue_size)
        self.worker_thread: Optional[threading.Thread] = None
        self._running = False
        self._lock = threading.Lock()
        self._started = False
        
        # Start worker immediately
        self._start_worker()
    
    def _start_worker(self):
        """Start background worker thread"""
        with self._lock:
            if self._started:
                logger.debug("Background worker already started")
                return
            
            if not self.base_transport:
                logger.error("Cannot start worker: base_transport not set!")
                return
            
            self._running = True
            self._started = True
            self.worker_thread = threading.Thread(
                target=self._worker,
                daemon=True,
                name=f"BackgroundTransport-{id(self)}"
            )
            self.worker_thread.start()
            
            # Wait a bit to ensure thread actually starts
            time.sleep(0.1)
            
            if self.worker_thread.is_alive():
                logger.info(f"✅ BackgroundTransport worker thread started")
            else:
                logger.error(f"❌ BackgroundTransport worker thread failed to start!")
    
    def _worker(self):
        """Background worker that processes events from the queue"""
        logger.info(f"🚀 Background worker started for {type(self.base_transport).__name__}")
        events_processed = 0
        consecutive_failures = 0
        
        while self._running:
            try:
                # Wait for event with timeout
                try:
                    event = self.queue.get(timeout=1.0)
                except queue.Empty:
                    continue
                
                # Process the event
                events_processed += 1
                logger.info(f"📤 Worker processing event {events_processed}: {event.id} (type={event.error_type})")
                
                try:
                    success = self.base_transport.send(event)
                    if success:
                        logger.success(f"✅ Worker sent event {event.id} to {type(self.base_transport).__name__}")
                        consecutive_failures = 0  # Reset failure counter
                    else:
                        consecutive_failures += 1
                        logger.error(f"❌ Base transport returned False for event {event.id}")
                        
                        if consecutive_failures >= 3:
                            logger.critical(
                                f"🚨 CRITICAL: {consecutive_failures} consecutive send failures! "
                                f"Transport may be broken. Check Kafka connectivity."
                            )
                            
                except Exception as e:
                    consecutive_failures += 1
                    logger.error(f"❌ Worker exception sending event {event.id}: {type(e).__name__}: {e}")
                    
                    # Log full traceback for first few failures
                    if consecutive_failures <= 3:
                        import traceback
                        traceback.print_exc()
                    
                    if consecutive_failures >= 3:
                        logger.critical(
                            f"🚨 CRITICAL: {consecutive_failures} consecutive exceptions! "
                            f"Last error: {type(e).__name__}: {e}"
                        )
                finally:
                    self.queue.task_done()
                    
            except Exception as e:
                logger.error(f"❌ Background worker outer loop error: {e}")
                import traceback
                traceback.print_exc()
        
        logger.info(f"🛑 Background worker stopped (processed {events_processed} events)")
    
    def health_check(self) -> dict:
        """Check background transport health"""
        health = {
            "running": self._running,
            "started": self._started,
            "worker_alive": self.worker_thread.is_alive() if self.worker_thread else False,
            "queue_size": self.queue.qsize(),
            "queue_full": self.queue.full(),
        }
        logger.debug(f"BackgroundTransport health: {health}")
        return health
    
    def send(self, event: ErrorEvent) -> bool:
        """Send event to background queue (non-blocking)"""
        # Ensure worker is started
        if not self._started or not self._running:
            logger.warning("⚠️ Background transport not running, starting worker...")
            self._start_worker()
        
        # Health check
        if not self.worker_thread or not self.worker_thread.is_alive():
            logger.error("❌ Background worker thread is dead! Attempting restart...")
            self._started = False
            self._start_worker()
            
            # If still dead, fall back to synchronous send
            if not self.worker_thread or not self.worker_thread.is_alive():
                logger.error("❌ Worker restart failed, falling back to synchronous send")
                return self.base_transport.send(event)
        
        try:
            # Try to put in queue without blocking
            self.queue.put_nowait(event)
            queue_size = self.queue.qsize()
            logger.info(f"✅ Queued event {event.id} (queue size: {queue_size})")
            
            # Warn if queue is getting full
            if queue_size > self.queue.maxsize * 0.8:
                logger.warning(f"⚠️ Queue is {(queue_size/self.queue.maxsize)*100:.1f}% full!")
            
            return True
            
        except queue.Full:
            logger.error(
                f"❌ Background transport queue is full ({self.queue.qsize()} items), "
                f"dropping event {event.id}"
            )
            return False
        except Exception as e:
            logger.error(f"❌ Failed to queue event: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def send_async(self, event: ErrorEvent) -> Optional[asyncio.Future]:
        """
        Async interface - just delegates to send() which queues the event.
        Returns None since queuing is non-blocking.
        """
        self.send(event)
        return None
    
    def flush(self, timeout: float = 5.0) -> bool:
        """Wait for all queued events to be processed"""
        try:
            logger.info(f"Flushing BackgroundTransport (queue size: {self.queue.qsize()})...")
            self.queue.join()
            logger.info("✅ BackgroundTransport flushed")
            return True
        except Exception as e:
            logger.error(f"Flush failed: {e}")
            return False
    
    def close(self) -> None:
        """Close the background transport"""
        logger.info("Closing BackgroundTransport...")
        self._running = False
        
        # Flush remaining events
        try:
            if self.queue.qsize() > 0:
                logger.info(f"Flushing {self.queue.qsize()} remaining events...")
                self.flush(timeout=5.0)
        except Exception as e:
            logger.warning(f"Failed to flush during close: {e}")
        
        if self.worker_thread and self.worker_thread.is_alive():
            # Give worker time to finish
            self.worker_thread.join(timeout=2.0)
            if self.worker_thread.is_alive():
                logger.warning("Worker thread did not stop gracefully")
        
        self.base_transport.close()
        logger.info("BackgroundTransport closed")


def create_default_transport() -> Transport:
    """Create default transport based on settings"""
    settings = get_settings()
    transports: List[Transport] = []

    # Always add console transport if enabled
    if settings.log_to_console:
        console_transport = ConsoleTransport()
        transports.append(console_transport)
        logger.debug("Added ConsoleTransport")

    # Add Kafka transport if enabled
    if settings.KAFKA_ENABLED_RCA:
        try:
            logger.info(f"Initializing Kafka transport for {settings.KAFKA_BOOTSTRAP_SERVERS_RCA}")
            kafka_transport = KafkaTransport()
            logger.info("✅ KafkaTransport created")
            
            # Wrap in background transport
            if settings.use_background_transport:
                logger.info("Wrapping Kafka transport in BackgroundTransport")
                kafka_transport = BackgroundTransport(kafka_transport)
                logger.info("✅ BackgroundTransport created and started")
            
            transports.append(kafka_transport)
            logger.info("✅ Kafka transport initialized")
            
        except Exception as e:
            logger.error(f"❌ Failed to initialize Kafka transport: {e}")
            import traceback
            traceback.print_exc()

    # Return multi-transport or console fallback
    if len(transports) > 1:
        logger.info(f"Creating MultiTransport with {len(transports)} transports")
        return MultiTransport(transports)
    elif transports:
        logger.info(f"Using single transport: {type(transports[0]).__name__}")
        return transports[0]
    else:
        logger.warning("No transports configured, using ConsoleTransport fallback")
        return ConsoleTransport()