from dataclasses import dataclass

AUTH_PROVIDERS = ("anthropic", "openai", "google", "copilot")


@dataclass(frozen=True)
class ModelSelectionResult:
    """Result for catalog-aware model validation."""

    is_ok: bool
    warning: str | None = None
    error: str | None = None


@dataclass(frozen=True)
class _ProviderCatalog:
    provider: str
    display_name: str
    prefixes: tuple[str, ...]
    models: tuple[str, ...]
    default_model: str


_PROVIDER_CATALOG: dict[str, _ProviderCatalog] = {
    "anthropic": _ProviderCatalog(
        provider="anthropic",
        display_name="Anthropic",
        prefixes=("anthropic", "claude-code"),
        models=(
            "anthropic/claude-haiku-4-5",
            "anthropic/claude-sonnet-4-5",
            "anthropic/claude-sonnet-4-6",
            "anthropic/claude-opus-4-5",
            "anthropic/claude-opus-4-6",
            "claude-code/claude-haiku-4-5",
            "claude-code/claude-sonnet-4-5",
            "claude-code/claude-sonnet-4-6",
            "claude-code/claude-opus-4-5",
            "claude-code/claude-opus-4-6",
        ),
        default_model="anthropic/claude-sonnet-4-5",
    ),
    "openai": _ProviderCatalog(
        provider="openai",
        display_name="OpenAI",
        prefixes=("openai", "codex"),
        models=(
            "openai/gpt-5.4-codex",
            "openai/gpt-5.3-codex",
            "openai/gpt-5.3-codex-spark",
            "openai/gpt-5.2-codex",
            "openai/gpt-5.1-codex-max",
            "openai/gpt-5.2",
            "openai/gpt-5.1-codex-mini",
            "codex/gpt-5.4-codex",
            "codex/gpt-5.3-codex",
            "codex/gpt-5.3-codex-spark",
            "codex/gpt-5.2-codex",
            "codex/gpt-5.1-codex-max",
            "codex/gpt-5.2",
            "codex/gpt-5.1-codex-mini",
        ),
        default_model="codex/gpt-5.4-codex",
    ),
    "google": _ProviderCatalog(
        provider="google",
        display_name="Google",
        prefixes=("google", "gemini-cli"),
        models=(
            "google/gemini-2.5-pro",
            "google/gemini-2.5-flash",
            "google/gemini-3-pro-preview",
            "google/gemini-3-flash-preview",
            "google/gemini-3.1-pro-preview",
            "gemini-cli/gemini-2.5-pro",
            "gemini-cli/gemini-2.5-flash",
            "gemini-cli/gemini-3-pro-preview",
            "gemini-cli/gemini-3-flash-preview",
            "gemini-cli/gemini-3.1-pro-preview",
        ),
        default_model="google/gemini-2.5-pro",
    ),
    "copilot": _ProviderCatalog(
        provider="copilot",
        display_name="Copilot",
        prefixes=("copilot", "github_copilot"),
        models=(
            "github_copilot/gpt-4o",
            "github_copilot/gpt-4o-mini",
            "github_copilot/gpt-5-mini",
            "github_copilot/gpt-5",
            "github_copilot/gpt-5.2",
            "github_copilot/gpt-5.4-codex",
            "github_copilot/gpt-5.3-codex",
        ),
        default_model="github_copilot/gpt-5-mini",
    ),
}


def list_oauth_providers() -> list[str]:
    """Return supported OAuth providers in stable order."""
    return list(AUTH_PROVIDERS)


def list_oauth_models(provider: str) -> list[str]:
    """Return known model IDs for a provider."""
    catalog = _get_catalog(provider)
    if catalog is None:
        return []
    return list(catalog.models)


def get_default_oauth_model(provider: str) -> str | None:
    """Return the curated default model for a provider."""
    catalog = _get_catalog(provider)
    if catalog is None:
        return None
    return catalog.default_model


def get_oauth_provider_for_model(model: str) -> str | None:
    """Return OAuth provider inferred from an Otto model ID, if known."""
    normalized_model = model.strip().lower()
    for provider in AUTH_PROVIDERS:
        catalog = _get_catalog(provider)
        if catalog is None:
            continue
        if any(_model_has_prefix(normalized_model, prefix) for prefix in catalog.prefixes):
            return provider
    return None


def validate_oauth_model_for_provider(
    model: str,
    *,
    provider: str | None = None,
) -> ModelSelectionResult:
    """Validate model selection for OAuth-first setup.

    Rules:
      * Known models are accepted silently.
      * Unknown models with the correct OAuth prefix are accepted with a warning.
      * Models with mismatched OAuth prefixes are rejected.
    """
    normalized_model = model.strip()
    if not normalized_model:
        return ModelSelectionResult(is_ok=False, error="Model name cannot be empty.")

    selected_provider = _normalize_provider(provider)
    selected_catalog = _get_catalog(selected_provider)
    model_provider = get_oauth_provider_for_model(normalized_model)

    if selected_catalog is None:
        # No selected-provider context. Warn for known custom IDs.
        if model_provider is None:
            return ModelSelectionResult(is_ok=True)

        provider_catalog = _get_catalog(model_provider)
        if provider_catalog is None or normalized_model in provider_catalog.models:
            return ModelSelectionResult(is_ok=True)

        return ModelSelectionResult(
            is_ok=True,
            warning=(
                f"Model '{normalized_model}' is not in the curated list for "
                f"{model_provider}. Continuing with custom model IDs is supported."
            ),
        )

    if model_provider is None:
        if "/" in normalized_model:
            return ModelSelectionResult(
                is_ok=False,
                error=(
                    f"Model '{normalized_model}' does not match the selected "
                    f"OAuth provider '{selected_provider}'. "
                    f"Use one of: {', '.join(_sorted_prefixes(selected_catalog))}."
                ),
            )
        return ModelSelectionResult(is_ok=True)

    if model_provider != selected_provider:
        selected_prefixes = ", ".join(_sorted_prefixes(selected_catalog))
        return ModelSelectionResult(
            is_ok=False,
            error=(
                f"Model '{normalized_model}' belongs to '{model_provider}' "
                f"(expected '{selected_provider}'). "
                f"Use one of: {selected_prefixes} for '{selected_provider}'."
            ),
        )

    provider_catalog = _get_catalog(model_provider)
    if provider_catalog is None:
        return ModelSelectionResult(is_ok=False, error="Unknown provider state. Please retry.")

    if normalized_model not in provider_catalog.models:
        return ModelSelectionResult(
            is_ok=True,
            warning=(
                f"Model '{normalized_model}' is not in the curated list for "
                f"{selected_provider}. Continuing with custom model IDs is supported."
            ),
        )

    return ModelSelectionResult(is_ok=True)


def _get_catalog(provider: str | None) -> _ProviderCatalog | None:
    normalized = _normalize_provider(provider)
    if normalized is None:
        return None
    return _PROVIDER_CATALOG.get(normalized)


def _normalize_provider(provider: str | None) -> str | None:
    if provider is None:
        return None
    normalized = provider.strip().lower()
    return normalized if normalized in _PROVIDER_CATALOG else None


def _sorted_prefixes(catalog: _ProviderCatalog) -> tuple[str, ...]:
    return tuple(sorted(catalog.prefixes))


def _model_has_prefix(model: str, prefix: str) -> bool:
    normalized_prefix = prefix.strip().lower().rstrip("/")
    return model == normalized_prefix or model.startswith(f"{normalized_prefix}/")
