"""
OAuth provider configurations for Brynq Runtime.

Each provider defines the URLs, scopes, and environment variable names
needed for the OAuth 2.0 authorization code flow. Client IDs are read
from environment variables at runtime -- they are NEVER hardcoded.

Provider-specific notes:
    - Anthropic: Requires PKCE (Proof Key for Code Exchange)
    - Google: Standard OAuth 2.0 with offline access for refresh tokens
    - OpenAI: Auth0-based OAuth 2.0
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from typing import Optional


@dataclass(frozen=True)
class ProviderConfig:
    """Immutable OAuth configuration for a single provider.

    Attributes:
        name:           Human-readable provider name.
        auth_url:       Authorization endpoint (user consent page).
        token_url:      Token exchange endpoint.
        revoke_url:     Token revocation endpoint (None if not supported).
        scopes:         OAuth scopes to request.
        client_id_env:  Environment variable name for the client ID.
        requires_pkce:  Whether PKCE is required for this provider.
        extra_auth_params: Additional query params for the authorize request.
    """

    name: str
    auth_url: str
    token_url: str
    revoke_url: Optional[str]
    scopes: list[str]
    client_id_env: str
    client_secret_env: Optional[str] = None
    requires_pkce: bool = False
    extra_auth_params: dict[str, str] | None = None

    def get_client_id(self) -> Optional[str]:
        """Read the client ID from the environment. Returns None if unset."""
        return os.environ.get(self.client_id_env)

    def get_client_secret(self) -> Optional[str]:
        """Read the client secret from the environment. Returns None if unset."""
        if self.client_secret_env is None:
            return None
        return os.environ.get(self.client_secret_env)


# ── Provider Registry ──────────────────────────────────────────────────

PROVIDERS: dict[str, ProviderConfig] = {
    "anthropic": ProviderConfig(
        name="Anthropic (Claude)",
        auth_url="https://console.anthropic.com/oauth/authorize",
        token_url="https://console.anthropic.com/oauth/token",
        revoke_url="https://console.anthropic.com/oauth/revoke",
        scopes=["user:read", "messages:write"],
        client_id_env="BRYNQ_ANTHROPIC_CLIENT_ID",
        requires_pkce=True,
    ),
    "google": ProviderConfig(
        name="Google (Gemini)",
        auth_url="https://accounts.google.com/o/oauth2/v2/auth",
        token_url="https://oauth2.googleapis.com/token",
        revoke_url="https://oauth2.googleapis.com/revoke",
        scopes=["https://www.googleapis.com/auth/cloud-platform"],
        client_id_env="BRYNQ_GOOGLE_CLIENT_ID",
        client_secret_env="BRYNQ_GOOGLE_CLIENT_SECRET",
        requires_pkce=False,
        extra_auth_params={"access_type": "offline", "prompt": "consent"},
    ),
    "openai": ProviderConfig(
        name="OpenAI (ChatGPT)",
        auth_url="https://auth0.openai.com/authorize",
        token_url="https://auth0.openai.com/oauth/token",
        revoke_url=None,  # OpenAI does not expose a public revocation endpoint
        scopes=["openid", "profile"],
        client_id_env="BRYNQ_OPENAI_CLIENT_ID",
        requires_pkce=False,
        extra_auth_params={"audience": "https://api.openai.com/v1"},
    ),
}


def get_provider(name: str) -> ProviderConfig:
    """Look up a provider config by name (case-insensitive).

    Raises:
        ValueError: If the provider is not supported.
    """
    key = name.strip().lower()
    if key not in PROVIDERS:
        supported = ", ".join(sorted(PROVIDERS))
        raise ValueError(
            f"Unknown OAuth provider: {name!r}. Supported: {supported}"
        )
    return PROVIDERS[key]


def list_provider_names() -> list[str]:
    """Return all supported provider names, sorted."""
    return sorted(PROVIDERS)
