#!/usr/bin/env python3
"""
OAuth Metrics Monitoring Module

Tracks and monitors OAuth connection metrics including:
- Wait-until performance
- Connection success/failure rates
- Retry patterns
- Circuit breaker events
"""

from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from collections import deque, defaultdict
import logging

log = logging.getLogger(__name__)


@dataclass
class WaitUntilMetric:
    """Metrics for a single wait-until operation"""

    account_id: str
    wait_until: str  # 'connected' or 'disconnected'
    timeout_ms: int
    elapsed_ms: int
    poll_count: int
    success: bool
    timestamp: datetime = field(default_factory=datetime.utcnow)
    backoff_pattern: List[int] = field(default_factory=list)


@dataclass
class ConnectionMetric:
    """Metrics for OAuth connection attempts"""

    account_id: str
    success: bool
    error_type: Optional[str] = None
    error_message: Optional[str] = None
    duration_ms: int = 0
    timestamp: datetime = field(default_factory=datetime.utcnow)


@dataclass
class CircuitBreakerMetric:
    """Metrics for circuit breaker events"""

    account_id: str
    event: str  # 'open', 'close', 'half_open'
    failure_count: int
    timestamp: datetime = field(default_factory=datetime.utcnow)


class OAuthMetricsCollector:
    """Collects and aggregates OAuth performance metrics"""

    def __init__(self, window_size_minutes: int = 60):
        """Initialize metrics collector

        Args:
            window_size_minutes: Rolling window size for metrics aggregation
        """
        self.window_size = timedelta(minutes=window_size_minutes)

        # Use deques for efficient rolling window operations
        self.wait_until_metrics: deque = deque(maxlen=10000)
        self.connection_metrics: deque = deque(maxlen=10000)
        self.circuit_breaker_metrics: deque = deque(maxlen=1000)

        # Aggregate stats
        self.stats_cache: Dict[str, Any] = {}
        self.stats_cache_timestamp: Optional[datetime] = None
        self.cache_ttl = timedelta(seconds=10)

    def record_wait_until(self, metric: WaitUntilMetric):
        """Record a wait-until operation metric"""
        self.wait_until_metrics.append(metric)
        self._invalidate_cache()

        # Log interesting patterns
        if not metric.success:
            log.warning(
                f"Wait-until timeout for {metric.account_id}: "
                f"waited {metric.elapsed_ms}ms, {metric.poll_count} polls"
            )
        elif metric.elapsed_ms > 1000:
            log.info(
                f"Slow wait-until for {metric.account_id}: "
                f"{metric.elapsed_ms}ms, {metric.poll_count} polls"
            )

    def record_connection(self, metric: ConnectionMetric):
        """Record an OAuth connection attempt"""
        self.connection_metrics.append(metric)
        self._invalidate_cache()

        if not metric.success:
            log.error(
                f"OAuth connection failed for {metric.account_id}: "
                f"{metric.error_type} - {metric.error_message}"
            )

    def record_circuit_breaker(self, metric: CircuitBreakerMetric):
        """Record a circuit breaker event"""
        self.circuit_breaker_metrics.append(metric)
        self._invalidate_cache()

        log.warning(
            f"Circuit breaker {metric.event} for {metric.account_id}, "
            f"failure count: {metric.failure_count}"
        )

    def _invalidate_cache(self):
        """Invalidate the stats cache"""
        self.stats_cache_timestamp = None

    def _clean_old_metrics(self):
        """Remove metrics outside the rolling window"""
        cutoff = datetime.utcnow() - self.window_size

        # Clean wait-until metrics
        while self.wait_until_metrics and self.wait_until_metrics[0].timestamp < cutoff:
            self.wait_until_metrics.popleft()

        # Clean connection metrics
        while self.connection_metrics and self.connection_metrics[0].timestamp < cutoff:
            self.connection_metrics.popleft()

        # Clean circuit breaker metrics
        while (
            self.circuit_breaker_metrics
            and self.circuit_breaker_metrics[0].timestamp < cutoff
        ):
            self.circuit_breaker_metrics.popleft()

    def get_stats(self) -> Dict[str, Any]:
        """Get aggregated statistics for the rolling window"""

        # Check cache
        if (
            self.stats_cache_timestamp
            and datetime.utcnow() - self.stats_cache_timestamp < self.cache_ttl
        ):
            return self.stats_cache

        # Clean old metrics
        self._clean_old_metrics()

        # Calculate wait-until stats
        wait_until_stats = self._calculate_wait_until_stats()

        # Calculate connection stats
        connection_stats = self._calculate_connection_stats()

        # Calculate circuit breaker stats
        circuit_breaker_stats = self._calculate_circuit_breaker_stats()

        # Build response
        stats = {
            "window_minutes": self.window_size.total_seconds() / 60,
            "timestamp": datetime.utcnow().isoformat(),
            "wait_until": wait_until_stats,
            "connections": connection_stats,
            "circuit_breaker": circuit_breaker_stats,
            "health_score": self._calculate_health_score(
                wait_until_stats, connection_stats, circuit_breaker_stats
            ),
        }

        # Update cache
        self.stats_cache = stats
        self.stats_cache_timestamp = datetime.utcnow()

        return stats

    def _calculate_wait_until_stats(self) -> Dict[str, Any]:
        """Calculate wait-until performance statistics"""
        if not self.wait_until_metrics:
            return {
                "total": 0,
                "success_rate": 1.0,
                "avg_elapsed_ms": 0,
                "p50_elapsed_ms": 0,
                "p95_elapsed_ms": 0,
                "p99_elapsed_ms": 0,
                "avg_poll_count": 0,
            }

        total = len(self.wait_until_metrics)
        successes = sum(1 for m in self.wait_until_metrics if m.success)
        elapsed_times = sorted([m.elapsed_ms for m in self.wait_until_metrics])
        poll_counts = [m.poll_count for m in self.wait_until_metrics]

        def percentile(data, p):
            if not data:
                return 0
            k = (len(data) - 1) * p / 100
            f = int(k)
            c = f + 1 if f < len(data) - 1 else f
            return data[f] + (data[c] - data[f]) * (k - f)

        return {
            "total": total,
            "success_rate": successes / total if total > 0 else 1.0,
            "avg_elapsed_ms": sum(elapsed_times) / len(elapsed_times)
            if elapsed_times
            else 0,
            "p50_elapsed_ms": percentile(elapsed_times, 50),
            "p95_elapsed_ms": percentile(elapsed_times, 95),
            "p99_elapsed_ms": percentile(elapsed_times, 99),
            "avg_poll_count": sum(poll_counts) / len(poll_counts) if poll_counts else 0,
            "timeout_count": sum(1 for m in self.wait_until_metrics if not m.success),
        }

    def _calculate_connection_stats(self) -> Dict[str, Any]:
        """Calculate OAuth connection statistics"""
        if not self.connection_metrics:
            return {
                "total": 0,
                "success_rate": 1.0,
                "avg_duration_ms": 0,
                "error_breakdown": {},
            }

        total = len(self.connection_metrics)
        successes = sum(1 for m in self.connection_metrics if m.success)
        durations = [
            m.duration_ms for m in self.connection_metrics if m.duration_ms > 0
        ]

        # Error breakdown
        error_counts = defaultdict(int)
        for m in self.connection_metrics:
            if not m.success and m.error_type:
                error_counts[m.error_type] += 1

        return {
            "total": total,
            "success_rate": successes / total if total > 0 else 1.0,
            "avg_duration_ms": sum(durations) / len(durations) if durations else 0,
            "error_breakdown": dict(error_counts),
            "failure_count": total - successes,
        }

    def _calculate_circuit_breaker_stats(self) -> Dict[str, Any]:
        """Calculate circuit breaker statistics"""
        if not self.circuit_breaker_metrics:
            return {"total_events": 0, "open_count": 0, "current_state": "closed"}

        open_count = sum(1 for m in self.circuit_breaker_metrics if m.event == "open")

        # Get current state from most recent event
        latest = max(self.circuit_breaker_metrics, key=lambda m: m.timestamp)
        current_state = latest.event if latest else "closed"

        return {
            "total_events": len(self.circuit_breaker_metrics),
            "open_count": open_count,
            "current_state": current_state,
            "last_event_time": latest.timestamp.isoformat() if latest else None,
        }

    def _calculate_health_score(
        self, wait_stats: Dict, conn_stats: Dict, circuit_stats: Dict
    ) -> float:
        """Calculate overall health score (0-100)"""

        # Weight factors
        wait_weight = 0.3
        conn_weight = 0.5
        circuit_weight = 0.2

        # Wait-until score (based on success rate and performance)
        wait_score = wait_stats["success_rate"] * 100
        if wait_stats["avg_elapsed_ms"] > 1000:
            wait_score *= 0.8  # Penalty for slow wait times

        # Connection score
        conn_score = conn_stats["success_rate"] * 100

        # Circuit breaker score
        if circuit_stats["current_state"] == "open":
            circuit_score = 0
        elif circuit_stats["open_count"] > 0:
            circuit_score = max(0, 100 - (circuit_stats["open_count"] * 20))
        else:
            circuit_score = 100

        # Calculate weighted score
        health_score = (
            wait_score * wait_weight
            + conn_score * conn_weight
            + circuit_score * circuit_weight
        )

        return round(health_score, 2)

    def get_account_stats(self, account_id: str) -> Dict[str, Any]:
        """Get statistics for a specific account"""

        # Clean old metrics
        self._clean_old_metrics()

        # Filter metrics for this account
        account_wait_metrics = [
            m for m in self.wait_until_metrics if m.account_id == account_id
        ]
        account_conn_metrics = [
            m for m in self.connection_metrics if m.account_id == account_id
        ]
        account_circuit_metrics = [
            m for m in self.circuit_breaker_metrics if m.account_id == account_id
        ]

        return {
            "account_id": account_id,
            "timestamp": datetime.utcnow().isoformat(),
            "wait_until_count": len(account_wait_metrics),
            "connection_count": len(account_conn_metrics),
            "circuit_breaker_events": len(account_circuit_metrics),
            "recent_failures": [
                {
                    "type": "connection",
                    "error": m.error_type,
                    "message": m.error_message,
                    "timestamp": m.timestamp.isoformat(),
                }
                for m in account_conn_metrics[-5:]  # Last 5 failures
                if not m.success
            ],
        }


# Global metrics collector instance
oauth_metrics = OAuthMetricsCollector()


# Helper functions for easy metric recording
def track_wait_until(
    account_id: str,
    wait_until: str,
    timeout_ms: int,
    elapsed_ms: int,
    poll_count: int,
    success: bool,
    backoff_pattern: Optional[List[int]] = None,
):
    """Track a wait-until operation"""
    metric = WaitUntilMetric(
        account_id=account_id,
        wait_until=wait_until,
        timeout_ms=timeout_ms,
        elapsed_ms=elapsed_ms,
        poll_count=poll_count,
        success=success,
        backoff_pattern=backoff_pattern or [],
    )
    oauth_metrics.record_wait_until(metric)


def track_connection(
    account_id: str,
    success: bool,
    duration_ms: int = 0,
    error_type: Optional[str] = None,
    error_message: Optional[str] = None,
):
    """Track an OAuth connection attempt"""
    metric = ConnectionMetric(
        account_id=account_id,
        success=success,
        duration_ms=duration_ms,
        error_type=error_type,
        error_message=error_message,
    )
    oauth_metrics.record_connection(metric)


def track_circuit_breaker(account_id: str, event: str, failure_count: int):
    """Track a circuit breaker event"""
    metric = CircuitBreakerMetric(
        account_id=account_id, event=event, failure_count=failure_count
    )
    oauth_metrics.record_circuit_breaker(metric)


# Export main components
__all__ = [
    "oauth_metrics",
    "track_wait_until",
    "track_connection",
    "track_circuit_breaker",
    "OAuthMetricsCollector",
    "WaitUntilMetric",
    "ConnectionMetric",
    "CircuitBreakerMetric",
]
