"""Async channel for message communication"""

import asyncio
from typing import Any, AsyncIterator, Callable, Awaitable, Optional, Dict
from dataclasses import dataclass, field
from .message import Message, MessageType, MessagePriority
from .subscriber import Subscriber, SubscriberRegistry


@dataclass
class ChannelOptions:
    """Channel configuration options"""
    name: str = ""
    max_queue_size: int = 1000
    max_subscribers: int = 100
    enable_broadcast: bool = True
    enable_persistence: bool = False
    message_ttl: int = 3600


class Channel:
    """Async channel for message communication between components"""
    
    def __init__(self, name: str, options: Optional[ChannelOptions] = None):
        self.name = name
        self.options = options or ChannelOptions(name=name)
        self._registry = SubscriberRegistry()
        self._message_queue: asyncio.Queue = asyncio.Queue(maxsize=self.options.max_queue_size)
        self._running = False
        self._task: Optional[asyncio.Task] = None
        self._handlers: Dict[str, Callable] = {}
    
    async def start(self):
        """Start the channel"""
        if self._running:
            return
        self._running = True
        self._task = asyncio.create_task(self._process_messages())
    
    async def stop(self):
        """Stop the channel"""
        self._running = False
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass
    
    async def _process_messages(self):
        """Process messages from queue"""
        while self._running:
            try:
                message = await asyncio.wait_for(
                    self._message_queue.get(),
                    timeout=1.0
                )
                await self._dispatch_message(message)
            except asyncio.TimeoutError:
                continue
            except asyncio.CancelledError:
                break
            except Exception:
                pass
    
    async def _dispatch_message(self, message: Message):
        """Dispatch message to subscribers"""
        subscribers = self._registry.get_subscribers(message.channel)
        
        if self.options.enable_broadcast:
            subscribers = list(subscribers) + self._registry.get_all_subscribers()
        
        tasks = []
        for subscriber in subscribers:
            if subscriber.subscription_type.value == "broadcast":
                task = asyncio.create_task(subscriber.handle_message(message))
                tasks.append(task)
            elif self._match_subscriber(subscriber, message):
                task = asyncio.create_task(subscriber.handle_message(message))
                tasks.append(task)
        
        if tasks:
            await asyncio.gather(*tasks, return_exceptions=True)
    
    def _match_subscriber(self, subscriber: Subscriber, message: Message) -> bool:
        """Check if subscriber should receive message"""
        if subscriber.filters:
            for key, expected in subscriber.filters.items():
                if hasattr(message, key):
                    actual = getattr(message, key)
                    if isinstance(expected, (list, tuple)):
                        if actual not in expected:
                            return False
                    elif actual != expected:
                        return False
        return True
    
    async def publish(self, message: Message) -> bool:
        """Publish message to channel"""
        if not self._running:
            return False
        
        try:
            self._message_queue.put_nowait(message)
            return True
        except asyncio.QueueFull:
            return False
    
    async def send(self, channel: str, payload: Any, **kwargs) -> bool:
        """Send message to channel"""
        message = Message(channel=channel, payload=payload, **kwargs)
        return await self.publish(message)
    
    async def request(
        self,
        channel: str,
        payload: Any,
        timeout: float = 30.0,
        **kwargs
    ) -> Optional[Message]:
        """Send request and wait for response"""
        message = Message(
            channel=channel,
            type=MessageType.REQUEST,
            payload=payload,
            **kwargs
        )
        
        response_event = asyncio.Event()
        response_holder: list = [None]
        
        async def handler(msg: Message):
            if msg.type == MessageType.RESPONSE and msg.correlation_id == message.id:
                response_holder[0] = msg
                response_event.set()
        
        subscriber = self._registry.subscribe(
            channel=f"{channel}_response",
            callback=handler,
            name=f"temp_response_{message.id}"
        )
        
        try:
            await self.publish(message)
            
            try:
                await asyncio.wait_for(response_event.wait(), timeout=timeout)
                return response_holder[0]
            except asyncio.TimeoutError:
                return None
        finally:
            self._registry.unsubscribe(subscriber.id)
    
    def subscribe(
        self,
        channel: str,
        callback: Callable[[Any], Awaitable[None]],
        name: str = "",
        filters: Optional[Dict] = None,
        priority: int = 0
    ) -> Subscriber:
        """Subscribe to channel"""
        return self._registry.subscribe(channel, callback, name, filters, priority)
    
    def subscribe_pattern(
        self,
        pattern: str,
        callback: Callable[[Any], Awaitable[None]],
        name: str = "",
        filters: Optional[Dict] = None,
        priority: int = 0
    ) -> Subscriber:
        """Subscribe to channels matching pattern"""
        return self._registry.subscribe_pattern(pattern, callback, name, filters, priority)
    
    def subscribe_broadcast(
        self,
        callback: Callable[[Any], Awaitable[None]],
        name: str = "",
        filters: Optional[Dict] = None,
        priority: int = 0
    ) -> Subscriber:
        """Subscribe to all messages"""
        return self._registry.subscribe_broadcast(callback, name, filters, priority)
    
    def unsubscribe(self, subscriber_id: str) -> bool:
        """Unsubscribe"""
        return self._registry.unsubscribe(subscriber_id)
    
    @property
    def subscriber_count(self) -> int:
        """Get subscriber count"""
        return len(self._registry.get_all_subscribers())
    
    @property
    def queue_size(self) -> int:
        """Get current queue size"""
        return self._message_queue.qsize()
