"""
OAuth utilities for pygeai-gam.

This module provides utility functions for OAuth 2.0 flows, including
redirect URI resolution with dynamic port assignment support.
"""

import socket
import logging
from typing import Tuple
from urllib.parse import urlparse

logger = logging.getLogger(__name__)

# GEAI convention: XXXX placeholder for dynamic port assignment
DYNAMIC_PORT_PLACEHOLDER = "XXXX"


def find_available_port() -> int:
    """
    Find an available ephemeral port on localhost.
    
    Uses socket binding to find a free port without race conditions.
    The OS assigns the port, which is released immediately after discovery.
    
    Returns:
        Available port number.
        
    Raises:
        OSError: If unable to find an available port.
    
    Example:
        >>> port = find_available_port()
        >>> print(f"Available port: {port}")
        Available port: 52431
        >>> isinstance(port, int) and port > 0
        True
    """
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.bind(("localhost", 0))
            _, port = sock.getsockname()
            logger.debug(f"Found available port: {port}")
            return port
    except OSError as e:
        logger.error(f"Failed to find available port: {e}")
        raise


def extract_port_from_uri(uri: str) -> int:
    """
    Extract port number from a URI.
    
    Args:
        uri: The URI to parse (e.g., "http://localhost:8080/callback").
        
    Returns:
        Port number.
        
    Raises:
        ValueError: If no port found in URI.
    
    Example:
        >>> port = extract_port_from_uri("http://localhost:8080/callback")
        >>> port
        8080
        >>> extract_port_from_uri("http://localhost/callback")
        Traceback (most recent call last):
        ...
        ValueError: No port found in URI: http://localhost/callback
    """
    parsed = urlparse(uri)
    if not parsed.port:
        raise ValueError(f"No port found in URI: {uri}")
    return parsed.port


def resolve_redirect_uri(redirect_uri: str) -> Tuple[str, int]:
    """
    Resolve a redirect URI, replacing XXXX placeholder with an available port.
    
    This function supports the GEAI convention of using 'XXXX' as a placeholder
    for dynamically-assigned ports. This allows multiple client instances to
    run simultaneously without port conflicts.
    
    Args:
        redirect_uri: The redirect URI, may contain 'XXXX' placeholder.
                     Example: "http://localhost:XXXX/callback"
    
    Returns:
        Tuple of (resolved_uri, port).
        - If XXXX present: replaces with available port
        - If no XXXX: extracts existing port from URI
        
    Raises:
        ValueError: If URI format is invalid or has no port.
        OSError: If unable to find available port (when using XXXX).
    
    Examples:
        With dynamic port placeholder:
        >>> uri, port = resolve_redirect_uri("http://localhost:XXXX/callback")
        >>> print(f"URI: {uri}, Port: {port}")
        URI: http://localhost:45123/callback, Port: 45123
        >>> isinstance(port, int) and port > 0
        True
        >>> "XXXX" not in uri
        True
        
        With fixed port:
        >>> uri, port = resolve_redirect_uri("http://localhost:8080/callback")
        >>> print(f"URI: {uri}, Port: {port}")
        URI: http://localhost:8080/callback, Port: 8080
        >>> (uri, port)
        ('http://localhost:8080/callback', 8080)
    """
    try:
        if DYNAMIC_PORT_PLACEHOLDER not in redirect_uri:
            # Fixed port - extract and return as-is
            port = extract_port_from_uri(redirect_uri)
            logger.debug(f"Using fixed port from redirect URI: {port}")
            return redirect_uri, port
        
        # Dynamic port - find available port and substitute
        available_port = find_available_port()
        resolved_uri = redirect_uri.replace(DYNAMIC_PORT_PLACEHOLDER, str(available_port))
        logger.info(f"Resolved dynamic redirect URI: {redirect_uri} -> {resolved_uri}")
        return resolved_uri, available_port
        
    except ValueError as e:
        logger.error(f"Invalid redirect URI: {redirect_uri}: {e}")
        raise
    except OSError as e:
        logger.error(f"Failed to resolve redirect URI with dynamic port: {e}")
        raise
