"""
OAuth Configuration Management for Google Workspace MCP.

This module consolidates OAuth-related configuration from:
- oauth_config.py (OAuthConfig class, environment handling)
- google_oauth_config.py (embedded credentials, directory paths)

Provides a single source of truth for all OAuth-related settings.
Supports both OAuth 2.0 and OAuth 2.1 with automatic client capability detection.
"""

import os
from typing import Any
from urllib.parse import urlparse

# =============================================================================
# Google Workspace OAuth Desktop Application Credentials
# =============================================================================
# GCP Project: annular-aria-484116-r2
# OAuth Type: Desktop Application
#
# Per Google's documentation, client secrets for Desktop apps
# are not confidential and can be embedded in distributed applications.
# Reference: https://developers.google.com/identity/protocols/oauth2/native-app
# =============================================================================

GOOGLE_OAUTH_CLIENT_ID_EMBEDDED = ""
GOOGLE_OAUTH_CLIENT_SECRET_EMBEDDED = ""

# Application metadata
GOOGLE_WORKSPACE_MCP_APP_NAME = "GWS MCP Advanced"
GOOGLE_WORKSPACE_MCP_CREDENTIALS_DIR = "~/.config/gws-mcp-advanced"


class OAuthConfig:
    """
    Centralized OAuth configuration management.

    This class eliminates the hardcoded configuration anti-pattern by providing
    a single source of truth for all OAuth-related configuration values.
    """

    def __init__(self):
        # Base server configuration
        self.base_uri = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")
        self.port = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", "9876")))
        self.base_url = f"{self.base_uri}:{self.port}"

        # External URL for reverse proxy scenarios
        self.external_url = os.getenv("WORKSPACE_EXTERNAL_URL")

        # OAuth client configuration (prefer env vars, fallback to embedded)
        self.client_id = os.getenv("GOOGLE_OAUTH_CLIENT_ID", GOOGLE_OAUTH_CLIENT_ID_EMBEDDED) or None
        self.client_secret = os.getenv("GOOGLE_OAUTH_CLIENT_SECRET", GOOGLE_OAUTH_CLIENT_SECRET_EMBEDDED) or None

        # OAuth 2.1 configuration
        self.oauth21_enabled = os.getenv("MCP_ENABLE_OAUTH21", "false").lower() == "true"
        self.pkce_required = self.oauth21_enabled  # PKCE is mandatory in OAuth 2.1
        self.supported_code_challenge_methods = ["S256", "plain"] if not self.oauth21_enabled else ["S256"]

        # External OAuth 2.1 provider configuration
        self.external_oauth21_provider = os.getenv("EXTERNAL_OAUTH21_PROVIDER", "false").lower() == "true"
        if self.external_oauth21_provider and not self.oauth21_enabled:
            raise ValueError("EXTERNAL_OAUTH21_PROVIDER requires MCP_ENABLE_OAUTH21=true")

        # Stateless mode configuration
        self.stateless_mode = os.getenv("WORKSPACE_MCP_STATELESS_MODE", "false").lower() == "true"
        if self.stateless_mode and not self.oauth21_enabled:
            raise ValueError("WORKSPACE_MCP_STATELESS_MODE requires MCP_ENABLE_OAUTH21=true")

        # Transport mode (will be set at runtime)
        self._transport_mode = "stdio"  # Default

        # Redirect URI configuration
        self.redirect_uri = self._get_redirect_uri()
        self.redirect_path = self._get_redirect_path(self.redirect_uri)

        # Ensure FastMCP's Google provider picks up our existing configuration
        self._apply_fastmcp_google_env()

    def _get_redirect_uri(self) -> str:
        """
        Get the OAuth redirect URI, supporting reverse proxy configurations.

        Returns:
            The configured redirect URI
        """
        explicit_uri = os.getenv("GOOGLE_OAUTH_REDIRECT_URI")
        if explicit_uri:
            return explicit_uri
        return f"{self.base_url}/oauth2callback"

    @staticmethod
    def _get_redirect_path(uri: str) -> str:
        """Extract the redirect path from a full redirect URI."""
        parsed = urlparse(uri)
        if parsed.scheme or parsed.netloc:
            path = parsed.path or "/oauth2callback"
        else:
            # If the value was already a path, ensure it starts with '/'
            path = uri if uri.startswith("/") else f"/{uri}"
        return path or "/oauth2callback"

    def _apply_fastmcp_google_env(self) -> None:
        """Mirror legacy GOOGLE_* env vars into FastMCP Google provider settings."""
        if not self.client_id:
            return

        def _set_if_absent(key: str, value: str | None) -> None:
            if value and key not in os.environ:
                os.environ[key] = value

        # Don't set FASTMCP_SERVER_AUTH if using external OAuth provider
        # (external OAuth means protocol-level auth is disabled, only tool-level auth)
        if not self.external_oauth21_provider:
            _set_if_absent(
                "FASTMCP_SERVER_AUTH",
                "fastmcp.server.auth.providers.google.GoogleProvider" if self.oauth21_enabled else None,
            )

        _set_if_absent("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID", self.client_id)
        _set_if_absent("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET", self.client_secret)
        _set_if_absent("FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL", self.get_oauth_base_url())
        _set_if_absent("FASTMCP_SERVER_AUTH_GOOGLE_REDIRECT_PATH", self.redirect_path)

    def get_redirect_uris(self) -> list[str]:
        """
        Get all valid OAuth redirect URIs.

        Returns:
            List of all supported redirect URIs
        """
        uris = []

        # Primary redirect URI
        uris.append(self.redirect_uri)

        # Custom redirect URIs from environment
        custom_uris = os.getenv("OAUTH_CUSTOM_REDIRECT_URIS")
        if custom_uris:
            uris.extend([uri.strip() for uri in custom_uris.split(",")])

        # Remove duplicates while preserving order
        return list(dict.fromkeys(uris))

    def get_allowed_origins(self) -> list[str]:
        """
        Get allowed CORS origins for OAuth endpoints.

        Returns:
            List of allowed origins for CORS
        """
        origins = []

        # Server's own origin
        origins.append(self.base_url)

        # VS Code and development origins
        origins.extend(
            [
                "vscode-webview://",
                "https://vscode.dev",
                "https://github.dev",
            ]
        )

        # Custom origins from environment
        custom_origins = os.getenv("OAUTH_ALLOWED_ORIGINS")
        if custom_origins:
            origins.extend([origin.strip() for origin in custom_origins.split(",")])

        return list(dict.fromkeys(origins))

    def is_configured(self) -> bool:
        """
        Check if OAuth is properly configured.

        Returns:
            True if OAuth client credentials are available
        """
        return bool(self.client_id and self.client_secret)

    def get_oauth_base_url(self) -> str:
        """
        Get OAuth base URL for constructing OAuth endpoints.

        Uses WORKSPACE_EXTERNAL_URL if set (for reverse proxy scenarios),
        otherwise falls back to constructed base_url with port.

        Returns:
            Base URL for OAuth endpoints
        """
        if self.external_url:
            return self.external_url
        return self.base_url

    def validate_redirect_uri(self, uri: str) -> bool:
        """
        Validate if a redirect URI is allowed.

        Args:
            uri: The redirect URI to validate

        Returns:
            True if the URI is allowed, False otherwise
        """
        allowed_uris = self.get_redirect_uris()
        return uri in allowed_uris

    def get_environment_summary(self) -> dict:
        """
        Get a summary of the current OAuth configuration.

        Returns:
            Dictionary with configuration summary (excluding secrets)
        """
        return {
            "base_url": self.base_url,
            "external_url": self.external_url,
            "effective_oauth_url": self.get_oauth_base_url(),
            "redirect_uri": self.redirect_uri,
            "redirect_path": self.redirect_path,
            "client_configured": bool(self.client_id),
            "oauth21_enabled": self.oauth21_enabled,
            "external_oauth21_provider": self.external_oauth21_provider,
            "pkce_required": self.pkce_required,
            "transport_mode": self._transport_mode,
            "total_redirect_uris": len(self.get_redirect_uris()),
            "total_allowed_origins": len(self.get_allowed_origins()),
        }

    def set_transport_mode(self, mode: str) -> None:
        """
        Set the current transport mode for OAuth callback handling.

        Args:
            mode: Transport mode ("stdio", "streamable-http", etc.)
        """
        self._transport_mode = mode

    def get_transport_mode(self) -> str:
        """
        Get the current transport mode.

        Returns:
            Current transport mode
        """
        return self._transport_mode

    def is_oauth21_enabled(self) -> bool:
        """
        Check if OAuth 2.1 mode is enabled.

        Returns:
            True if OAuth 2.1 is enabled
        """
        return self.oauth21_enabled

    def is_external_oauth21_provider(self) -> bool:
        """
        Check if external OAuth 2.1 provider mode is enabled.

        When enabled, the server expects external OAuth flow with bearer tokens
        in Authorization headers for tool calls. Protocol-level auth is disabled.

        Returns:
            True if external OAuth 2.1 provider is enabled
        """
        return self.external_oauth21_provider

    def detect_oauth_version(self, request_params: dict[str, Any]) -> str:
        """
        Detect OAuth version based on request parameters.

        This method implements a conservative detection strategy:
        - Only returns "oauth21" when we have clear indicators
        - Defaults to "oauth20" for backward compatibility
        - Respects the global oauth21_enabled flag

        Args:
            request_params: Request parameters from authorization or token request

        Returns:
            "oauth21" or "oauth20" based on detection
        """
        # If OAuth 2.1 is not enabled globally, always return OAuth 2.0
        if not self.oauth21_enabled:
            return "oauth20"

        # Use the structured type for cleaner detection logic
        from auth.credential_types.types import OAuthVersionDetectionParams

        params = OAuthVersionDetectionParams.from_request(request_params)

        # Clear OAuth 2.1 indicator: PKCE is present
        if params.has_pkce:
            return "oauth21"

        # Additional detection: Check if we have an active OAuth 2.1 session
        # This is important for tool calls where PKCE params aren't available
        authenticated_user = request_params.get("authenticated_user")
        if authenticated_user:
            try:
                from auth.oauth21_session_store import get_oauth21_session_store

                store = get_oauth21_session_store()
                if store.has_session(authenticated_user):
                    return "oauth21"
            except (ImportError, AttributeError, RuntimeError):
                pass  # Fall back to OAuth 2.0 if session check fails

        # For public clients in OAuth 2.1 mode, we require PKCE
        # But since they didn't send PKCE, fall back to OAuth 2.0
        # This ensures backward compatibility

        # Default to OAuth 2.0 for maximum compatibility
        return "oauth20"

    def get_authorization_server_metadata(self, scopes: list[str] | None = None) -> dict[str, Any]:
        """
        Get OAuth authorization server metadata per RFC 8414.

        Args:
            scopes: Optional list of supported scopes to include in metadata

        Returns:
            Authorization server metadata dictionary
        """
        oauth_base = self.get_oauth_base_url()
        metadata = {
            "issuer": "https://accounts.google.com",
            "authorization_endpoint": f"{oauth_base}/oauth2/authorize",
            "token_endpoint": f"{oauth_base}/oauth2/token",
            "registration_endpoint": f"{oauth_base}/oauth2/register",
            "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
            "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
            "response_types_supported": ["code", "token"],
            "grant_types_supported": ["authorization_code", "refresh_token"],
            "token_endpoint_auth_methods_supported": [
                "client_secret_post",
                "client_secret_basic",
            ],
            "code_challenge_methods_supported": self.supported_code_challenge_methods,
        }

        # Include scopes if provided
        if scopes is not None:
            metadata["scopes_supported"] = scopes

        # Add OAuth 2.1 specific metadata
        if self.oauth21_enabled:
            metadata["pkce_required"] = True
            # OAuth 2.1 deprecates implicit flow
            metadata["response_types_supported"] = ["code"]
            # OAuth 2.1 requires exact redirect URI matching
            metadata["require_exact_redirect_uri"] = True

        return metadata

    def get_google_oauth_config(self) -> dict[str, Any]:
        """
        Get the OAuth configuration in Google's expected format.

        Returns:
            OAuth client configuration in Google's expected format.

        Raises:
            ValueError: If OAuth credentials are not configured.
        """
        if not self.client_id or not self.client_secret:
            raise ValueError(
                "Google OAuth credentials not configured. "
                "Set GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET environment variables, "
                "or provide embedded credentials in auth/config.py"
            )

        return {
            "installed": {
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                "project_id": os.getenv("GOOGLE_PROJECT_ID", "annular-aria-484116-r2"),
                "auth_uri": "https://accounts.google.com/o/oauth2/auth",
                "token_uri": "https://oauth2.googleapis.com/token",
                "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
                "redirect_uris": ["http://localhost"],
            }
        }


# Global configuration instance
_oauth_config: OAuthConfig | None = None


def get_oauth_config() -> OAuthConfig:
    """
    Get the global OAuth configuration instance.

    Returns:
        The singleton OAuth configuration instance
    """
    global _oauth_config
    if _oauth_config is None:
        _oauth_config = OAuthConfig()
    return _oauth_config


def reload_oauth_config() -> OAuthConfig:
    """
    Reload the OAuth configuration from environment variables.

    This is useful for testing or when environment variables change.

    Returns:
        The reloaded OAuth configuration instance
    """
    global _oauth_config
    _oauth_config = OAuthConfig()
    return _oauth_config


# =============================================================================
# Convenience functions for backward compatibility
# =============================================================================


def get_oauth_base_url() -> str:
    """Get OAuth base URL."""
    return get_oauth_config().get_oauth_base_url()


def get_redirect_uris() -> list[str]:
    """Get all valid OAuth redirect URIs."""
    return get_oauth_config().get_redirect_uris()


def get_allowed_origins() -> list[str]:
    """Get allowed CORS origins."""
    return get_oauth_config().get_allowed_origins()


def is_oauth_configured() -> bool:
    """Check if OAuth is properly configured."""
    return get_oauth_config().is_configured()


def set_transport_mode(mode: str) -> None:
    """Set the current transport mode."""
    get_oauth_config().set_transport_mode(mode)


def get_transport_mode() -> str:
    """Get the current transport mode."""
    return get_oauth_config().get_transport_mode()


def is_oauth21_enabled() -> bool:
    """Check if OAuth 2.1 is enabled."""
    return get_oauth_config().is_oauth21_enabled()


def get_oauth_redirect_uri() -> str:
    """Get the primary OAuth redirect URI."""
    return get_oauth_config().redirect_uri


def is_stateless_mode() -> bool:
    """Check if stateless mode is enabled."""
    return get_oauth_config().stateless_mode


def is_external_oauth21_provider() -> bool:
    """Check if external OAuth 2.1 provider mode is enabled."""
    return get_oauth_config().is_external_oauth21_provider()


def get_google_oauth_config() -> dict[str, Any]:
    """
    Get the OAuth configuration for Google Workspace MCP.

    Returns:
        OAuth client configuration in Google's expected format.

    Raises:
        ValueError: If OAuth credentials are not configured.
    """
    return get_oauth_config().get_google_oauth_config()


def get_credentials_directory() -> str:
    """
    Get the directory for storing user credentials.

    Returns:
        Expanded path to credentials directory.
    """
    base_dir = os.getenv("WORKSPACE_MCP_CONFIG_DIR", GOOGLE_WORKSPACE_MCP_CREDENTIALS_DIR)
    return os.path.expanduser(base_dir)


def get_sync_map_path() -> str:
    """
    Get the path for the sync map file.

    Returns:
        Expanded path to gdrive_map.json.
    """
    return os.path.join(get_credentials_directory(), "gdrive_map.json")


def is_using_embedded_credentials() -> bool:
    """
    Check if using embedded credentials vs environment override.

    Returns:
        True if using embedded credentials.
    """
    return os.getenv("GOOGLE_OAUTH_CLIENT_ID") is None and os.getenv("GOOGLE_OAUTH_CLIENT_SECRET") is None


# =============================================================================
# Module-level constants for backward compatibility
# These were originally in core/config.py and are re-exported here.
# =============================================================================

# Server configuration (read once at module load time)
WORKSPACE_MCP_PORT = int(os.getenv("PORT", os.getenv("WORKSPACE_MCP_PORT", "9876")))
WORKSPACE_MCP_BASE_URI = os.getenv("WORKSPACE_MCP_BASE_URI", "http://localhost")

# User configuration
# Disable USER_GOOGLE_EMAIL in OAuth 2.1 multi-user mode
USER_GOOGLE_EMAIL = None if is_oauth21_enabled() else os.getenv("USER_GOOGLE_EMAIL", None)
