"""Event Subscriber and Routing System.

Provides subscriber management, event routing, and subscription lifecycle
for the ACB Events System using the unified messaging adapter's pub/sub backend.
Supports both pull and push subscription models with advanced filtering and routing.

Features:
- Pub/sub messaging backend integration
- Event subscription management
- Advanced event filtering and routing
- Pull and push subscription models
- Event buffering and batching
- Subscription health monitoring
"""

import asyncio
import logging
import typing as t
from collections import defaultdict, deque
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager, suppress
from datetime import datetime
from enum import Enum
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field
from acb.services import ServiceBase, ServiceSettings

from ._base import (
    Event,
    EventHandler,
    EventHandlerResult,
    EventSubscription,
)


class SubscriptionMode(Enum):
    """Subscription delivery modes."""

    PUSH = "push"  # Events are pushed to handlers
    PULL = "pull"  # Handlers pull events from queue
    HYBRID = "hybrid"  # Mix of push and pull based on conditions


class SubscriberSettings(ServiceSettings):
    """Settings for event subscriber configuration."""

    # Subscription configuration
    default_mode: SubscriptionMode = Field(default=SubscriptionMode.PUSH)
    max_subscriptions: int = Field(
        default=1000,
        description="Maximum number of subscriptions",
    )
    subscription_timeout: float = Field(
        default=30.0,
        description="Default subscription timeout",
    )

    # Buffer configuration
    enable_buffering: bool = Field(default=True)
    buffer_size: int = Field(
        default=1000,
        description="Maximum events in buffer per subscription",
    )
    buffer_timeout: float = Field(default=5.0, description="Buffer flush timeout")

    # Batch processing
    enable_batching: bool = Field(default=False)
    batch_size: int = Field(default=10, description="Events per batch")
    batch_timeout: float = Field(default=1.0, description="Batch timeout")

    # Performance settings
    max_concurrent_handlers: int = Field(default=100)
    handler_timeout: float = Field(default=30.0)

    # Health monitoring
    health_check_enabled: bool = Field(default=True)

    # Retry configuration
    enable_retries: bool = Field(default=True)
    max_retries: int = Field(default=3)
    retry_delay: float = Field(default=1.0)


class EventFilter(BaseModel):
    """Event filtering configuration."""

    # Basic filters
    event_types: list[str] | None = Field(
        default=None,
        description="Allowed event types",
    )
    sources: list[str] | None = Field(default=None, description="Allowed event sources")
    tags: list[str] | None = Field(default=None, description="Required tags")

    # Content filters
    payload_filters: dict[str, t.Any] | None = Field(
        default=None,
        description="Payload field filters",
    )
    header_filters: dict[str, t.Any] | None = Field(
        default=None,
        description="Header field filters",
    )

    # Pattern matching
    event_type_patterns: list[str] | None = Field(
        default=None,
        description="Event type regex patterns",
    )
    source_patterns: list[str] | None = Field(
        default=None,
        description="Source regex patterns",
    )

    # Priority and routing
    min_priority: str | None = Field(default=None, description="Minimum event priority")
    routing_keys: list[str] | None = Field(
        default=None,
        description="Required routing keys",
    )

    def matches(self, event: Event) -> bool:
        """Check if event matches this filter."""
        return (
            self._matches_basic_filters(event)
            and self._matches_content_filters(event)
            and self._matches_pattern_filters(event)
            and self._matches_priority_filter(event)
            and self._matches_routing_keys(event)
        )

    def _matches_basic_filters(self, event: Event) -> bool:
        """Check basic event type, source, and tags filters."""
        # Check event types
        if self.event_types and event.metadata.event_type not in self.event_types:
            return False

        # Check sources
        if self.sources and event.metadata.source not in self.sources:
            return False

        # Check tags
        if self.tags:
            required_tags = set(self.tags)
            if not required_tags.issubset(set(event.metadata.tags)):
                return False

        return True

    def _matches_content_filters(self, event: Event) -> bool:
        """Check payload and header content filters."""
        # Check payload filters
        if self.payload_filters:
            for key, expected_value in self.payload_filters.items():
                if key not in event.payload or event.payload[key] != expected_value:
                    return False

        # Check header filters
        if self.header_filters:
            for key, expected_value in self.header_filters.items():
                if (
                    key not in event.metadata.headers
                    or event.metadata.headers[key] != expected_value
                ):
                    return False

        return True

    def _matches_pattern_filters(self, event: Event) -> bool:
        """Check regex pattern filters for event type and source."""
        import re

        # Check event type patterns
        if self.event_type_patterns:
            if not any(
                re.match(
                    pattern, event.metadata.event_type
                )  # REGEX OK: User-provided pattern matching for event filtering
                for pattern in self.event_type_patterns
            ):
                return False

        # Check source patterns
        if self.source_patterns:
            if not any(
                re.match(
                    pattern, event.metadata.source
                )  # REGEX OK: User-provided pattern matching for source filtering
                for pattern in self.source_patterns
            ):
                return False

        return True

    def _matches_priority_filter(self, event: Event) -> bool:
        """Check minimum priority filter using match statement."""
        if not self.min_priority:
            return True

        # Use match statement for priority comparison
        match self.min_priority:
            case "low":
                min_level = 0
            case "normal":
                min_level = 1
            case "high":
                min_level = 2
            case "critical":
                min_level = 3
            case _:
                min_level = 0

        match event.metadata.priority.value:
            case "low":
                event_level = 0
            case "normal":
                event_level = 1
            case "high":
                event_level = 2
            case "critical":
                event_level = 3
            case _:
                event_level = 0

        return event_level >= min_level

    def _matches_routing_keys(self, event: Event) -> bool:
        """Check routing key filters."""
        if not self.routing_keys:
            return True

        return (
            event.metadata.routing_key is not None
            and event.metadata.routing_key in self.routing_keys
        )


class EventBuffer:
    """Event buffer for subscription event queuing."""

    def __init__(self, max_size: int = 1000, timeout: float = 5.0) -> None:
        self._buffer: deque[Event] = deque(maxlen=max_size)
        self._max_size = max_size
        self._timeout = timeout
        self._lock = asyncio.Lock()
        self._not_empty = asyncio.Condition(self._lock)

    async def put(self, event: Event) -> None:
        """Add event to buffer."""
        async with self._lock:
            self._buffer.append(event)
            self._not_empty.notify()

    async def get(self, timeout: float | None = None) -> Event | None:
        """Get next event from buffer."""
        async with self._not_empty:
            while not self._buffer:
                try:
                    await asyncio.wait_for(
                        self._not_empty.wait(),
                        timeout=timeout or self._timeout,
                    )
                except TimeoutError:
                    return None

            return self._buffer.popleft()

    async def get_batch(
        self,
        batch_size: int,
        timeout: float | None = None,
    ) -> list[Event]:
        """Get a batch of events from buffer."""
        events = []
        batch_timeout = timeout or self._timeout

        # Get first event (wait if necessary)
        first_event = await self.get(batch_timeout)
        if first_event:
            events.append(first_event)

        # Get remaining events (non-blocking)
        for _ in range(batch_size - 1):
            try:
                event = await asyncio.wait_for(self.get(0.01), timeout=0.01)
                if event:
                    events.append(event)
                else:
                    break
            except TimeoutError:
                break

        return events

    def size(self) -> int:
        """Get current buffer size."""
        return len(self._buffer)

    def is_full(self) -> bool:
        """Check if buffer is full."""
        return len(self._buffer) >= self._max_size

    async def clear(self) -> list[Event]:
        """Clear buffer and return all events."""
        async with self._lock:
            events = list(self._buffer)
            self._buffer.clear()
            return events


class ManagedSubscription(BaseModel):
    """Managed subscription with routing and health tracking."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    subscription: EventSubscription
    filter: EventFilter | None = None
    mode: SubscriptionMode = SubscriptionMode.PUSH
    buffer: EventBuffer | None = None

    # Health tracking
    created_at: datetime = Field(default_factory=datetime.now)
    last_activity: datetime = Field(default_factory=datetime.now)
    events_processed: int = 0
    events_failed: int = 0
    avg_processing_time: float = 0.0

    # State management
    active: bool = True
    paused: bool = False
    error_count: int = 0
    last_error: str | None = None

    def record_success(self, processing_time: float) -> None:
        """Record successful event processing."""
        self.last_activity = datetime.now()
        self.events_processed += 1
        # Update running average
        if self.events_processed == 1:
            self.avg_processing_time = processing_time
        else:
            self.avg_processing_time = (
                self.avg_processing_time * (self.events_processed - 1) + processing_time
            ) / self.events_processed

    def record_failure(self, error: str) -> None:
        """Record failed event processing."""
        self.last_activity = datetime.now()
        self.events_failed += 1
        self.error_count += 1
        self.last_error = error

    def get_health_score(self) -> float:
        """Calculate health score (0.0 to 1.0)."""
        if self.events_processed + self.events_failed == 0:
            return 1.0

        success_rate = self.events_processed / (
            self.events_processed + self.events_failed
        )
        error_penalty = min(self.error_count * 0.1, 0.5)  # Max 50% penalty
        return max(0.0, success_rate - error_penalty)

    def is_healthy(self, min_score: float = 0.7) -> bool:
        """Check if subscription is healthy."""
        return self.active and not self.paused and self.get_health_score() >= min_score


class EventRouter:
    """Event routing engine for subscriptions."""

    def __init__(self) -> None:
        self._type_routes: dict[str, list[ManagedSubscription]] = defaultdict(list)
        self._wildcard_routes: list[ManagedSubscription] = []
        self._filtered_routes: list[ManagedSubscription] = []

    def add_subscription(self, managed_sub: ManagedSubscription) -> None:
        """Add subscription to routing tables."""
        if managed_sub.subscription.event_type:
            self._type_routes[managed_sub.subscription.event_type].append(managed_sub)
        elif managed_sub.filter:
            self._filtered_routes.append(managed_sub)
        else:
            self._wildcard_routes.append(managed_sub)

    def remove_subscription(self, subscription_id: UUID) -> bool:
        """Remove subscription from routing tables."""
        # Check type routes
        for subs in self._type_routes.values():
            for i, managed_sub in enumerate(subs):
                if managed_sub.subscription.subscription_id == subscription_id:
                    subs.pop(i)
                    return True

        # Check wildcard routes
        for i, managed_sub in enumerate(self._wildcard_routes):
            if managed_sub.subscription.subscription_id == subscription_id:
                self._wildcard_routes.pop(i)
                return True

        # Check filtered routes
        for i, managed_sub in enumerate(self._filtered_routes):
            if managed_sub.subscription.subscription_id == subscription_id:
                self._filtered_routes.pop(i)
                return True

        return False

    def find_matching_subscriptions(self, event: Event) -> list[ManagedSubscription]:
        """Find all subscriptions that should receive this event."""
        matching = []

        # Check type-specific routes
        type_subs = self._type_routes.get(event.metadata.event_type, [])
        for managed_sub in type_subs:
            if self._subscription_matches(managed_sub, event):
                matching.append(managed_sub)

        # Check filtered routes
        for managed_sub in self._filtered_routes:
            if self._subscription_matches(managed_sub, event):
                matching.append(managed_sub)

        # Check wildcard routes
        for managed_sub in self._wildcard_routes:
            if self._subscription_matches(managed_sub, event):
                matching.append(managed_sub)

        return matching

    def _subscription_matches(
        self,
        managed_sub: ManagedSubscription,
        event: Event,
    ) -> bool:
        """Check if a managed subscription matches an event."""
        if not managed_sub.active or managed_sub.paused:
            return False

        # Check subscription-level matching
        if not managed_sub.subscription.matches(event):
            return False

        # Check filter-level matching
        return not (managed_sub.filter and not managed_sub.filter.matches(event))


class EventSubscriber(ServiceBase):
    """Event subscriber with routing and subscription management using queue adapter."""

    def __init__(self, settings: SubscriberSettings | None = None) -> None:
        super().__init__()
        self._settings = settings or SubscriberSettings()
        self._router = EventRouter()
        self._subscriptions: dict[UUID, ManagedSubscription] = {}

        # PubSub adapter integration (lazy initialization)
        self._pubsub: t.Any = None
        self._pubsub_class: t.Any = None

        # Processing control
        self._processing_semaphore = asyncio.Semaphore(
            self._settings.max_concurrent_handlers,
        )
        self._shutdown_event = asyncio.Event()

        # Health monitoring
        self._health_check_task: asyncio.Task[None] | None = None

        self._logger = logging.getLogger(__name__)

    async def _initialize(self) -> None:
        """Initialize the event subscriber (ServiceBase requirement)."""
        # Start health monitoring if enabled
        if self._settings.health_check_enabled:
            self._health_check_task = asyncio.create_task(self._health_check_worker())

        self._logger.info("Event subscriber started")

    async def _shutdown(self) -> None:
        """Shutdown the event subscriber (ServiceBase requirement)."""
        self._shutdown_event.set()

        # Stop health monitoring
        if self._health_check_task and not self._health_check_task.done():
            self._health_check_task.cancel()
            with suppress(asyncio.CancelledError):
                await self._health_check_task

        self._logger.info("Event subscriber stopped")

    async def start(self) -> None:
        """Start the event subscriber (public API)."""
        await self.initialize()

    async def stop(self) -> None:
        """Stop the event subscriber (public API)."""
        await self.shutdown()

    async def subscribe(
        self,
        handler: EventHandler,
        event_type: str | None = None,
        event_filter: EventFilter | None = None,
        mode: SubscriptionMode | None = None,
        **subscription_kwargs: t.Any,
    ) -> UUID:
        """Create a new event subscription.

        Args:
            handler: Event handler instance
            event_type: Specific event type to subscribe to
            event_filter: Advanced event filtering
            mode: Subscription mode (push/pull/hybrid)
            **subscription_kwargs: Additional subscription options

        Returns:
            Subscription ID
        """
        if len(self._subscriptions) >= self._settings.max_subscriptions:
            msg = "Maximum number of subscriptions reached"
            raise ValueError(msg)

        # Create subscription
        subscription = EventSubscription(
            handler=handler,
            event_type=event_type,
            **subscription_kwargs,
        )

        # Create buffer if needed
        buffer = None
        sub_mode = mode or self._settings.default_mode
        if self._settings.enable_buffering and sub_mode in (
            SubscriptionMode.PULL,
            SubscriptionMode.HYBRID,
        ):
            buffer = EventBuffer(
                max_size=self._settings.buffer_size,
                timeout=self._settings.buffer_timeout,
            )

        # Create managed subscription
        managed_sub = ManagedSubscription(
            subscription=subscription,
            filter=event_filter,
            mode=sub_mode,
            buffer=buffer,
        )

        # Register subscription
        self._subscriptions[subscription.subscription_id] = managed_sub
        self._router.add_subscription(managed_sub)

        self._logger.debug(
            "Added subscription: %s (handler=%s, event_type=%s)",
            subscription.subscription_id,
            handler.handler_name,
            event_type,
        )

        return subscription.subscription_id

    async def unsubscribe(self, subscription_id: UUID) -> bool:
        """Remove an event subscription.

        Args:
            subscription_id: ID of subscription to remove

        Returns:
            True if subscription was found and removed
        """
        if subscription_id not in self._subscriptions:
            return False

        # Remove from router
        self._router.remove_subscription(subscription_id)

        # Remove from subscriptions
        managed_sub = self._subscriptions.pop(subscription_id)

        # Clear buffer if exists
        if managed_sub.buffer:
            await managed_sub.buffer.clear()

        self._logger.debug("Removed subscription: %s", subscription_id)
        return True

    async def deliver_event(self, event: Event) -> dict[UUID, EventHandlerResult]:
        """Deliver event to matching subscriptions.

        Args:
            event: Event to deliver

        Returns:
            Dict mapping subscription IDs to handler results
        """
        # Find matching subscriptions
        matching_subs = self._router.find_matching_subscriptions(event)

        if not matching_subs:
            return {}

        # Deliver to each subscription based on mode
        results = {}
        tasks = []

        for managed_sub in matching_subs:
            if managed_sub.mode == SubscriptionMode.PUSH:
                task = asyncio.create_task(self._deliver_push(event, managed_sub))
                tasks.append((managed_sub.subscription.subscription_id, task))
            elif managed_sub.mode in (SubscriptionMode.PULL, SubscriptionMode.HYBRID):
                # Add to buffer for pull-based consumption
                if managed_sub.buffer:
                    await managed_sub.buffer.put(event)
                    results[managed_sub.subscription.subscription_id] = (
                        EventHandlerResult(success=True)
                    )

        # Wait for push deliveries
        if tasks:
            completed_tasks = await asyncio.gather(
                *[task for _, task in tasks],
                return_exceptions=True,
            )

            for (sub_id, _), result in zip(tasks, completed_tasks, strict=False):
                if isinstance(result, Exception):
                    results[sub_id] = EventHandlerResult(
                        success=False,
                        error_message=str(result),
                    )
                elif isinstance(result, EventHandlerResult):
                    results[sub_id] = result
                else:
                    # Handle unexpected result type
                    results[sub_id] = EventHandlerResult(
                        success=False,
                        error_message="Unexpected result type",
                    )

        return results

    async def pull_events(
        self,
        subscription_id: UUID,
        batch_size: int = 1,
        timeout: float | None = None,
    ) -> list[Event]:
        """Pull events for a subscription (pull mode).

        Args:
            subscription_id: Subscription ID
            batch_size: Maximum number of events to return
            timeout: Pull timeout

        Returns:
            List of events
        """
        managed_sub = self._subscriptions.get(subscription_id)
        if not managed_sub or not managed_sub.buffer:
            return []

        if batch_size == 1:
            event = await managed_sub.buffer.get(timeout)
            return [event] if event else []
        return await managed_sub.buffer.get_batch(batch_size, timeout)

    async def _deliver_push(
        self,
        event: Event,
        managed_sub: ManagedSubscription,
    ) -> EventHandlerResult:
        """Deliver event using push mode."""
        async with self._processing_semaphore:
            start_time = asyncio.get_event_loop().time()

            try:
                # Apply timeout
                result = await asyncio.wait_for(
                    managed_sub.subscription.handler.handle(event),
                    timeout=self._settings.handler_timeout,
                )

                # Record success
                processing_time = asyncio.get_event_loop().time() - start_time
                managed_sub.record_success(processing_time)

                return result

            except TimeoutError:
                error_msg = f"Handler timeout after {self._settings.handler_timeout}s"
                managed_sub.record_failure(error_msg)
                return EventHandlerResult(success=False, error_message=error_msg)

            except Exception as e:
                error_msg = str(e)
                managed_sub.record_failure(error_msg)
                return await managed_sub.subscription.handler.handle_error(event, e)

    async def _health_check_worker(self) -> None:
        """Background worker for subscription health monitoring."""
        while not self._shutdown_event.is_set():
            try:
                await asyncio.sleep(self._settings.health_check_interval)

                unhealthy_subs = [
                    sub_id
                    for sub_id, managed_sub in self._subscriptions.items()
                    if not managed_sub.is_healthy()
                ]

                if unhealthy_subs:
                    self._logger.warning(
                        "Found %d unhealthy subscriptions: %s",
                        len(unhealthy_subs),
                        unhealthy_subs,
                    )

            except Exception as e:
                self._logger.exception("Health check error: %s", e)

    # Subscription management methods
    async def pause_subscription(self, subscription_id: UUID) -> bool:
        """Pause a subscription."""
        managed_sub = self._subscriptions.get(subscription_id)
        if managed_sub:
            managed_sub.paused = True
            return True
        return False

    async def resume_subscription(self, subscription_id: UUID) -> bool:
        """Resume a paused subscription."""
        managed_sub = self._subscriptions.get(subscription_id)
        if managed_sub:
            managed_sub.paused = False
            return True
        return False

    async def get_subscription_stats(
        self,
        subscription_id: UUID,
    ) -> dict[str, t.Any] | None:
        """Get statistics for a subscription."""
        managed_sub = self._subscriptions.get(subscription_id)
        if not managed_sub:
            return None

        return {
            "subscription_id": str(subscription_id),
            "handler_name": managed_sub.subscription.handler.handler_name,
            "event_type": managed_sub.subscription.event_type,
            "mode": managed_sub.mode.value,
            "active": managed_sub.active,
            "paused": managed_sub.paused,
            "events_processed": managed_sub.events_processed,
            "events_failed": managed_sub.events_failed,
            "avg_processing_time": managed_sub.avg_processing_time,
            "health_score": managed_sub.get_health_score(),
            "buffer_size": managed_sub.buffer.size() if managed_sub.buffer else 0,
        }

    async def get_all_subscription_stats(self) -> list[dict[str, t.Any]]:
        """Get statistics for all subscriptions."""
        return [
            sub_stats
            for sub_id in self._subscriptions
            if (sub_stats := await self.get_subscription_stats(sub_id)) is not None
        ]

    # Context manager support (delegating to ServiceBase)
    async def __aenter__(self) -> "EventSubscriber":
        await self.initialize()
        return self

    async def __aexit__(self, exc_type: t.Any, exc_val: t.Any, exc_tb: t.Any) -> None:
        await self.shutdown()


# Factory function
def create_event_subscriber(**settings_kwargs: t.Any) -> EventSubscriber:
    """Create an event subscriber with specified settings.

    Args:
        **settings_kwargs: Settings overrides

    Returns:
        Configured EventSubscriber instance
    """
    settings = SubscriberSettings(**settings_kwargs)
    return EventSubscriber(settings)


# Convenience context manager
@asynccontextmanager
async def event_subscriber_context(
    settings: SubscriberSettings | None = None,
) -> AsyncGenerator[EventSubscriber]:
    """Context manager for event subscriber lifecycle.

    Args:
        settings: Subscriber settings

    Yields:
        Started EventSubscriber instance
    """
    subscriber = EventSubscriber(settings)
    try:
        await subscriber.initialize()
        yield subscriber
    finally:
        await subscriber.shutdown()
