from typing import Optional, Dict, Any
import json
import requests

from ws_package.log.writer import LogWriter
from ws_package.repositories.user_repo import user_manager
from ws_package.utils.reauth_notifier import get_reauth_notifier
from ws_package.utils.api_log_policy import api_name_from_url, log_api_response_summary, log_testing_request_headers
from ws_package.exceptions.custom_exceptions import (
    APIError, NetworkError, TimeoutError, ValidationError, 
    AuthenticationError, AuthorizationError, ResourceError
)

logger = LogWriter.file(__name__)


def create_robust_ssl_context():
    """
    Create a robust SSL context that handles missing certificate files gracefully.
    
    This function attempts to create a default SSL context first, and if that fails
    (e.g., due to missing certificate files in temp directory), it falls back to
    an unverified context.
    
    Returns:
        ssl.SSLContext: Configured SSL context for HTTP requests
    """
    import ssl
    
    try:
        # Try to create default SSL context first (most secure)
        ssl_context = ssl.create_default_context()
        logger.info("Created default SSL context successfully")
        return ssl_context
    except (FileNotFoundError, OSError) as e:
        logger.warning("Failed to create default SSL context: %s. Using fallback context.", e)
        # Fallback to unverified context if certificate files are missing
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE
        logger.warning("Using unverified SSL context as fallback")
        return ssl_context

class APIRequest:
    """
    Synchronous HTTP client for API requests with comprehensive error handling.

    This class provides a robust interface for making HTTP requests with:
    - Comprehensive error handling with specific exception types
    - Request/response timeout management
    - Instance-specific state management for thread safety

    Attributes:
        url (str): The target URL for API requests
        headers (Dict[str, str]): HTTP headers for requests
        timeout (float): Request timeout in seconds
        max_retries (int): Maximum number of retry attempts
        backoff_factor (float): Exponential backoff multiplier
        response (Optional[requests.Response]): Last response received

    Example:
        >>> api_request = APIRequest("https://api.example.com/data")
        >>> api_request.post({"key": "value"})
        >>> data = api_request.data()
    """
    # Class-level constants (these can remain as class variables since they're immutable)
    ERROR_CODE_MAPPINGS: Dict[int, str] = {
        400: "BAD_REQUEST",
        401: "UNAUTHORIZED", 
        403: "FORBIDDEN",
        404: "NOT_FOUND",
        429: "RATE_LIMITED",
        500: "INTERNAL_SERVER_ERROR",
        502: "BAD_GATEWAY",
        503: "SERVICE_UNAVAILABLE",
        504: "GATEWAY_TIMEOUT"
    }
    
    def __init__(
        self, 
        url: str, 
        token: Optional[str] = None, 
        headers: Optional[Dict[str, str]] = None, 
        content_type: Optional[str] = "application/json", 
        timeout: Optional[int] = None
    ) -> None:
       
        # Instance-specific state (converted from class-level)
        self.headers: Dict[str, str] = {}
        self.timeout: float = 10.0  # Default timeout in seconds
        self.max_retries: int = 3
        self.backoff_factor: float = 0.5
        if url is None or not url.strip():
            raise ValidationError("URL is required and cannot be empty", context={"url": url})
        
        self.url: str = url.strip()
        
        if content_type:
            self.headers['Content-Type'] = content_type
        if token is not None:
            self.headers['Authorization'] = f'Bearer {token}'
        if headers is not None:
            self.headers.update(headers)
        from ws_package.utils.desktop_key import apply_desktop_key
        self.headers = apply_desktop_key(self.headers)
        if timeout is not None:
            self.timeout = float(timeout)
        self.response: Optional[requests.Response] = None

    @property
    def api_name(self) -> str:
        return api_name_from_url(self.url)

    def payload(self, payload: Dict[str, Any]) -> str:
        """
        Convert payload dictionary to JSON string with validation and logging.
        
        Args:
            payload (Dict[str, Any]): The data to be serialized to JSON.
            
        Returns:
            str: JSON string representation of the payload.
            
        Raises:
            ValidationError: If payload cannot be serialized to JSON.
            
        Example:
            >>> api_request = APIRequest("https://api.example.com")
            >>> json_str = api_request.payload({"key": "value", "number": 123})
            >>> print(json_str)  # '{"key": "value", "number": 123}'
        """
        try:
            json_payload = json.dumps(payload)
            return json_payload
        except (TypeError, ValueError) as e:
            raise ValidationError(f"Invalid payload format: {e}", context={"payload": str(payload)[:100]})

    def _log_response(self, response: requests.Response) -> None:
        log_api_response_summary(
            logger,
            self.api_name,
            response.status_code,
            response.text,
            name=self.api_name,
        )

    def get(self, payload: dict):
        name = self.api_name
        try:
            log_testing_request_headers(logger, "GET", self.url, self.headers, payload)
            data = self.payload(payload)
            response = requests.get(self.url, headers=self.headers, data=data, timeout=10)
            self._log_response(response)

            if response.ok:
                self.response = response
                if 'Authorization' in self.headers:
                    get_reauth_notifier().on_auth_success()
            else:
                if response.status_code == 401 and 'Authorization' in self.headers:
                    get_reauth_notifier().on_401()
                self.response = None

        except requests.exceptions.ConnectionError:
            logger.error("[API] %s | connection error", name)
            self.response = None
        except requests.exceptions.Timeout:
            logger.error("[API] %s | timeout", name)
            self.response = None
        except requests.exceptions.RequestException as e:
            logger.error("[API] %s | request failed: %s", name, e)
            self.response = None
        except Exception as e:
            logger.exception("[API] %s | unexpected error: %s", name, e)
            self.response = None

    def post(self, payload: dict):
        name = self.api_name
        try:
            log_testing_request_headers(logger, "POST", self.url, self.headers, payload)
            data = self.payload(payload)
            response = requests.post(self.url, headers=self.headers, data=data, timeout=10)
            self._log_response(response)

            if response.ok:
                self.response = response
                if 'Authorization' in self.headers:
                    get_reauth_notifier().on_auth_success()
            else:
                if response.status_code == 401 and 'Authorization' in self.headers:
                    get_reauth_notifier().on_401()
                self.response = None

        except requests.exceptions.ConnectionError:
            logger.error("[API] %s | connection error", name)
            self.response = None
        except requests.exceptions.Timeout:
            logger.error("[API] %s | timeout", name)
            self.response = None
        except requests.exceptions.RequestException as e:
            logger.error("[API] %s | request failed: %s", name, e)
            self.response = None
        except Exception as e:
            logger.exception("[API] %s | unexpected error: %s", name, e)
            self.response = None
            
    def data(self) -> Optional[Dict[str, Any]]:
        """
        Parse and return the response data from the last request.
        
        This method handles various response formats and provides comprehensive
        error handling for empty responses and JSON parsing errors.
        
        Returns:
            Optional[Dict[str, Any]]: Parsed response data, or None if no response
            or empty response received.
            
        Raises:
            ValidationError: For invalid JSON responses or missing expected keys.
            
        Example:
            >>> api_request = APIRequest("https://api.example.com/data")
            >>> await api_request.post({"key": "value"})
            >>> response_data = api_request.data()
            >>> if response_data:
            ...     print(f"Received: {response_data}")
        """
        if self.response is None:
            logger.error("[API] %s | no response received", self.api_name)
            return None
            
        # requests responses use status_code for success checking
        if self.response.status_code == 200:
            try:
                response_text = self.response.text.strip()
                
                if not response_text:
                    logger.warning("[API] %s | empty response body", self.api_name)
                    return None
                    
                json_data = self.response.json()
                
                # Handle nested response structure
                if 'data' in json_data.get('response', {}):
                    return json_data['response']['data']
                elif 'response' in json_data:
                    return json_data['response']
                else:
                    return json_data
                    
            except json.JSONDecodeError as e:
                logger.error("[API] %s | invalid JSON: %s", self.api_name, e)
                raise ValidationError(
                    f"Invalid JSON response from server",
                    context={"api": self.api_name, "response_text": self.response.text[:200]}
                )
            except KeyError as e:
                logger.error("[API] %s | missing expected key: %s", self.api_name, e)
                raise ValidationError(
                    f"Missing expected key in response: {e}",
                    context={"api": self.api_name, "response_keys": list(json_data.keys()) if 'json_data' in locals() else []}
                )
            except Exception as e:
                logger.error("[API] %s | parse error: %s", self.api_name, e)
                raise APIError(
                    f"Unexpected error parsing response: {e}",
                    context={"api": self.api_name}
                )

        log_api_response_summary(
            logger,
            self.api_name,
            self.response.status_code,
            self.response.text,
            name=self.api_name,
        )
        return None

    def stealth_data(self):
        """Parse stealth response data with improved error handling."""
        if self.response is None:
            logger.error("[API] %s | no response received", self.api_name)
            return None
            
        if self.response.status_code == 200:
            try:
                json_data = self.response.json()
                
                # Handle nested response structure
                if 'data' in json_data.get('response', {}):
                    return json_data['response']['data']
                elif 'response' in json_data:
                    return json_data['response']
                else:
                    return json_data
                    
            except json.JSONDecodeError as e:
                logger.error("[API] %s | invalid JSON: %s", self.api_name, e)
                raise ValidationError(
                    f"Invalid JSON response from server",
                    context={"api": self.api_name, "response_text": self.response.text[:200]}
                )
            except KeyError as e:
                logger.error("[API] %s | missing expected key: %s", self.api_name, e)
                raise ValidationError(
                    f"Missing expected key in response: {e}",
                    context={"api": self.api_name, "response_keys": list(json_data.keys()) if 'json_data' in locals() else []}
                )
            except Exception as e:
                logger.error("[API] %s | parse error: %s", self.api_name, e)
                raise APIError(
                    f"Unexpected error parsing stealth response: {e}",
                    context={"api": self.api_name}
                )
                
        log_api_response_summary(
            logger,
            self.api_name,
            self.response.status_code,
            self.response.text,
            name=self.api_name,
        )
        return None

    @classmethod
    async def with_auth(cls, url: str, headers: Optional[Dict[str, str]] = None) -> 'APIRequest':
        """
        Create APIRequest instance with authentication token from user manager.
        
        This class method automatically retrieves the user's authentication token
        and creates an APIRequest instance with proper authentication headers.
        
        Args:
            url (str): The target URL for API requests.
            headers (Optional[Dict[str, str]]): Additional HTTP headers. Defaults to None.
            
        Returns:
            APIRequest: Configured instance with authentication token.
            
        Example:
            >>> api_request = await APIRequest.with_auth(
            ...     url="https://api.example.com/protected",
            ...     headers={"Custom-Header": "value"}
            ... )
            >>> await api_request.post({"data": "value"})
        """
        user_entity = await user_manager.get_user()
        token = user_entity.token if user_entity else None
        return cls(url=url, token=token, headers=headers)
