#!/usr/bin/env python
"""
Performance Monitoring System
Tracks operation duration and performance metrics.
"""

import time
import functools
import logging
import asyncio
from typing import Dict, Any, Callable, Optional
from collections import defaultdict
import statistics

logger = logging.getLogger(__name__)


class PerformanceMonitor:
    """Monitors and tracks performance metrics for operations."""

    def __init__(self):
        """Initialize the performance monitor."""
        self._metrics = defaultdict(list)  # operation_name -> [durations]
        self._operation_counts = defaultdict(int)
        self._slow_threshold = 1.0  # Log operations taking > 1 second

    def record_duration(self, operation: str, duration: float):
        """
        Record the duration of an operation.

        Args:
            operation: Operation name
            duration: Duration in seconds
        """
        self._metrics[operation].append(duration)
        self._operation_counts[operation] += 1

        # Log slow operations
        if duration > self._slow_threshold:
            logger.warning(f"Slow operation '{operation}': {duration:.3f}s")

        # Keep only last 1000 measurements per operation
        if len(self._metrics[operation]) > 1000:
            self._metrics[operation] = self._metrics[operation][-1000:]

    def get_stats(self, operation: str) -> Optional[Dict[str, Any]]:
        """
        Get statistics for an operation.

        Args:
            operation: Operation name

        Returns:
            Dictionary with min, max, mean, median, count
        """
        if operation not in self._metrics or not self._metrics[operation]:
            return None

        durations = self._metrics[operation]
        return {
            'operation': operation,
            'count': len(durations),
            'total_calls': self._operation_counts[operation],
            'min': min(durations),
            'max': max(durations),
            'mean': statistics.mean(durations),
            'median': statistics.median(durations),
            'p95': self._percentile(durations, 0.95),
            'p99': self._percentile(durations, 0.99)
        }

    def get_all_stats(self) -> Dict[str, Dict[str, Any]]:
        """
        Get statistics for all operations.

        Returns:
            Dictionary mapping operation names to their stats
        """
        return {
            op: self.get_stats(op)
            for op in self._metrics.keys()
        }

    def _percentile(self, data: list, percentile: float) -> float:
        """Calculate percentile value."""
        if not data:
            return 0.0
        sorted_data = sorted(data)
        index = int(len(sorted_data) * percentile)
        return sorted_data[min(index, len(sorted_data) - 1)]

    def clear(self, operation: Optional[str] = None):
        """
        Clear metrics for an operation or all operations.

        Args:
            operation: Optional operation name. If None, clears all.
        """
        if operation:
            self._metrics.pop(operation, None)
            self._operation_counts.pop(operation, None)
        else:
            self._metrics.clear()
            self._operation_counts.clear()

    def set_slow_threshold(self, threshold: float):
        """
        Set the threshold for logging slow operations.

        Args:
            threshold: Threshold in seconds
        """
        self._slow_threshold = threshold
        logger.info(f"Slow operation threshold set to {threshold}s")


# Global performance monitor
_monitor = PerformanceMonitor()


def get_monitor() -> PerformanceMonitor:
    """Get the global performance monitor instance."""
    return _monitor


def measure_performance(operation_name: Optional[str] = None):
    """
    Decorator to measure function performance.

    Args:
        operation_name: Optional custom operation name. If None, uses function name.

    Example:
        @measure_performance()
        def process_event(player, event):
            # function code
            pass

        @measure_performance("custom_operation")
        def complex_calculation():
            # function code
            pass
    """
    def decorator(func: Callable) -> Callable:
        op_name = operation_name or f"{func.__module__}.{func.__name__}"

        @functools.wraps(func)
        def sync_wrapper(*args, **kwargs):
            start_time = time.time()
            try:
                result = func(*args, **kwargs)
                return result
            finally:
                duration = time.time() - start_time
                _monitor.record_duration(op_name, duration)

        @functools.wraps(func)
        async def async_wrapper(*args, **kwargs):
            start_time = time.time()
            try:
                result = await func(*args, **kwargs)
                return result
            finally:
                duration = time.time() - start_time
                _monitor.record_duration(op_name, duration)

        # Return appropriate wrapper based on whether function is async
        if asyncio.iscoroutinefunction(func):
            return async_wrapper
        return sync_wrapper

    return decorator


# Context manager for measuring code blocks
class measure_block:
    """
    Context manager for measuring performance of code blocks.

    Example:
        with measure_block('database_query'):
            result = execute_query()
    """

    def __init__(self, operation_name: str):
        """
        Initialize the measurement context.

        Args:
            operation_name: Name of the operation being measured
        """
        self.operation_name = operation_name
        self.start_time = None

    def __enter__(self):
        """Start measuring."""
        self.start_time = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Stop measuring and record duration."""
        duration = time.time() - self.start_time
        _monitor.record_duration(self.operation_name, duration)


# Convenience functions
def log_performance_report():
    """Log a performance report for all monitored operations."""
    stats = _monitor.get_all_stats()
    if not stats:
        logger.info("No performance data available")
        return

    logger.info("=== Performance Report ===")
    for operation, data in sorted(stats.items(), key=lambda x: x[1]['mean'], reverse=True):
        logger.info(
            f"{operation}: "
            f"calls={data['total_calls']}, "
            f"mean={data['mean']*1000:.1f}ms, "
            f"median={data['median']*1000:.1f}ms, "
            f"p95={data['p95']*1000:.1f}ms, "
            f"max={data['max']*1000:.1f}ms"
        )
