"""OAuth provider registry and authentication flag generation.

Encodes supported OAuth providers as a declarative registry of frozen
dataclasses.  Each provider knows its issuer URI template, required
parameters, and the Helm ``--set`` flags needed to configure it.
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Any

from ilum.errors import AuthError

# ---------------------------------------------------------------------------
# Enumerations
# ---------------------------------------------------------------------------


class AuthMode(Enum):
    """Authentication mode for the Ilum platform."""

    INTERNAL = "internal"
    HYDRA = "hydra"
    EXTERNAL_OAUTH2 = "oauth2"


class ProviderName(Enum):
    """Supported OAuth provider identifiers."""

    HYDRA = "hydra"
    GOOGLE = "google"
    GITLAB = "gitlab"
    GITHUB = "github"
    KEYCLOAK = "keycloak"
    AWS_COGNITO = "aws-cognito"
    AZURE_AD = "azure-ad"
    OKTA = "okta"
    OIDC = "oidc"


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class ProviderParam:
    """Describes a single parameter for an OAuth provider."""

    name: str
    """CLI flag name, e.g. ``"client-id"``."""

    prompt: str
    """Human-readable prompt text."""

    required: bool = True
    """Whether this parameter must be supplied."""

    default: str = ""
    """Default value (empty string means no default)."""

    secret: bool = False
    """Whether this value should be masked in output."""

    env_var: str = ""
    """Optional environment variable to read from."""


@dataclass(frozen=True)
class OAuthProvider:
    """Immutable descriptor for a supported OAuth provider."""

    name: ProviderName
    """Provider identifier."""

    description: str
    """One-line human-readable summary."""

    auth_mode: AuthMode
    """Whether this uses Hydra or external OAuth2."""

    issuer_uri_template: str
    """Template for the OIDC issuer URI (interpolated with user params)."""

    params: tuple[ProviderParam, ...] = ()
    """Parameters the user must supply."""

    extra_set_flags: tuple[str, ...] = ()
    """Additional Helm ``--set`` flags beyond the standard ones."""

    notes: str = ""
    """Optional usage notes displayed to the user."""


# ---------------------------------------------------------------------------
# Common parameters
# ---------------------------------------------------------------------------

_CLIENT_ID = ProviderParam(
    name="client-id",
    prompt="OAuth2 Client ID",
)

_CLIENT_SECRET = ProviderParam(
    name="client-secret",
    prompt="OAuth2 Client Secret",
    secret=True,
)


# ---------------------------------------------------------------------------
# Provider registry
# ---------------------------------------------------------------------------

PROVIDER_REGISTRY: dict[ProviderName, OAuthProvider] = {}


def _register(provider: OAuthProvider) -> OAuthProvider:
    """Register a provider and return it."""
    PROVIDER_REGISTRY[provider.name] = provider
    return provider


_register(
    OAuthProvider(
        name=ProviderName.HYDRA,
        description="Ory Hydra internal OIDC provider",
        auth_mode=AuthMode.HYDRA,
        issuer_uri_template="",
        params=(
            ProviderParam(
                name="domain",
                prompt="UI domain (e.g. ilum.example.com)",
            ),
            ProviderParam(
                name="protocol",
                prompt="UI protocol",
                default="http",
            ),
            ProviderParam(
                name="client-id",
                prompt="Hydra Client ID",
                default="ilum-client",
            ),
            ProviderParam(
                name="client-secret",
                prompt="Hydra Client Secret",
                secret=True,
                default="secret",
            ),
        ),
        extra_set_flags=("postgresql.enabled=true",),
        notes="Hydra requires PostgreSQL. It will be enabled automatically.",
    )
)

_register(
    OAuthProvider(
        name=ProviderName.GOOGLE,
        description="Google OAuth2",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="https://accounts.google.com",
        params=(_CLIENT_ID, _CLIENT_SECRET),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.GITLAB,
        description="GitLab OAuth2",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="{url}",
        params=(
            _CLIENT_ID,
            _CLIENT_SECRET,
            ProviderParam(
                name="url",
                prompt="GitLab URL",
                default="https://gitlab.com",
            ),
        ),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.GITHUB,
        description="GitHub OAuth2",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="https://github.com",
        params=(_CLIENT_ID, _CLIENT_SECRET),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.KEYCLOAK,
        description="Keycloak OIDC",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="{url}/realms/{realm}",
        params=(
            _CLIENT_ID,
            _CLIENT_SECRET,
            ProviderParam(name="url", prompt="Keycloak base URL"),
            ProviderParam(name="realm", prompt="Keycloak realm name"),
        ),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.AWS_COGNITO,
        description="AWS Cognito",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="https://cognito-idp.{region}.amazonaws.com/{user_pool_id}",
        params=(
            _CLIENT_ID,
            _CLIENT_SECRET,
            ProviderParam(name="region", prompt="AWS region (e.g. us-east-1)"),
            ProviderParam(name="user-pool-id", prompt="Cognito User Pool ID"),
        ),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.AZURE_AD,
        description="Microsoft Azure AD (Entra ID)",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="https://login.microsoftonline.com/{tenant_id}/v2.0",
        params=(
            _CLIENT_ID,
            _CLIENT_SECRET,
            ProviderParam(name="tenant-id", prompt="Azure AD Tenant ID"),
        ),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.OKTA,
        description="Okta OIDC",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="https://{domain}",
        params=(
            _CLIENT_ID,
            _CLIENT_SECRET,
            ProviderParam(name="domain", prompt="Okta domain (e.g. dev-123.okta.com)"),
        ),
    )
)

_register(
    OAuthProvider(
        name=ProviderName.OIDC,
        description="Generic OIDC provider",
        auth_mode=AuthMode.EXTERNAL_OAUTH2,
        issuer_uri_template="{issuer_uri}",
        params=(
            _CLIENT_ID,
            _CLIENT_SECRET,
            ProviderParam(name="issuer-uri", prompt="OIDC Issuer URI"),
        ),
    )
)


# ---------------------------------------------------------------------------
# Lookup helpers
# ---------------------------------------------------------------------------


def get_provider(name: str) -> OAuthProvider:
    """Return a provider by name, or raise :class:`AuthError`."""
    try:
        key = ProviderName(name)
    except ValueError:
        available = ", ".join(p.value for p in ProviderName)
        raise AuthError(
            f"Unknown auth provider: {name!r}",
            suggestion=f"Available providers: {available}",
            error_code="ILUM-060",
        ) from None
    return PROVIDER_REGISTRY[key]


def list_providers() -> list[OAuthProvider]:
    """Return all registered providers in definition order."""
    return list(PROVIDER_REGISTRY.values())


# ---------------------------------------------------------------------------
# Issuer URI resolution
# ---------------------------------------------------------------------------


def resolve_issuer_uri(provider: OAuthProvider, params: dict[str, str]) -> str:
    """Interpolate the provider's issuer URI template with user params.

    Parameter names use hyphens in CLI flags but underscores in templates.
    Both forms are tried during interpolation.
    """
    if not provider.issuer_uri_template:
        return ""

    # Build a mapping that accepts both hyphenated and underscored keys
    template_vars: dict[str, str] = {}
    for key, value in params.items():
        template_vars[key] = value
        template_vars[key.replace("-", "_")] = value

    try:
        return provider.issuer_uri_template.format(**template_vars)
    except KeyError as exc:
        raise AuthError(
            f"Missing parameter for issuer URI template: {exc}",
            error_code="ILUM-061",
        ) from None


# ---------------------------------------------------------------------------
# Flag generation
# ---------------------------------------------------------------------------


def generate_enable_flags(
    provider: OAuthProvider,
    params: dict[str, str],
    scope: str = "",
) -> list[str]:
    """Produce all ``--set`` flags needed to enable the given provider.

    For Hydra providers, sets ``global.security.hydra.*`` flags.
    For external OAuth2 providers, sets ``ilum-core.security.type=oauth2``
    and ``global.security.oauth2.*`` flags.
    """
    flags: list[str] = []

    if provider.auth_mode is AuthMode.HYDRA:
        flags.append("global.security.hydra.enabled=true")
        if "domain" in params:
            flags.append(f"global.security.hydra.uiDomain={params['domain']}")
        if "protocol" in params:
            flags.append(f"global.security.hydra.uiProtocol={params['protocol']}")
        if "client-id" in params:
            flags.append(f"global.security.hydra.clientId={params['client-id']}")
        if "client-secret" in params:
            flags.append(f"global.security.hydra.clientSecret={params['client-secret']}")
    elif provider.auth_mode is AuthMode.EXTERNAL_OAUTH2:
        issuer = resolve_issuer_uri(provider, params)
        flags.append("ilum-core.security.type=oauth2")
        flags.append(f"global.security.oauth2.issuerUri={issuer}")
        if "client-id" in params:
            flags.append(f"global.security.oauth2.clientId={params['client-id']}")
        if "client-secret" in params:
            flags.append(f"global.security.oauth2.clientSecret={params['client-secret']}")
        if scope:
            flags.append(f"global.security.oauth2.scope={scope}")

    # Provider-specific extra flags
    flags.extend(provider.extra_set_flags)

    return flags


def generate_disable_flags() -> list[str]:
    """Produce ``--set`` flags to revert to internal authentication."""
    return [
        "ilum-core.security.type=internal",
        "global.security.hydra.enabled=false",
    ]


# ---------------------------------------------------------------------------
# Live detection
# ---------------------------------------------------------------------------


def detect_auth_mode(live_values: dict[str, Any]) -> tuple[AuthMode, dict[str, Any]]:
    """Inspect live Helm values to determine the current auth mode.

    Returns ``(mode, details)`` where *details* contains relevant config
    keys for display.
    """
    details: dict[str, Any] = {}

    # Check Hydra
    global_sec = (live_values.get("global") or {}).get("security") or {}
    hydra = global_sec.get("hydra") or {}
    if hydra.get("enabled"):
        details["provider"] = "hydra"
        details["domain"] = hydra.get("uiDomain", "")
        details["protocol"] = hydra.get("uiProtocol", "")
        details["client_id"] = hydra.get("clientId", "")
        return AuthMode.HYDRA, details

    # Check external OAuth2
    core = live_values.get("ilum-core") or {}
    sec_type = (core.get("security") or {}).get("type", "internal")
    if sec_type == "oauth2":
        oauth2 = global_sec.get("oauth2") or {}
        details["provider"] = "oauth2"
        details["issuer_uri"] = oauth2.get("issuerUri", "")
        details["client_id"] = oauth2.get("clientId", "")
        details["scope"] = oauth2.get("scope", "")
        return AuthMode.EXTERNAL_OAUTH2, details

    # Default: internal
    details["provider"] = "internal"
    return AuthMode.INTERNAL, details
