"""
OAuth 2.0 callback server for browser-based authorization flows.

This module provides a local HTTP server for receiving OAuth callbacks
in browser-based authorization flows (e.g., PKCE). It handles the OAuth
redirect, extracts parameters, validates state, and returns the authorization
code to the calling application.
"""

import logging
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread, Event
from typing import Dict, Optional, Any
from urllib.parse import parse_qs, urlparse

from pygeai_gam.exceptions import GAMAuthenticationError, GAMAPIError

logger = logging.getLogger("pygeai_gam.oauth_callback")

DEFAULT_SUCCESS_HTML = """
<html>
    <head>
        <meta charset="UTF-8">
        <title>Authentication Successful</title>
    </head>
    <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
        <h1 style="color: #4CAF50;">✓ Authentication Successful!</h1>
        <p style="font-size: 18px;">You can close this window and return to your application.</p>
    </body>
</html>
"""

DEFAULT_ERROR_HTML = """
<html>
    <head>
        <meta charset="UTF-8">
        <title>Authentication Failed</title>
    </head>
    <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
        <h1 style="color: #f44336;">✗ Authentication Failed</h1>
        <p style="font-size: 18px;">Error: {error}</p>
        <p style="font-size: 14px; color: #666;">{error_description}</p>
        <p style="font-size: 16px;">You can close this window and return to your application.</p>
    </body>
</html>
"""


class OAuthCallbackHandler(BaseHTTPRequestHandler):
    """HTTP request handler for OAuth callbacks."""
    
    server_version = "pygeai-gam/1.0"
    
    def log_message(self, format, *args):
        """Suppress default HTTP server logging."""
        pass
    
    def do_GET(self):
        """Handle GET request from OAuth callback."""
        if '?' in self.path:
            parsed_url = urlparse(self.path)
            params = parse_qs(parsed_url.query)
            
            # Convert parse_qs result (lists) to single values
            callback_params = {k: v[0] if v else '' for k, v in params.items()}
            
            self.server.callback_params = callback_params
            self.server.callback_received.set()
            
            # Determine if this is an error response
            if 'error' in callback_params:
                error = callback_params.get('error', 'unknown_error')
                error_description = callback_params.get('error_description', 'No description provided')
                response_html = self.server.error_html.format(
                    error=error,
                    error_description=error_description
                )
            else:
                response_html = self.server.success_html
            
            self.send_response(200)
            self.send_header('Content-type', 'text/html; charset=utf-8')
            self.send_header('Content-Length', str(len(response_html.encode('utf-8'))))
            self.send_header('Connection', 'close')
            self.end_headers()
            self.wfile.write(response_html.encode('utf-8'))
            self.wfile.flush()


class OAuthCallbackServer:
    """
    Local HTTP server for receiving OAuth 2.0 authorization callbacks.
    
    This server is used in browser-based OAuth flows to receive the authorization
    code after user consent. It starts a local HTTP server, waits for the OAuth
    provider to redirect the browser back with the authorization code, and then
    returns the code to the application.
    
    :param port: Port to bind the server to (default: 8080)
    :param timeout: Maximum time to wait for callback in seconds (default: 300)
    :param success_html: Custom HTML to display on successful authentication
    :param error_html: Custom HTML to display on authentication error (must support {error} and {error_description} placeholders)
    :param host: Host to bind server to (default: 'localhost' for security)
    
    Example:
        >>> server = OAuthCallbackServer(port=8080, timeout=300)
        >>> result = server.wait_for_callback(expected_state="random_state")
        >>> auth_code = result['code']
    """
    
    def __init__(
        self,
        port: int = 8080,
        timeout: int = 300,
        success_html: Optional[str] = None,
        error_html: Optional[str] = None,
        host: str = 'localhost'
    ):
        """
        Initialize the OAuth callback server.
        
        :param port: Port to bind the server to
        :param timeout: Maximum time to wait for callback in seconds
        :param success_html: Custom HTML for success page
        :param error_html: Custom HTML for error page
        :param host: Host to bind server to
        """
        self.port = port
        self.timeout = timeout
        self.success_html = success_html or DEFAULT_SUCCESS_HTML
        self.error_html = error_html or DEFAULT_ERROR_HTML
        self.host = host
        
        self._server: Optional[HTTPServer] = None
        self._server_thread: Optional[Thread] = None
        self._callback_received: Optional[Event] = None
        self._callback_params: Dict[str, str] = {}
    
    def start(self) -> None:
        """
        Start the HTTP server in a background thread.
        
        :raises GAMAPIError: If server fails to start
        """
        try:
            self._callback_received = Event()
            self._callback_params = {}
            
            self._server = HTTPServer((self.host, self.port), OAuthCallbackHandler)
            self._server.callback_received = self._callback_received
            self._server.callback_params = self._callback_params
            self._server.success_html = self.success_html
            self._server.error_html = self.error_html
            
            self._server_thread = Thread(target=self._server.serve_forever, daemon=True)
            self._server_thread.start()
            
            logger.debug(f"OAuth callback server started on {self.host}:{self.port}")
            
        except Exception as e:
            error_msg = f"Failed to start OAuth callback server: {str(e)}"
            logger.error(error_msg)
            raise GAMAPIError(error_msg) from e
    
    def stop(self) -> None:
        """Stop the HTTP server."""
        if self._server:
            self._server.shutdown()
            self._server = None
            logger.debug("OAuth callback server stopped")
    
    def wait_for_callback(self, expected_state: str) -> Dict[str, Any]:
        """
        Wait for OAuth callback and return the authorization code.
        
        This method blocks until either:
        1. A callback is received (returns authorization code)
        2. Timeout is reached (raises error)
        
        :param expected_state: Expected state parameter for CSRF protection
        :return: Dictionary with 'code' and 'state' keys
        :raises GAMAuthenticationError: If state mismatch, OAuth error, or missing code
        :raises GAMAPIError: If timeout occurs
        
        Example:
            >>> server = OAuthCallbackServer(port=8080)
            >>> server.start()
            >>> result = server.wait_for_callback(expected_state="xyz")
            >>> server.stop()
            >>> print(result['code'])
        """
        if not self._callback_received:
            raise GAMAPIError("Server not started. Call start() first.")
        
        logger.debug(f"Waiting for OAuth callback (timeout: {self.timeout}s)")
        
        if self._callback_received.wait(timeout=self.timeout):
            received_state = self._server.callback_params.get('state')
            
            # Validate state parameter (CSRF protection)
            if received_state != expected_state:
                error_msg = "State mismatch in OAuth callback - possible CSRF attack"
                logger.error(error_msg)
                raise GAMAuthenticationError(error_msg, status_code=400)
            
            # Check for OAuth error response
            if 'error' in self._server.callback_params:
                error = self._server.callback_params.get('error')
                error_description = self._server.callback_params.get('error_description', error)
                error_msg = f"OAuth authorization failed: {error_description}"
                logger.error(error_msg)
                raise GAMAuthenticationError(error_msg, status_code=400)
            
            # Extract authorization code
            auth_code = self._server.callback_params.get('code')
            if not auth_code:
                error_msg = "No authorization code received in OAuth callback"
                logger.error(error_msg)
                raise GAMAuthenticationError(error_msg, status_code=400)
            
            logger.debug("Authorization code received successfully")
            return {
                'code': auth_code,
                'state': received_state
            }
        else:
            error_msg = f"OAuth callback timeout after {self.timeout} seconds"
            logger.error(error_msg)
            raise GAMAPIError(error_msg)
    
    def __enter__(self):
        """Context manager entry - starts the server."""
        self.start()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - stops the server."""
        self.stop()
        return False
