"""OAuth client error classes with rich context.

These error classes provide detailed, actionable error messages that help
developers quickly diagnose and fix OAuth integration issues.

Usage:
    try:
        token = await oauth.get_token()
    except OAuthTokenError as e:
        print(f"Error: {e.error_code}")
        print(f"Troubleshooting tips: {e.troubleshooting}")
"""

from typing import Optional, List
from ..exceptions import EmpowerNowError


class OAuthError(EmpowerNowError):
    """Base OAuth error with rich context for debugging.
    
    All OAuth errors include:
    - Human-readable message
    - Machine-readable error code
    - Troubleshooting tips
    - Request context (URLs, auth methods, etc.)
    """
    
    def __init__(
        self,
        message: str,
        *,
        error_code: Optional[str] = None,
        error_description: Optional[str] = None,
        status_code: Optional[int] = None,
        url: Optional[str] = None,
        auth_method: Optional[str] = None,
        client_id: Optional[str] = None,
        troubleshooting: Optional[List[str]] = None,
    ):
        """Initialize OAuth error with rich context.
        
        Args:
            message: Human-readable error message
            error_code: OAuth error code (e.g., 'invalid_client')
            error_description: OAuth error description from server
            status_code: HTTP status code if applicable
            url: URL that was being accessed
            auth_method: Authentication method being used
            client_id: OAuth client ID
            troubleshooting: List of troubleshooting tips
        """
        self.message = message
        self.error_code = error_code
        self.error_description = error_description
        self.status_code = status_code
        self.url = url
        self.auth_method = auth_method
        self.client_id = client_id
        self.troubleshooting = troubleshooting or []
        super().__init__(str(self))
    
    def __str__(self) -> str:
        """Format error with all available context."""
        parts = [self.message]
        
        if self.status_code:
            parts.append(f"  Status: {self.status_code}")
        if self.error_code:
            parts.append(f"  Error: {self.error_code}")
        if self.error_description:
            parts.append(f"  Description: {self.error_description}")
        if self.url:
            parts.append(f"  URL: {self.url}")
        if self.auth_method:
            parts.append(f"  Auth Method: {self.auth_method}")
        if self.client_id:
            parts.append(f"  Client ID: {self.client_id}")
        
        if self.troubleshooting:
            parts.append("")
            parts.append("  Troubleshooting:")
            for tip in self.troubleshooting:
                parts.append(f"    - {tip}")
        
        return "\n".join(parts)
    
    def to_dict(self) -> dict:
        """Convert error to dictionary for logging/serialization."""
        return {
            "message": self.message,
            "error_code": self.error_code,
            "error_description": self.error_description,
            "status_code": self.status_code,
            "url": self.url,
            "auth_method": self.auth_method,
            "client_id": self.client_id,
            "troubleshooting": self.troubleshooting,
        }


class OAuthTokenError(OAuthError):
    """Token acquisition failed.
    
    Raised when the OAuth token endpoint returns an error or is unreachable.
    """
    
    @classmethod
    def from_response(
        cls,
        status_code: int,
        response_body: Optional[dict],
        token_url: str,
        auth_method: str,
        client_id: str,
    ) -> "OAuthTokenError":
        """Create error from HTTP response with helpful context.
        
        Automatically adds troubleshooting tips based on the error type.
        """
        error_code = None
        error_description = None
        troubleshooting = []
        
        if response_body:
            error_code = response_body.get("error")
            error_description = response_body.get("error_description")
        
        # Add troubleshooting tips based on status code and error
        if status_code == 401:
            troubleshooting = [
                "Verify client_id matches exactly (case-sensitive)",
                "Check client_secret is correct and not URL-encoded",
                f"Ensure IdP expects '{auth_method}' authentication method",
            ]
            if auth_method == "private_key_jwt":
                troubleshooting.extend([
                    "Verify private key matches public key registered with IdP",
                    "Check that the key ID (kid) is correct",
                    "Ensure the assertion hasn't expired",
                ])
            if error_code == "invalid_client":
                troubleshooting.insert(0, "Client credentials are incorrect or client doesn't exist")
        
        elif status_code == 400:
            troubleshooting = [
                "Check grant_type is supported by the IdP",
                "Verify scope values are valid and allowed for this client",
                "Ensure all required parameters are provided",
            ]
            if error_code == "invalid_grant":
                troubleshooting.insert(0, "The authorization grant is invalid or expired")
            elif error_code == "invalid_scope":
                troubleshooting.insert(0, "One or more requested scopes are invalid")
        
        elif status_code == 403:
            troubleshooting = [
                "Client may not be authorized for the requested scope",
                "Check if the client is allowed to use client_credentials grant",
            ]
        
        elif status_code == 429:
            troubleshooting = [
                "Rate limit exceeded - implement exponential backoff",
                "Check Retry-After header for wait time",
            ]
        
        elif status_code >= 500:
            troubleshooting = [
                "IdP may be experiencing issues - check status page",
                "Retry the request with exponential backoff",
                "If persistent, contact IdP support",
            ]
        
        message = f"Token request failed: HTTP {status_code}"
        if error_code:
            message = f"Token request failed: {error_code}"
        
        return cls(
            message,
            error_code=error_code,
            error_description=error_description,
            status_code=status_code,
            url=token_url,
            auth_method=auth_method,
            client_id=client_id,
            troubleshooting=troubleshooting,
        )
    
    @classmethod
    def connection_error(
        cls,
        exception: Exception,
        token_url: str,
        client_id: str,
    ) -> "OAuthTokenError":
        """Create error for connection failures."""
        return cls(
            f"Failed to connect to token endpoint: {exception}",
            url=token_url,
            client_id=client_id,
            troubleshooting=[
                "Verify the token URL is correct and reachable",
                "Check network connectivity and firewall rules",
                "Ensure DNS is resolving correctly",
                "If using HTTPS, verify TLS certificates are valid",
            ],
        )
    
    @classmethod
    def timeout_error(
        cls,
        token_url: str,
        timeout_seconds: float,
        client_id: str,
    ) -> "OAuthTokenError":
        """Create error for timeout failures."""
        return cls(
            f"Token request timed out after {timeout_seconds}s",
            url=token_url,
            client_id=client_id,
            troubleshooting=[
                "IdP may be overloaded or experiencing issues",
                "Consider increasing timeout if IdP is known to be slow",
                "Check if there are network latency issues",
            ],
        )


class OAuthCircuitBreakerError(OAuthError):
    """Circuit breaker is open - requests are being rejected.
    
    The circuit breaker opens after too many consecutive failures to protect
    the system from cascading failures.
    """
    
    def __init__(
        self,
        message: str,
        *,
        time_until_reset: Optional[float] = None,
        failure_count: Optional[int] = None,
        **kwargs,
    ):
        self.time_until_reset = time_until_reset
        self.failure_count = failure_count
        
        troubleshooting = [
            "The OAuth endpoint has had multiple consecutive failures",
            "Requests are temporarily blocked to prevent cascading failures",
        ]
        if time_until_reset:
            troubleshooting.append(f"Circuit will reset in {time_until_reset:.1f} seconds")
        troubleshooting.append("Check IdP status and logs for the underlying issue")
        
        super().__init__(message, troubleshooting=troubleshooting, **kwargs)


class OAuthDiscoveryError(OAuthError):
    """OIDC discovery failed.
    
    Raised when the .well-known/openid-configuration endpoint is unreachable
    or returns invalid data.
    """
    
    def __init__(self, message: str, *, discovery_url: Optional[str] = None, **kwargs):
        troubleshooting = [
            "Verify the issuer URL is correct",
            "Check that the IdP supports OIDC discovery",
            f"Try accessing {discovery_url} in a browser" if discovery_url else "Check the discovery URL",
            "Ensure the IdP is running and accessible",
        ]
        super().__init__(message, url=discovery_url, troubleshooting=troubleshooting, **kwargs)


class OAuthConfigurationError(OAuthError):
    """OAuth client is misconfigured.
    
    Raised when the client configuration is invalid or incomplete.
    """
    pass


class OAuthIntrospectionError(OAuthError):
    """Token introspection failed."""
    pass


class OAuthRevocationError(OAuthError):
    """Token revocation failed."""
    pass
