"""
Comprehensive Exception Handling and Logging for Subscription System
Provides robust error handling, logging, and monitoring for the usage-based subscription system.
"""

import traceback
import json
from datetime import datetime
from typing import Dict, Any, Optional, Union, List
from enum import Enum
from loguru import logger
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError

from models.subscription_models import APIProvider, UsageAlert

class SubscriptionErrorType(Enum):
    USAGE_LIMIT_EXCEEDED = "usage_limit_exceeded"
    PRICING_ERROR = "pricing_error"
    TRACKING_ERROR = "tracking_error"
    DATABASE_ERROR = "database_error"
    API_PROVIDER_ERROR = "api_provider_error"
    AUTHENTICATION_ERROR = "authentication_error"
    BILLING_ERROR = "billing_error"
    CONFIGURATION_ERROR = "configuration_error"

class SubscriptionErrorSeverity(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"

class SubscriptionException(Exception):
    """Base exception for subscription system errors."""
    
    def __init__(
        self,
        message: str,
        error_type: SubscriptionErrorType,
        severity: SubscriptionErrorSeverity = SubscriptionErrorSeverity.MEDIUM,
        user_id: str = None,
        provider: APIProvider = None,
        context: Dict[str, Any] = None,
        original_error: Exception = None
    ):
        self.message = message
        self.error_type = error_type
        self.severity = severity
        self.user_id = user_id
        self.provider = provider
        self.context = context or {}
        self.original_error = original_error
        self.timestamp = datetime.utcnow()
        
        super().__init__(message)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert exception to dictionary for logging/storage."""
        return {
            "message": self.message,
            "error_type": self.error_type.value,
            "severity": self.severity.value,
            "user_id": self.user_id,
            "provider": self.provider.value if self.provider else None,
            "context": self.context,
            "timestamp": self.timestamp.isoformat(),
            "original_error": str(self.original_error) if self.original_error else None,
            "traceback": traceback.format_exc() if self.original_error else None
        }

class UsageLimitExceededException(SubscriptionException):
    """Exception raised when usage limits are exceeded."""
    
    def __init__(
        self,
        message: str,
        user_id: str,
        provider: APIProvider,
        limit_type: str,
        current_usage: Union[int, float],
        limit_value: Union[int, float],
        context: Dict[str, Any] = None
    ):
        context = context or {}
        context.update({
            "limit_type": limit_type,
            "current_usage": current_usage,
            "limit_value": limit_value,
            "usage_percentage": (current_usage / max(limit_value, 1)) * 100
        })
        
        super().__init__(
            message=message,
            error_type=SubscriptionErrorType.USAGE_LIMIT_EXCEEDED,
            severity=SubscriptionErrorSeverity.HIGH,
            user_id=user_id,
            provider=provider,
            context=context
        )

class PricingException(SubscriptionException):
    """Exception raised for pricing calculation errors."""
    
    def __init__(
        self,
        message: str,
        provider: APIProvider = None,
        model_name: str = None,
        context: Dict[str, Any] = None,
        original_error: Exception = None
    ):
        context = context or {}
        if model_name:
            context["model_name"] = model_name
        
        super().__init__(
            message=message,
            error_type=SubscriptionErrorType.PRICING_ERROR,
            severity=SubscriptionErrorSeverity.MEDIUM,
            provider=provider,
            context=context,
            original_error=original_error
        )

class TrackingException(SubscriptionException):
    """Exception raised for usage tracking errors."""
    
    def __init__(
        self,
        message: str,
        user_id: str = None,
        provider: APIProvider = None,
        context: Dict[str, Any] = None,
        original_error: Exception = None
    ):
        super().__init__(
            message=message,
            error_type=SubscriptionErrorType.TRACKING_ERROR,
            severity=SubscriptionErrorSeverity.MEDIUM,
            user_id=user_id,
            provider=provider,
            context=context,
            original_error=original_error
        )

class SubscriptionExceptionHandler:
    """Comprehensive exception handler for the subscription system."""
    
    def __init__(self, db: Session = None):
        self.db = db
        self._setup_logging()
    
    def _setup_logging(self):
        """Setup structured logging for subscription errors."""
        from utils.logger_utils import get_service_logger
        return get_service_logger("subscription_exception_handler")
    
    def handle_exception(
        self,
        error: Union[Exception, SubscriptionException],
        context: Dict[str, Any] = None,
        log_level: str = "error"
    ) -> Dict[str, Any]:
        """Handle and log subscription system exceptions."""
        
        context = context or {}
        
        # Convert regular exceptions to SubscriptionException
        if not isinstance(error, SubscriptionException):
            error = SubscriptionException(
                message=str(error),
                error_type=self._classify_error(error),
                severity=self._determine_severity(error),
                context=context,
                original_error=error
            )
        
        # Log the error
        error_data = error.to_dict()
        error_data.update(context)
        
        log_message = f"Subscription Error: {error.message}"
        
        if log_level == "critical":
            logger.critical(log_message, extra={"error_data": error_data})
        elif log_level == "error":
            logger.error(log_message, extra={"error_data": error_data})
        elif log_level == "warning":
            logger.warning(log_message, extra={"error_data": error_data})
        else:
            logger.info(log_message, extra={"error_data": error_data})
        
        # Store critical errors in database for alerting
        if error.severity in [SubscriptionErrorSeverity.HIGH, SubscriptionErrorSeverity.CRITICAL]:
            self._store_error_alert(error)
        
        # Return formatted error response
        return self._format_error_response(error)
    
    def _classify_error(self, error: Exception) -> SubscriptionErrorType:
        """Classify an exception into a subscription error type."""
        
        error_str = str(error).lower()
        error_type_name = type(error).__name__.lower()
        
        if "limit" in error_str or "exceeded" in error_str:
            return SubscriptionErrorType.USAGE_LIMIT_EXCEEDED
        elif "pricing" in error_str or "cost" in error_str:
            return SubscriptionErrorType.PRICING_ERROR
        elif "tracking" in error_str or "usage" in error_str:
            return SubscriptionErrorType.TRACKING_ERROR
        elif "database" in error_str or "sql" in error_type_name:
            return SubscriptionErrorType.DATABASE_ERROR
        elif "api" in error_str or "provider" in error_str:
            return SubscriptionErrorType.API_PROVIDER_ERROR
        elif "auth" in error_str or "permission" in error_str:
            return SubscriptionErrorType.AUTHENTICATION_ERROR
        elif "billing" in error_str or "payment" in error_str:
            return SubscriptionErrorType.BILLING_ERROR
        else:
            return SubscriptionErrorType.CONFIGURATION_ERROR
    
    def _determine_severity(self, error: Exception) -> SubscriptionErrorSeverity:
        """Determine the severity of an error."""
        
        error_str = str(error).lower()
        error_type = type(error)
        
        # Critical errors
        if isinstance(error, (SQLAlchemyError, ConnectionError)):
            return SubscriptionErrorSeverity.CRITICAL
        
        # High severity errors
        if "limit exceeded" in error_str or "unauthorized" in error_str:
            return SubscriptionErrorSeverity.HIGH
        
        # Medium severity errors
        if "pricing" in error_str or "tracking" in error_str:
            return SubscriptionErrorSeverity.MEDIUM
        
        # Default to low
        return SubscriptionErrorSeverity.LOW
    
    def _store_error_alert(self, error: SubscriptionException):
        """Store critical errors as alerts in the database."""
        
        if not self.db or not error.user_id:
            return
        
        try:
            alert = UsageAlert(
                user_id=error.user_id,
                alert_type="system_error",
                threshold_percentage=0,
                provider=error.provider,
                title=f"System Error: {error.error_type.value}",
                message=error.message,
                severity=error.severity.value,
                billing_period=datetime.now().strftime("%Y-%m")
            )
            
            self.db.add(alert)
            self.db.commit()
            
        except Exception as e:
            logger.error(f"Failed to store error alert: {e}")
    
    def _format_error_response(self, error: SubscriptionException) -> Dict[str, Any]:
        """Format error for API response."""
        
        response = {
            "success": False,
            "error": {
                "type": error.error_type.value,
                "message": error.message,
                "severity": error.severity.value,
                "timestamp": error.timestamp.isoformat()
            }
        }
        
        # Add context for debugging (non-sensitive info only)
        if error.context:
            safe_context = {
                k: v for k, v in error.context.items()
                if k not in ["password", "token", "key", "secret"]
            }
            response["error"]["context"] = safe_context
        
        # Add user-friendly message based on error type
        user_messages = {
            SubscriptionErrorType.USAGE_LIMIT_EXCEEDED: 
                "You have reached your usage limit. Please upgrade your plan or wait for the next billing cycle.",
            SubscriptionErrorType.PRICING_ERROR:
                "There was an issue calculating the cost for this request. Please try again.",
            SubscriptionErrorType.TRACKING_ERROR:
                "Unable to track usage for this request. Please contact support if this persists.",
            SubscriptionErrorType.DATABASE_ERROR:
                "A database error occurred. Please try again later.",
            SubscriptionErrorType.API_PROVIDER_ERROR:
                "There was an issue with the API provider. Please try again.",
            SubscriptionErrorType.AUTHENTICATION_ERROR:
                "Authentication failed. Please check your credentials.",
            SubscriptionErrorType.BILLING_ERROR:
                "There was a billing-related error. Please contact support.",
            SubscriptionErrorType.CONFIGURATION_ERROR:
                "System configuration error. Please contact support."
        }
        
        response["error"]["user_message"] = user_messages.get(
            error.error_type, 
            "An unexpected error occurred. Please try again or contact support."
        )
        
        return response

# Utility functions for common error scenarios
def handle_usage_limit_error(
    user_id: str,
    provider: APIProvider,
    limit_type: str,
    current_usage: Union[int, float],
    limit_value: Union[int, float],
    db: Session = None
) -> Dict[str, Any]:
    """Handle usage limit exceeded errors."""
    
    handler = SubscriptionExceptionHandler(db)
    error = UsageLimitExceededException(
        message=f"Usage limit exceeded for {limit_type}",
        user_id=user_id,
        provider=provider,
        limit_type=limit_type,
        current_usage=current_usage,
        limit_value=limit_value
    )
    
    return handler.handle_exception(error, log_level="warning")

def handle_pricing_error(
    message: str,
    provider: APIProvider = None,
    model_name: str = None,
    original_error: Exception = None,
    db: Session = None
) -> Dict[str, Any]:
    """Handle pricing calculation errors."""
    
    handler = SubscriptionExceptionHandler(db)
    error = PricingException(
        message=message,
        provider=provider,
        model_name=model_name,
        original_error=original_error
    )
    
    return handler.handle_exception(error)

def handle_tracking_error(
    message: str,
    user_id: str = None,
    provider: APIProvider = None,
    original_error: Exception = None,
    db: Session = None
) -> Dict[str, Any]:
    """Handle usage tracking errors."""
    
    handler = SubscriptionExceptionHandler(db)
    error = TrackingException(
        message=message,
        user_id=user_id,
        provider=provider,
        original_error=original_error
    )
    
    return handler.handle_exception(error)

def log_usage_event(
    user_id: str,
    provider: APIProvider,
    action: str,
    details: Dict[str, Any] = None
):
    """Log usage events for monitoring and debugging."""
    
    details = details or {}
    log_data = {
        "user_id": user_id,
        "provider": provider.value,
        "action": action,
        "timestamp": datetime.utcnow().isoformat(),
        **details
    }
    
    logger.info(f"Usage Tracking: {action}", extra={"usage_data": log_data})

# Decorator for automatic exception handling
def handle_subscription_errors(db: Session = None):
    """Decorator to automatically handle subscription-related exceptions."""
    
    def decorator(func):
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except SubscriptionException as e:
                handler = SubscriptionExceptionHandler(db)
                return handler.handle_exception(e)
            except Exception as e:
                handler = SubscriptionExceptionHandler(db)
                return handler.handle_exception(e)
        
        return wrapper
    return decorator
