"""
Kindred AI SDK - Core interceptor logic and initialization.
"""

import json
import logging
import os
from typing import Dict, List, Optional
from contextlib import contextmanager
from urllib.parse import urlparse

from .rules import validate_rules
from .patchers import RequestsPatcher, HTTPXPatcher

logger = logging.getLogger(__name__)

# Environment variable name for agent token (also serves as agent ID)
ENV_AGENT_TOKEN = "KINDRED_AGENT_TOKEN"
# Environment variable name for Kindred URL (used for fetching restricted URLs and as sidecar endpoint)
ENV_KINDRED_URL = "KINDRED_URL"


class Interceptor:
    """
    Kindred AI interceptor class for rerouting HTTP requests.
    
    Example:
        >>> rules = {
        ...     "api.stripe.com": "http://localhost:8080/stripe-mock",
        ...     "api.openai.com": "https://my-gateway.com/openai"
        ... }
        >>> Interceptor.init(routing_rules=rules, verbose=True)
        >>> # Now all requests matching the rules will be rerouted
        >>> Interceptor.stop()
    """
    
    _is_initialized = False
    _rules: Dict[str, str] = {}
    _verbose: bool = False
    _agent_token: Optional[str] = None
    
    @classmethod
    def init(
        cls,
        routing_rules: Optional[Dict[str, str]] = None,
        agent_token: Optional[str] = None,
        kindred_url: Optional[str] = None,
        verbose: bool = False
    ) -> None:
        """
        Initialize the Kindred AI interceptor with routing rules.
        
        Args:
            routing_rules: Dictionary mapping original hostnames to new destinations.
                Example: {"api.stripe.com": "http://localhost:8080/stripe-mock", "api.openai.com": "https://my-gateway.com/openai"}
                If None, will attempt to fetch from server using KINDRED_AGENT_TOKEN and KINDRED_URL environment variables.
                Special key "*" or "__all__" routes all requests to the specified endpoint.
            agent_token: The agent token (also serves as agent ID). If provided along with kindred_url, will fetch restricted URLs from server.
                Can also be set via KINDRED_AGENT_TOKEN environment variable.
            kindred_url: Base URL of the Kindred server. Used for fetching restricted URLs and as the sidecar endpoint.
                Can also be set via KINDRED_URL environment variable.
            verbose: Whether to log intercepted requests and patching status
            
        Raises:
            ValueError: If routing rules are invalid or required parameters are missing
        """
        if cls._is_initialized:
            logger.warning("Kindred AI interceptor is already initialized. Call stop() first to reinitialize.")
            return
        
        # Get agent_token and kindred_url from parameters or environment variables
        agent_token = agent_token or os.getenv(ENV_AGENT_TOKEN)
        kindred_url = kindred_url or os.getenv(ENV_KINDRED_URL)
        
        # If no rules provided, try to fetch from server if agent_token and kindred_url are available
        if routing_rules is None:
            # If both agent_token and kindred_url are available, fetch restricted URLs from server
            if agent_token and kindred_url:
                try:
                    if verbose:
                        logger.info(f"Fetching restricted URLs from server for agent {agent_token}")
                    
                    # Fetch restricted URLs (agent_token serves as agent_id)
                    restricted_urls = cls.fetch_restricted_urls_from_server(
                        server_url=kindred_url,
                        agent_id=agent_token,
                        verbose=verbose
                    )
                    
                    # If no restricted URLs found, don't initialize interceptor
                    if not restricted_urls:
                        if verbose:
                            logger.info(f"No restricted URLs found for agent {agent_token}. Kindred AI interceptor will not be initialized - no requests will be intercepted.")
                        return  # Don't initialize if there are no restricted URLs
                    
                    # Build rules from restricted URLs (kindred_url is used as the sidecar endpoint)
                    routing_rules = cls.build_rules_from_restricted_urls(
                        restricted_urls=restricted_urls,
                        sidecar_endpoint=kindred_url,
                        verbose=verbose
                    )
                    
                    # If building rules resulted in empty dict (shouldn't happen if restricted_urls is non-empty, but check anyway)
                    if not routing_rules:
                        if verbose:
                            logger.info(f"No valid restricted URLs found for agent {agent_token}. Kindred AI interceptor will not be initialized - no requests will be intercepted.")
                        return  # Don't initialize if there are no rules
                    
                except Exception as e:
                    if verbose:
                        logger.warning(f"Failed to fetch restricted URLs from server: {e}")
                    # When fetching fails, don't intercept anything rather than intercepting everything
                    # This is safer - if we can't get the restricted list, we shouldn't intercept
                    if verbose:
                        logger.info("Failed to fetch restricted URLs. Kindred AI interceptor will not be initialized - no requests will be intercepted.")
                    return  # Don't initialize if we can't fetch restricted URLs
            else:
                raise ValueError(
                    f"No routing rules provided and required environment variables not set. "
                    f"Either provide routing_rules, or set {ENV_AGENT_TOKEN} and {ENV_KINDRED_URL} environment variables."
                )
        
        # Validate rules
        validate_rules(routing_rules)
        
        cls._rules = routing_rules.copy()
        cls._verbose = verbose
        
        # Use agent_token from parameter or environment variable
        cls._agent_token = agent_token
        if cls._agent_token and verbose:
            logger.info(f"Agent token found (will be added to intercepted requests)")
        elif not cls._agent_token and verbose:
            logger.warning(f"No agent token set - requests will not include agent token")
        
        # Configure logging if verbose
        if verbose:
            logging.basicConfig(
                level=logging.INFO,
                format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            )
        
        # Apply patches
        try:
            RequestsPatcher.patch(rules=cls._rules, agent_token=cls._agent_token, verbose=verbose)
        except Exception as e:
            if verbose:
                logger.warning(f"Could not patch requests: {e}")
        
        try:
            HTTPXPatcher.patch(rules=cls._rules, agent_token=cls._agent_token, verbose=verbose)
        except Exception as e:
            if verbose:
                logger.warning(f"Could not patch httpx: {e}")
        
        cls._is_initialized = True
        
        if verbose:
            logger.info(f"Kindred AI interceptor initialized with {len(routing_rules)} routing rules")
    
    @classmethod
    def stop(cls) -> None:
        """
        Stop interception and restore original behavior.
        """
        if not cls._is_initialized:
            return
        
        RequestsPatcher.unpatch()
        HTTPXPatcher.unpatch()
        
        cls._is_initialized = False
        cls._rules = {}
        cls._agent_token = None
        
        if cls._verbose:
            logger.info("Kindred AI interceptor stopped")
    
    @classmethod
    @contextmanager
    def scope(
        cls,
        routing_rules: Optional[Dict[str, str]] = None,
        agent_token: Optional[str] = None,
        kindred_url: Optional[str] = None,
        verbose: bool = False
    ):
        """
        Context manager for temporary Kindred AI interception.
        
        Args:
            routing_rules: Dictionary mapping original hostnames to new destinations.
                         If None, will attempt to fetch from server using KINDRED_AGENT_TOKEN and KINDRED_URL environment variables.
            agent_token: The agent token (also serves as agent ID). If provided along with kindred_url, will fetch restricted URLs from server.
            kindred_url: Base URL of the Kindred server. Used for fetching restricted URLs and as the sidecar endpoint.
            verbose: Whether to log intercepted requests
            
        Example:
            >>> with Interceptor.scope({"api.stripe.com": "http://localhost:8080"}):
            ...     # Requests here are intercepted
            ...     requests.get("https://api.stripe.com/v1/charges")
            >>> # Requests here are normal
        """
        was_initialized = cls._is_initialized
        
        # If already initialized, stop first
        if was_initialized:
            cls.stop()
        
        try:
            cls.init(
                routing_rules=routing_rules,
                agent_token=agent_token,
                kindred_url=kindred_url,
                verbose=verbose
            )
            yield
        finally:
            cls.stop()
            
            # Re-initialize if it was initialized before
            if was_initialized:
                cls.init(routing_rules=cls._rules, verbose=cls._verbose)
    
    @classmethod
    def is_active(cls) -> bool:
        """
        Check if the Kindred AI interceptor is currently active.
        
        Returns:
            True if interceptor is initialized, False otherwise
        """
        return cls._is_initialized
    
    @classmethod
    def get_rules(cls) -> Dict[str, str]:
        """
        Get the current routing rules.
        
        Returns:
            Copy of the current routing rules dictionary
        """
        return cls._rules.copy()
    
    @classmethod
    def fetch_restricted_urls_from_server(
        cls,
        server_url: str,
        agent_id: str,
        verbose: bool = False
    ) -> List[str]:
        """
        Fetch restricted URLs for an agent from the Kindred AI server.
        
        Args:
            server_url: Base URL of the sidecar server (e.g., "http://localhost:8080")
            agent_id: UUID of the agent
            verbose: Whether to log the fetch operation
            
        Returns:
            List of restricted URLs (strings)
            
        Raises:
            ValueError: If server_url or agent_id is not provided
            ConnectionError: If unable to connect to the server
            Exception: If the server returns an error
        """
        if not server_url:
            raise ValueError("server_url is required")
        if not agent_id:
            raise ValueError("agent_id is required")
        
        try:
            import urllib.request
            import urllib.parse
            import urllib.error
            
            # Build the request URL
            url = f"{server_url.rstrip('/')}/restricted-urls?agent_id={urllib.parse.quote(agent_id)}"
            
            if verbose:
                logger.info(f"Fetching restricted URLs from {url}")
            
            # Make the request
            req = urllib.request.Request(url)
            req.add_header('Accept', 'application/json')
            
            with urllib.request.urlopen(req, timeout=10) as response:
                if response.status != 200:
                    error_body = response.read().decode('utf-8')
                    try:
                        error_data = json.loads(error_body)
                        error_message = error_data.get('message', error_body)
                    except (json.JSONDecodeError, AttributeError):
                        error_message = error_body
                    
                    if response.status == 503:
                        raise ConnectionError(
                            f"Server unavailable (503): {error_message}. "
                            "This usually means Supabase is not configured on the server. "
                            "Please ensure SUPABASE_URL and SUPABASE_KEY environment variables are set on the server."
                        )
                    else:
                        raise Exception(f"Server returned status {response.status}: {error_message}")
                
                data = json.loads(response.read().decode('utf-8'))
                
                if data.get('status') != 'ok':
                    raise Exception(f"Server error: {data.get('message', 'Unknown error')}")
                
                urls = data.get('urls', [])
                
                if verbose:
                    logger.info(f"Fetched {len(urls)} restricted URLs for agent {agent_id}")
                
                return urls
                
        except urllib.error.URLError as e:
            raise ConnectionError(f"Unable to connect to server at {server_url}: {e}")
        except Exception as e:
            if verbose:
                logger.error(f"Error fetching restricted URLs: {e}")
            raise
    
    @classmethod
    def build_rules_from_restricted_urls(
        cls,
        restricted_urls: List[str],
        sidecar_endpoint: str,
        verbose: bool = False
    ) -> Dict[str, str]:
        """
        Build routing rules dictionary from a list of restricted URLs.
        
        Each restricted URL's hostname will be mapped to the sidecar endpoint.
        If multiple URLs share the same hostname, the hostname will only appear once.
        
        Args:
            restricted_urls: List of URLs that should be intercepted
            sidecar_endpoint: The sidecar server endpoint to route intercepted requests to
            verbose: Whether to log the rule building process
            
        Returns:
            Dictionary mapping hostnames to the sidecar endpoint
            
        Example:
            >>> urls = ["https://api.stripe.com/v1/charges", "https://api.openai.com/v1/chat"]
            >>> rules = Interceptor.build_rules_from_restricted_urls(urls, "http://localhost:8080")
            >>> # rules = {"api.stripe.com": "http://localhost:8080", "api.openai.com": "http://localhost:8080"}
        """
        rules = {}
        
        for url in restricted_urls:
            try:
                parsed = urlparse(url)
                hostname = parsed.netloc.split(':')[0]  # Remove port if present
                
                if hostname:
                    rules[hostname] = sidecar_endpoint
                    if verbose:
                        logger.debug(f"Added rule: {hostname} -> {sidecar_endpoint}")
                else:
                    if verbose:
                        logger.warning(f"Could not extract hostname from URL: {url}")
            except Exception as e:
                if verbose:
                    logger.warning(f"Error parsing URL {url}: {e}")
        
        if verbose:
            logger.info(f"Built {len(rules)} routing rules from {len(restricted_urls)} restricted URLs")
        
        return rules

