import json
import logging
from typing import Dict, Any, Callable, List
from datetime import datetime
import pika
from pika.exceptions import AMQPConnectionError
import threading
from collections import defaultdict

logger = logging.getLogger(__name__)

class Event:
    """Base event class"""
    def __init__(self, event_type: str, data: Dict[str, Any], source: str = "unknown"):
        self.id = self._generate_id()
        self.type = event_type
        self.data = data
        self.source = source
        self.timestamp = datetime.utcnow().isoformat()
    
    def _generate_id(self) -> str:
        import uuid
        return str(uuid.uuid4())
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "id": self.id,
            "type": self.type,
            "data": self.data,
            "source": self.source,
            "timestamp": self.timestamp
        }
    
    def to_json(self) -> str:
        return json.dumps(self.to_dict())

class EventBus:
    """Event bus for pub/sub pattern with RabbitMQ backend"""
    
    def __init__(self, rabbitmq_url: str):
        self.rabbitmq_url = rabbitmq_url
        self.connection = None
        self.channel = None
        self.exchange_name = "license_events"
        self.local_handlers: Dict[str, List[Callable]] = defaultdict(list)
        self._connect()
    
    def _connect(self):
        """Establish connection to RabbitMQ"""
        try:
            parameters = pika.URLParameters(self.rabbitmq_url)
            self.connection = pika.BlockingConnection(parameters)
            self.channel = self.connection.channel()
            
            # Declare exchange
            self.channel.exchange_declare(
                exchange=self.exchange_name,
                exchange_type='topic',
                durable=True
            )
            
            logger.info("Connected to RabbitMQ")
        except AMQPConnectionError as e:
            logger.error(f"Failed to connect to RabbitMQ: {e}")
            # Fallback to local-only event handling
            self.connection = None
            self.channel = None
    
    def publish(self, event: Event):
        """Publish an event"""
        try:
            # Publish to RabbitMQ if connected
            if self.channel and not self.channel.is_closed:
                self.channel.basic_publish(
                    exchange=self.exchange_name,
                    routing_key=event.type,
                    body=event.to_json(),
                    properties=pika.BasicProperties(
                        delivery_mode=2,  # Make message persistent
                        content_type='application/json'
                    )
                )
                logger.debug(f"Published event: {event.type}")
            
            # Also handle local subscribers
            self._handle_local_event(event)
            
        except Exception as e:
            logger.error(f"Error publishing event: {e}")
            # Ensure local handlers still get called
            self._handle_local_event(event)
    
    def subscribe(self, event_type: str, handler: Callable):
        """Subscribe to an event type locally"""
        self.local_handlers[event_type].append(handler)
        logger.debug(f"Subscribed to {event_type}")
    
    def subscribe_queue(self, event_types: List[str], queue_name: str, handler: Callable):
        """Subscribe to events via RabbitMQ queue"""
        if not self.channel:
            logger.warning("RabbitMQ not connected, falling back to local subscription")
            for event_type in event_types:
                self.subscribe(event_type, handler)
            return
        
        try:
            # Declare queue
            self.channel.queue_declare(queue=queue_name, durable=True)
            
            # Bind queue to exchange for each event type
            for event_type in event_types:
                self.channel.queue_bind(
                    exchange=self.exchange_name,
                    queue=queue_name,
                    routing_key=event_type
                )
            
            # Set up consumer
            def callback(ch, method, properties, body):
                try:
                    event_data = json.loads(body)
                    event = Event(
                        event_type=event_data['type'],
                        data=event_data['data'],
                        source=event_data['source']
                    )
                    handler(event)
                    ch.basic_ack(delivery_tag=method.delivery_tag)
                except Exception as e:
                    logger.error(f"Error handling event: {e}")
                    ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
            
            self.channel.basic_consume(queue=queue_name, on_message_callback=callback)
            
            # Start consuming in a separate thread
            consumer_thread = threading.Thread(target=self.channel.start_consuming)
            consumer_thread.daemon = True
            consumer_thread.start()
            
            logger.info(f"Started consuming from queue: {queue_name}")
            
        except Exception as e:
            logger.error(f"Error setting up queue subscription: {e}")
    
    def _handle_local_event(self, event: Event):
        """Handle event with local subscribers"""
        handlers = self.local_handlers.get(event.type, [])
        for handler in handlers:
            try:
                handler(event)
            except Exception as e:
                logger.error(f"Error in event handler: {e}")
    
    def close(self):
        """Close RabbitMQ connection"""
        if self.connection and not self.connection.is_closed:
            self.connection.close()
            logger.info("Closed RabbitMQ connection")

# Event types
class EventTypes:
    """Centralized event type definitions"""
    
    # License events
    LICENSE_VALIDATED = "license.validated"
    LICENSE_VALIDATION_FAILED = "license.validation.failed"
    LICENSE_ACTIVATED = "license.activated"
    LICENSE_DEACTIVATED = "license.deactivated"
    LICENSE_TRANSFERRED = "license.transferred"
    LICENSE_EXPIRED = "license.expired"
    LICENSE_CREATED = "license.created"
    LICENSE_UPDATED = "license.updated"
    
    # Device events
    DEVICE_ADDED = "device.added"
    DEVICE_REMOVED = "device.removed"
    DEVICE_BLOCKED = "device.blocked"
    DEVICE_DEACTIVATED = "device.deactivated"
    
    # Anomaly events
    ANOMALY_DETECTED = "anomaly.detected"
    ANOMALY_RESOLVED = "anomaly.resolved"
    
    # Session events
    SESSION_STARTED = "session.started"
    SESSION_ENDED = "session.ended"
    SESSION_EXPIRED = "session.expired"
    
    # System events
    RATE_LIMIT_EXCEEDED = "system.rate_limit_exceeded"
    API_ERROR = "system.api_error"