"""
API Client for Public Web Frontend

Provides HTTP request wrapper for communicating with the API backend.
Handles session cookie forwarding, error handling, and response parsing.
"""

import requests
from flask import request as flask_request, session as flask_session
from typing import Optional, Any
from app.config import load_config
from .logging import get_logger


logger = get_logger(__name__)


class APIError(Exception):
    """Base exception for API errors."""

    def __init__(self, message: str, status_code: int = 500, details: Optional[dict] = None):
        self.message = message
        self.status_code = status_code
        self.details = details or {}
        super().__init__(self.message)


class APITimeoutError(APIError):
    """Raised when API request times out."""

    def __init__(self, message: str = "Request timed out"):
        super().__init__(message, status_code=504)


class APINotFoundError(APIError):
    """Raised when resource not found (404)."""

    def __init__(self, message: str = "Resource not found"):
        super().__init__(message, status_code=404)


class APIAuthenticationError(APIError):
    """Raised when authentication fails (401)."""

    def __init__(self, message: str = "Authentication required"):
        super().__init__(message, status_code=401)


class APIClient:
    """
    HTTP client for making requests to the API backend.

    Usage:
        client = APIClient()

        # GET request
        response = client.get("/api/v1/characters")
        characters = response.get("result", [])

        # POST request
        response = client.post("/api/v1/characters", data={"name": "Hero"})

        # DELETE request
        client.delete("/api/v1/characters/123")
    """

    def __init__(self):
        """Initialize API client with config."""
        self.config = load_config()
        self.base_url = self.config.api.base_url.rstrip('/')
        self.timeout = self.config.api.timeout
        self.verify_ssl = self.config.api.verify_ssl

    def _get_cookies(self) -> dict:
        """
        Get cookies to forward to API.

        Returns:
            Dictionary of cookies to forward (session cookie).
        """
        cookies = {}

        # Get session cookie from Flask session (stored after login)
        try:
            if 'api_session_cookie' in flask_session:
                cookies['coc_session'] = flask_session['api_session_cookie']
        except RuntimeError:
            # Outside of request context
            pass

        return cookies

    def _save_session_cookie(self, response: requests.Response) -> None:
        """
        Save session cookie from API response to Flask session.

        Args:
            response: Response from requests library.
        """
        try:
            session_cookie = response.cookies.get('coc_session')
            if session_cookie:
                flask_session['api_session_cookie'] = session_cookie
                logger.debug("Saved API session cookie to Flask session")
        except RuntimeError:
            # Outside of request context
            pass

    def _get_headers(self) -> dict:
        """
        Get default headers for API requests.

        Returns:
            Dictionary of headers.
        """
        return {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }

    def _handle_response(self, response: requests.Response) -> dict:
        """
        Handle API response and raise appropriate exceptions.

        Args:
            response: Response from requests library.

        Returns:
            Parsed JSON response.

        Raises:
            APIError: For various HTTP error codes.
        """
        try:
            data = response.json()
        except ValueError:
            data = {}

        # Check for errors
        if response.status_code == 401:
            error_msg = data.get('error', {}).get('message', 'Authentication required')
            raise APIAuthenticationError(error_msg)

        if response.status_code == 404:
            error_msg = data.get('error', {}).get('message', 'Resource not found')
            raise APINotFoundError(error_msg)

        if response.status_code >= 400:
            error_msg = data.get('error', {}).get('message', f'API error: {response.status_code}')
            error_details = data.get('error', {}).get('details', {})
            raise APIError(error_msg, response.status_code, error_details)

        return data

    def get(self, endpoint: str, params: Optional[dict] = None) -> dict:
        """
        Make GET request to API.

        Args:
            endpoint: API endpoint (e.g., "/api/v1/characters").
            params: Optional query parameters.

        Returns:
            Parsed JSON response.

        Raises:
            APIError: For various error conditions.
        """
        url = f"{self.base_url}{endpoint}"

        try:
            response = requests.get(
                url,
                params=params,
                headers=self._get_headers(),
                cookies=self._get_cookies(),
                timeout=self.timeout,
                verify=self.verify_ssl
            )

            logger.debug("API GET request", url=url, status=response.status_code)
            return self._handle_response(response)

        except requests.exceptions.Timeout:
            logger.error("API timeout", url=url)
            raise APITimeoutError()

        except requests.exceptions.ConnectionError as e:
            logger.error("API connection error", url=url, error=str(e))
            raise APIError(f"Could not connect to API: {str(e)}", status_code=503)

        except requests.exceptions.RequestException as e:
            logger.error("API request error", url=url, error=str(e))
            raise APIError(f"Request failed: {str(e)}")

    def post(self, endpoint: str, data: Optional[dict] = None) -> dict:
        """
        Make POST request to API.

        Args:
            endpoint: API endpoint (e.g., "/api/v1/characters").
            data: Request body data.

        Returns:
            Parsed JSON response.

        Raises:
            APIError: For various error conditions.
        """
        url = f"{self.base_url}{endpoint}"

        try:
            response = requests.post(
                url,
                json=data or {},
                headers=self._get_headers(),
                cookies=self._get_cookies(),
                timeout=self.timeout,
                verify=self.verify_ssl
            )

            logger.debug("API POST request", url=url, status=response.status_code)

            # Save session cookie if present (for login responses)
            self._save_session_cookie(response)

            return self._handle_response(response)

        except requests.exceptions.Timeout:
            logger.error("API timeout", url=url)
            raise APITimeoutError()

        except requests.exceptions.ConnectionError as e:
            logger.error("API connection error", url=url, error=str(e))
            raise APIError(f"Could not connect to API: {str(e)}", status_code=503)

        except requests.exceptions.RequestException as e:
            logger.error("API request error", url=url, error=str(e))
            raise APIError(f"Request failed: {str(e)}")

    def delete(self, endpoint: str) -> dict:
        """
        Make DELETE request to API.

        Args:
            endpoint: API endpoint (e.g., "/api/v1/characters/123").

        Returns:
            Parsed JSON response.

        Raises:
            APIError: For various error conditions.
        """
        url = f"{self.base_url}{endpoint}"

        try:
            response = requests.delete(
                url,
                headers=self._get_headers(),
                cookies=self._get_cookies(),
                timeout=self.timeout,
                verify=self.verify_ssl
            )

            logger.debug("API DELETE request", url=url, status=response.status_code)
            return self._handle_response(response)

        except requests.exceptions.Timeout:
            logger.error("API timeout", url=url)
            raise APITimeoutError()

        except requests.exceptions.ConnectionError as e:
            logger.error("API connection error", url=url, error=str(e))
            raise APIError(f"Could not connect to API: {str(e)}", status_code=503)

        except requests.exceptions.RequestException as e:
            logger.error("API request error", url=url, error=str(e))
            raise APIError(f"Request failed: {str(e)}")

    def put(self, endpoint: str, data: Optional[dict] = None) -> dict:
        """
        Make PUT request to API.

        Args:
            endpoint: API endpoint.
            data: Request body data.

        Returns:
            Parsed JSON response.

        Raises:
            APIError: For various error conditions.
        """
        url = f"{self.base_url}{endpoint}"

        try:
            response = requests.put(
                url,
                json=data or {},
                headers=self._get_headers(),
                cookies=self._get_cookies(),
                timeout=self.timeout,
                verify=self.verify_ssl
            )

            logger.debug("API PUT request", url=url, status=response.status_code)
            return self._handle_response(response)

        except requests.exceptions.Timeout:
            logger.error("API timeout", url=url)
            raise APITimeoutError()

        except requests.exceptions.ConnectionError as e:
            logger.error("API connection error", url=url, error=str(e))
            raise APIError(f"Could not connect to API: {str(e)}", status_code=503)

        except requests.exceptions.RequestException as e:
            logger.error("API request error", url=url, error=str(e))
            raise APIError(f"Request failed: {str(e)}")


# Singleton instance
_api_client: Optional[APIClient] = None


def get_api_client() -> APIClient:
    """
    Get singleton API client instance.

    Returns:
        APIClient instance.
    """
    global _api_client
    if _api_client is None:
        _api_client = APIClient()
    return _api_client
