"""Performance metrics collection and tracking for the geo-targeting system.

This module provides utilities for collecting and tracking performance metrics
including geolocation lookup times, cache hit rates, and redirect decision times.
"""

import time
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from django.core.cache import cache
from dataclasses import dataclass, field

logger = logging.getLogger(__name__)


@dataclass
class MetricSnapshot:
    """A single metric measurement."""
    timestamp: datetime
    value: float
    label: str = ""
    
    def __repr__(self):
        return f"MetricSnapshot(timestamp={self.timestamp}, value={self.value}, label={self.label})"


@dataclass
class MetricStats:
    """Statistics for a metric over a time period."""
    metric_name: str
    count: int = 0
    total: float = 0.0
    min: float = float('inf')
    max: float = float('-inf')
    average: float = 0.0
    p50: float = 0.0  # Median
    p95: float = 0.0  # 95th percentile
    p99: float = 0.0  # 99th percentile
    
    def __post_init__(self):
        """Calculate average after initialization."""
        if self.count > 0:
            self.average = self.total / self.count
    
    def to_dict(self) -> Dict:
        """Convert to dictionary for serialization."""
        return {
            'metric_name': self.metric_name,
            'count': self.count,
            'total': self.total,
            'min': self.min if self.min != float('inf') else None,
            'max': self.max if self.max != float('-inf') else None,
            'average': self.average,
            'p50': self.p50,
            'p95': self.p95,
            'p99': self.p99,
        }


class PerformanceMetrics:
    """Service for collecting and tracking performance metrics.
    
    This service tracks:
    - Geolocation lookup time (milliseconds)
    - Cache hit rate (percentage)
    - Redirect decision time (milliseconds)
    
    Metrics are stored in cache with configurable retention periods.
    """
    
    # Cache key prefixes
    CACHE_KEY_PREFIX = 'geo_metrics:'
    GEOLOCATION_LOOKUP_TIME = 'geolocation_lookup_time'
    CACHE_HIT_RATE = 'cache_hit_rate'
    REDIRECT_DECISION_TIME = 'redirect_decision_time'
    
    # Default retention period (24 hours)
    DEFAULT_RETENTION_SECONDS = 86400
    
    # Metrics retention in cache (1 day)
    METRICS_CACHE_TIMEOUT = 86400
    
    def __init__(self, retention_seconds: int = DEFAULT_RETENTION_SECONDS):
        """Initialize the PerformanceMetrics service.
        
        Args:
            retention_seconds: How long to keep metrics in cache (default: 24 hours)
        """
        self.retention_seconds = retention_seconds
        self.retention_timedelta = timedelta(seconds=retention_seconds)
    
    def record_geolocation_lookup_time(self, duration_ms: float, is_cached: bool = False):
        """Record the time taken for a geolocation lookup.
        
        Args:
            duration_ms: Time taken in milliseconds
            is_cached: Whether the result came from cache
        """
        snapshot = MetricSnapshot(
            timestamp=datetime.now(),
            value=duration_ms,
            label='cached' if is_cached else 'provider'
        )
        
        self._append_metric(self.GEOLOCATION_LOOKUP_TIME, snapshot)
        
        logger.debug(
            f"Recorded geolocation lookup time: {duration_ms}ms "
            f"(source: {'cache' if is_cached else 'provider'})"
        )
    
    def record_cache_hit(self):
        """Record a cache hit event."""
        self._increment_counter('cache_hits')
    
    def record_cache_miss(self):
        """Record a cache miss event."""
        self._increment_counter('cache_misses')
    
    def record_redirect_decision_time(self, duration_ms: float):
        """Record the time taken to make a redirect decision.
        
        Args:
            duration_ms: Time taken in milliseconds
        """
        snapshot = MetricSnapshot(
            timestamp=datetime.now(),
            value=duration_ms,
            label='redirect_decision'
        )
        
        self._append_metric(self.REDIRECT_DECISION_TIME, snapshot)
        
        logger.debug(f"Recorded redirect decision time: {duration_ms}ms")
    
    def get_geolocation_stats(self, minutes: int = 60) -> MetricStats:
        """Get statistics for geolocation lookup times.
        
        Args:
            minutes: Number of minutes to look back (default: 60)
            
        Returns:
            MetricStats object with aggregated statistics
        """
        return self._calculate_stats(self.GEOLOCATION_LOOKUP_TIME, minutes)
    
    def get_cache_hit_rate(self, minutes: int = 60) -> float:
        """Get the cache hit rate as a percentage.
        
        Args:
            minutes: Number of minutes to look back (default: 60)
            
        Returns:
            Cache hit rate as a percentage (0-100)
        """
        hits = self._get_counter('cache_hits')
        misses = self._get_counter('cache_misses')
        
        total = hits + misses
        if total == 0:
            return 0.0
        
        return (hits / total) * 100
    
    def get_redirect_decision_stats(self, minutes: int = 60) -> MetricStats:
        """Get statistics for redirect decision times.
        
        Args:
            minutes: Number of minutes to look back (default: 60)
            
        Returns:
            MetricStats object with aggregated statistics
        """
        return self._calculate_stats(self.REDIRECT_DECISION_TIME, minutes)
    
    def get_all_metrics_summary(self, minutes: int = 60) -> Dict:
        """Get a summary of all metrics.
        
        Args:
            minutes: Number of minutes to look back (default: 60)
            
        Returns:
            Dictionary with all metrics summaries
        """
        return {
            'geolocation_lookup': self.get_geolocation_stats(minutes).to_dict(),
            'cache_hit_rate': self.get_cache_hit_rate(minutes),
            'redirect_decision': self.get_redirect_decision_stats(minutes).to_dict(),
            'timestamp': datetime.now().isoformat(),
        }
    
    def reset_metrics(self):
        """Reset all metrics (useful for testing)."""
        cache.delete(self._get_cache_key(self.GEOLOCATION_LOOKUP_TIME))
        cache.delete(self._get_cache_key(self.REDIRECT_DECISION_TIME))
        cache.delete(self._get_cache_key('cache_hits'))
        cache.delete(self._get_cache_key('cache_misses'))
        
        logger.info("All metrics have been reset")
    
    def _append_metric(self, metric_name: str, snapshot: MetricSnapshot):
        """Append a metric snapshot to the metrics list.
        
        Args:
            metric_name: Name of the metric
            snapshot: MetricSnapshot to append
        """
        cache_key = self._get_cache_key(metric_name)
        
        # Get existing metrics
        metrics = cache.get(cache_key, [])
        
        # Append new snapshot
        metrics.append(snapshot)
        
        # Clean up old metrics (older than retention period)
        cutoff_time = datetime.now() - self.retention_timedelta
        metrics = [m for m in metrics if m.timestamp > cutoff_time]
        
        # Store back in cache
        cache.set(cache_key, metrics, self.METRICS_CACHE_TIMEOUT)
    
    def _increment_counter(self, counter_name: str):
        """Increment a counter.
        
        Args:
            counter_name: Name of the counter
        """
        cache_key = self._get_cache_key(counter_name)
        current_value = cache.get(cache_key, 0)
        cache.set(cache_key, current_value + 1, self.METRICS_CACHE_TIMEOUT)
    
    def _get_counter(self, counter_name: str) -> int:
        """Get the current value of a counter.
        
        Args:
            counter_name: Name of the counter
            
        Returns:
            Current counter value
        """
        cache_key = self._get_cache_key(counter_name)
        return cache.get(cache_key, 0)
    
    def _calculate_stats(self, metric_name: str, minutes: int) -> MetricStats:
        """Calculate statistics for a metric.
        
        Args:
            metric_name: Name of the metric
            minutes: Number of minutes to look back
            
        Returns:
            MetricStats object with calculated statistics
        """
        cache_key = self._get_cache_key(metric_name)
        metrics = cache.get(cache_key, [])
        
        # Filter metrics by time window
        cutoff_time = datetime.now() - timedelta(minutes=minutes)
        recent_metrics = [m for m in metrics if m.timestamp > cutoff_time]
        
        # Calculate statistics
        stats = MetricStats(metric_name=metric_name)
        
        if not recent_metrics:
            return stats
        
        values = [m.value for m in recent_metrics]
        values.sort()
        
        stats.count = len(values)
        stats.total = sum(values)
        stats.min = min(values)
        stats.max = max(values)
        stats.average = stats.total / stats.count if stats.count > 0 else 0
        
        # Calculate percentiles
        if stats.count > 0:
            stats.p50 = self._percentile(values, 50)
            stats.p95 = self._percentile(values, 95)
            stats.p99 = self._percentile(values, 99)
        
        return stats
    
    def _percentile(self, values: List[float], percentile: int) -> float:
        """Calculate a percentile value.
        
        Args:
            values: Sorted list of values
            percentile: Percentile to calculate (0-100)
            
        Returns:
            The percentile value
        """
        if not values:
            return 0.0
        
        index = (percentile / 100.0) * (len(values) - 1)
        lower_index = int(index)
        upper_index = min(lower_index + 1, len(values) - 1)
        
        if lower_index == upper_index:
            return values[lower_index]
        
        # Linear interpolation
        lower_value = values[lower_index]
        upper_value = values[upper_index]
        fraction = index - lower_index
        
        return lower_value + (upper_value - lower_value) * fraction
    
    def _get_cache_key(self, metric_name: str) -> str:
        """Get the cache key for a metric.
        
        Args:
            metric_name: Name of the metric
            
        Returns:
            Cache key string
        """
        return f"{self.CACHE_KEY_PREFIX}{metric_name}"


# Global metrics instance
_metrics_instance: Optional[PerformanceMetrics] = None


def get_metrics() -> PerformanceMetrics:
    """Get the global PerformanceMetrics instance.
    
    Returns:
        PerformanceMetrics instance
    """
    global _metrics_instance
    if _metrics_instance is None:
        _metrics_instance = PerformanceMetrics()
    return _metrics_instance
