from typing import Any

from otto.auth import AuthStorage
from otto.log import get_logger

from .base import OAuthProvider, OAuthProviderError

# Lazy import to avoid circular dependency (otto.acp.registry -> otto.log -> ...)
_is_acp_model = None


def _check_acp_model(model: str) -> bool:
    """Return True if model is an ACP model. Uses lazy import to avoid circular deps."""
    global _is_acp_model
    if _is_acp_model is None:
        from otto.acp.registry import is_acp_model

        _is_acp_model = is_acp_model
    return _is_acp_model(model)


log = get_logger(__name__)

# === Registry State ===

_PROVIDERS: dict[str, type[OAuthProvider]] = {}

_PREFIX_MAP: dict[str, str] = {
    "claude-code": "anthropic_oauth",
    "copilot": "copilot",
    "github_copilot": "copilot",
    "codex": "openai_codex",
    "gemini-cli": "google_cloud_code",
}

_AUTH_PROVIDER_MAP: dict[str, str] = {
    "anthropic_oauth": "anthropic",
    "copilot": "copilot",
    "openai_codex": "openai",
    "google_cloud_code": "google",
}


# === Registration ===


def register_provider(provider_class: type[OAuthProvider]) -> type[OAuthProvider]:
    """Register an OAuth provider class."""
    provider_name = provider_class.provider_name
    _PROVIDERS[provider_name] = provider_class

    for prefix in provider_class.model_prefixes:
        _PREFIX_MAP[prefix.lower().rstrip("/")] = provider_name

    return provider_class


# === Lookup Helpers ===


def get_provider_for_model(model: str) -> str | None:
    """Return the OAuth provider name for a model prefix."""
    model_lower = model.lower()
    for prefix, provider_name in _PREFIX_MAP.items():
        if model_lower == prefix or model_lower.startswith(prefix + "/"):
            return provider_name
    return None


def get_auth_provider_name(provider_name: str) -> str:
    """Map provider implementation name to AuthStorage key."""
    return _AUTH_PROVIDER_MAP.get(provider_name, provider_name)


def _model_has_prefix(model: str, prefix: str) -> bool:
    model_lower = model.lower()
    normalized_prefix = prefix.lower().rstrip("/")
    return model_lower == normalized_prefix or model_lower.startswith(normalized_prefix + "/")


def needs_custom_provider(model: str) -> bool:
    """Return True when a model must use the custom OAuth provider path.

    ACP models are handled by their own provider and never enter the OAuth path.
    """
    if _check_acp_model(model):
        return False
    return (
        _model_has_prefix(model, "claude-code")
        or _model_has_prefix(model, "codex")
        or _model_has_prefix(model, "gemini-cli")
    )


def is_oauth_model(model: str, auth_storage: AuthStorage | None = None) -> bool:
    """Return True when a model should use OAuth provider flow.

    ACP models are never OAuth models — they use the ACP protocol instead.
    """
    if _check_acp_model(model):
        return False
    provider_name = get_provider_for_model(model)
    if not provider_name:
        return False

    if auth_storage is None:
        return True

    auth_name = get_auth_provider_name(provider_name)
    credentials = auth_storage.get_credentials(auth_name)
    if not credentials:
        return False

    return credentials.get("type") == "oauth"


async def get_litellm_overrides(
    model: str,
    auth_storage: AuthStorage,
) -> dict[str, Any] | None:
    """Return LiteLLM keyword overrides for OAuth models that support native routing."""
    _ = auth_storage

    if needs_custom_provider(model):
        return None

    provider_name = get_provider_for_model(model)
    if not provider_name:
        return None

    # Copilot now uses LiteLLM's native auth/token handling.
    if provider_name == "copilot":
        return None

    return None


async def get_oauth_provider(
    model: str,
    auth_storage: AuthStorage | None = None,
    credentials: dict[str, Any] | None = None,
) -> OAuthProvider:
    """Instantiate the OAuth provider for a model."""
    provider_name = get_provider_for_model(model)
    if not provider_name:
        raise OAuthProviderError(f"No OAuth provider registered for model: {model}")

    provider_class = _PROVIDERS.get(provider_name)
    if not provider_class:
        raise OAuthProviderError(f"Provider not found: {provider_name}")

    if credentials is None:
        if auth_storage is None:
            raise OAuthProviderError("Must provide auth_storage or credentials")

        auth_name = get_auth_provider_name(provider_name)

        # Trigger auto-refresh if token is expired before fetching credentials.
        try:
            refreshed_key = await auth_storage.get_api_key(auth_name)
            if not refreshed_key:
                raise OAuthProviderError(
                    f"No credentials found for {auth_name}. Run: otto auth login {auth_name}"
                )
        except OAuthProviderError:
            raise
        except Exception as exc:
            log.warning("Token refresh failed for %s: %s", auth_name, exc)

        credentials = auth_storage.get_credentials(auth_name)
        if not credentials:
            raise OAuthProviderError(
                f"No credentials found for {auth_name}. Run: otto auth login {auth_name}"
            )

    return provider_class(credentials)


def list_providers() -> list[str]:
    """List currently registered provider implementation names."""
    return list(_PROVIDERS.keys())


def list_prefixes() -> dict[str, str]:
    """List known prefix -> provider mappings."""
    return dict(_PREFIX_MAP)


# === Built-ins ===


def _register_builtins() -> None:
    """Register built-in provider modules if present."""
    try:
        from .providers.anthropic_oauth import AnthropicOAuthProvider

        register_provider(AnthropicOAuthProvider)
    except ImportError as exc:
        log.debug("Could not load Anthropic OAuth provider: %s", exc)

    try:
        from .providers.openai_codex import OpenAICodexProvider

        register_provider(OpenAICodexProvider)
    except ImportError as exc:
        log.debug("Could not load OpenAI Codex provider: %s", exc)

    try:
        from .providers.google_cloud_code import GoogleCloudCodeProvider

        register_provider(GoogleCloudCodeProvider)
    except ImportError as exc:
        log.debug("Could not load Google Cloud Code provider: %s", exc)


_register_builtins()
