"""
Asynchronous MetricsFirst client
"""

import asyncio
import json
import logging
from dataclasses import asdict
from typing import Optional, List, Dict, Any

try:
    import aiohttp
    HAS_AIOHTTP = True
except ImportError:
    HAS_AIOHTTP = False

from .types import (
    CommandEventData,
    InteractionEventData,
    ServiceEventData,
    ErrorEventData,
    PurchaseEventData,
    PurchaseStatus,
    RecurringChargeEventData,
    UserIdentifyData,
)

logger = logging.getLogger("metricsfirst")


class AsyncMetricsFirst:
    """
    Asynchronous MetricsFirst SDK client.
    
    Usage:
        mf = AsyncMetricsFirst(bot_id="your_bot_id", api_key="your_api_key")
        await mf.start()
        await mf.track_command(CommandEventData(user_id=123, command="/start"))
        await mf.shutdown()  # Call on exit to flush remaining events
    """
    
    DEFAULT_API_URL = "https://api.metricsfirst.com"
    
    def __init__(
        self,
        bot_id: str,
        api_key: str,
        api_url: Optional[str] = None,
        batch_events: bool = True,
        batch_size: int = 10,
        batch_interval: float = 5.0,
        debug: bool = False,
        timeout: float = 10.0,
    ):
        """
        Initialize the AsyncMetricsFirst client.
        
        Args:
            bot_id: Your bot's unique identifier
            api_key: Your API key from MetricsFirst dashboard
            api_url: Custom API URL (optional)
            batch_events: Whether to batch events before sending
            batch_size: Number of events per batch
            batch_interval: Seconds between batch flushes
            debug: Enable debug logging
            timeout: HTTP request timeout in seconds
        """
        if not HAS_AIOHTTP:
            raise ImportError(
                "aiohttp is required for AsyncMetricsFirst. "
                "Install with: pip install metricsfirst[async]"
            )
        
        self.bot_id = bot_id
        self.api_key = api_key
        self.api_url = (api_url or self.DEFAULT_API_URL).rstrip("/")
        self.batch_events = batch_events
        self.batch_size = batch_size
        self.batch_interval = batch_interval
        self.timeout = aiohttp.ClientTimeout(total=timeout)
        
        if debug:
            logging.basicConfig(level=logging.DEBUG)
            logger.setLevel(logging.DEBUG)
        
        self._queue: asyncio.Queue = asyncio.Queue()
        self._session: Optional[aiohttp.ClientSession] = None
        self._worker_task: Optional[asyncio.Task] = None
        self._shutdown_event = asyncio.Event()
    
    async def start(self):
        """Start the client and background worker"""
        self._session = aiohttp.ClientSession(timeout=self.timeout)
        self._shutdown_event.clear()
        
        if self.batch_events:
            self._worker_task = asyncio.create_task(self._worker())
        
        logger.debug("AsyncMetricsFirst client started")
    
    async def _worker(self):
        """Background worker that flushes events periodically"""
        events: List[Dict[str, Any]] = []
        
        while not self._shutdown_event.is_set():
            try:
                # Try to get an event with timeout
                event = await asyncio.wait_for(
                    self._queue.get(),
                    timeout=self.batch_interval
                )
                events.append(event)
                
                # Flush if batch is full
                if len(events) >= self.batch_size:
                    await self._flush_events(events)
                    events = []
                    
            except asyncio.TimeoutError:
                # Timeout - flush what we have
                if events:
                    await self._flush_events(events)
                    events = []
            except asyncio.CancelledError:
                break
        
        # Flush remaining events on shutdown
        while not self._queue.empty():
            try:
                events.append(self._queue.get_nowait())
            except asyncio.QueueEmpty:
                break
        
        if events:
            await self._flush_events(events)
    
    async def _flush_events(self, events: List[Dict[str, Any]]):
        """Send a batch of events to the API"""
        if not events:
            return
        
        try:
            tasks = [
                self._send_event(event["type"], event["data"])
                for event in events
            ]
            await asyncio.gather(*tasks, return_exceptions=True)
        except Exception as e:
            logger.error(f"Failed to flush events: {e}")
    
    async def _send_event(self, event_type: str, data: Dict[str, Any]):
        """Send a single event to the API"""
        if not self._session:
            logger.error("Client not started. Call start() first.")
            return
        
        payload = {
            "botId": self.bot_id,
            "type": event_type,
            "data": data,
        }
        
        try:
            async with self._session.post(
                f"{self.api_url}/api/analytics/track",
                json=payload,
                headers={"X-API-Key": self.api_key},
            ) as response:
                if response.status != 200:
                    text = await response.text()
                    logger.warning(f"API returned status {response.status}: {text}")
                else:
                    logger.debug(f"Event sent: {event_type}")
                    
        except aiohttp.ClientError as e:
            logger.error(f"HTTP error sending event: {e}")
        except Exception as e:
            logger.error(f"Error sending event: {e}")
    
    async def _track(self, event_type: str, data: Dict[str, Any]):
        """Queue or send an event"""
        if self.batch_events:
            await self._queue.put({"type": event_type, "data": data})
        else:
            await self._send_event(event_type, data)
    
    def _clean_dict(self, d: Dict[str, Any]) -> Dict[str, Any]:
        """Remove None values and convert enums"""
        result = {}
        for k, v in d.items():
            if v is None:
                continue
            if hasattr(v, "value"):  # Enum
                result[k] = v.value
            elif isinstance(v, dict):
                result[k] = self._clean_dict(v)
            else:
                result[k] = v
        return result
    
    # ==========================================
    # Tracking Methods
    # ==========================================
    
    async def track_command(self, data: CommandEventData):
        """Track a bot command"""
        await self._track("command", self._clean_dict(asdict(data)))
    
    async def track_interaction(self, data: InteractionEventData):
        """Track a user interaction"""
        await self._track("interaction", self._clean_dict(asdict(data)))
    
    async def track_service(self, data: ServiceEventData):
        """Track a service provided"""
        await self._track("service_provided", self._clean_dict(asdict(data)))
    
    async def track_error(self, data: ErrorEventData):
        """Track an error"""
        await self._track("error", self._clean_dict(asdict(data)))
    
    async def track_error_from_exception(
        self,
        exception: Exception,
        user_id: Optional[int] = None,
        severity: str = "error",
        context: Optional[Dict[str, Any]] = None,
    ):
        """Track an error from an exception"""
        import traceback
        
        data = ErrorEventData(
            error_type=type(exception).__name__,
            error_message=str(exception),
            user_id=user_id,
            error_stack=traceback.format_exc(),
            severity=severity,
            context=context or {},
        )
        await self.track_error(data)
    
    async def track_purchase_initiated(self, data: PurchaseEventData):
        """Track a purchase initiation"""
        data.status = PurchaseStatus.INITIATED
        await self._track("purchase_initiated", self._clean_dict(asdict(data)))
    
    async def track_purchase_completed(self, data: PurchaseEventData):
        """Track a completed purchase"""
        data.status = PurchaseStatus.COMPLETED
        await self._track("purchase_completed", self._clean_dict(asdict(data)))
    
    async def track_purchase_error(self, data: PurchaseEventData):
        """Track a failed purchase"""
        data.status = PurchaseStatus.FAILED
        await self._track("purchase_error", self._clean_dict(asdict(data)))
    
    async def track_recurring_charge_success(self, data: RecurringChargeEventData):
        """Track a successful recurring charge"""
        data.is_success = True
        await self._track("recurring_charge_success", self._clean_dict(asdict(data)))
    
    async def track_recurring_charge_failed(self, data: RecurringChargeEventData):
        """Track a failed recurring charge"""
        data.is_success = False
        await self._track("recurring_charge_failed", self._clean_dict(asdict(data)))
    
    async def identify(self, data: UserIdentifyData):
        """Identify a user with properties"""
        if not self._session:
            logger.error("Client not started. Call start() first.")
            return
        
        payload = self._clean_dict(asdict(data))
        
        try:
            async with self._session.post(
                f"{self.api_url}/api/users/identify",
                json={"botId": self.bot_id, **payload},
                headers={"X-API-Key": self.api_key},
            ) as response:
                if response.status != 200:
                    logger.warning(f"Identify returned status {response.status}")
                    
        except Exception as e:
            logger.error(f"Error identifying user: {e}")
    
    async def flush(self):
        """Manually flush all pending events"""
        if not self.batch_events:
            return
        
        events = []
        while not self._queue.empty():
            try:
                events.append(self._queue.get_nowait())
            except asyncio.QueueEmpty:
                break
        
        await self._flush_events(events)
    
    async def shutdown(self):
        """Shutdown the client and flush remaining events"""
        self._shutdown_event.set()
        
        if self._worker_task:
            self._worker_task.cancel()
            try:
                await self._worker_task
            except asyncio.CancelledError:
                pass
        
        # Flush any remaining events
        await self.flush()
        
        if self._session:
            await self._session.close()
        
        logger.debug("AsyncMetricsFirst client shutdown complete")
    
    async def __aenter__(self):
        await self.start()
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.shutdown()

