from ..config import settings
from .anthropic_provider import AnthropicProvider
from .base import LLMProvider
from .fallback_provider import FallbackProvider
from .openai_provider import OpenAICompatibleProvider


def _build_openai(model: str | None = None) -> LLMProvider:
    return OpenAICompatibleProvider(
        api_key=settings.openai_api_key,
        model=model or settings.openai_model,
    )


def _build_anthropic(model: str | None = None) -> LLMProvider:
    return AnthropicProvider(
        api_key=settings.anthropic_api_key,
        model=model or settings.anthropic_model,
    )


def _build_ollama(model: str | None = None) -> LLMProvider:
    return OpenAICompatibleProvider(
        api_key=settings.ollama_api_key,
        model=model or settings.ollama_model,
        base_url=settings.ollama_base_url,
        use_json_mode=False,
    )


def _build_gemini(model: str | None = None) -> LLMProvider:
    return OpenAICompatibleProvider(
        api_key=settings.gemini_api_key,
        model=model or settings.gemini_model,
        base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
    )


_BUILDERS: dict[str, object] = {
    "openai": _build_openai,
    "anthropic": _build_anthropic,
    "ollama": _build_ollama,
    "gemini": _build_gemini,
}


def _has_credentials(name: str, is_primary: bool) -> bool:
    if name == "openai":
        return bool(settings.openai_api_key)
    if name == "anthropic":
        return bool(settings.anthropic_api_key)
    if name == "gemini":
        return bool(settings.gemini_api_key)
    if name == "ollama":
        # Ollama has no credit concept and no key. Only include it when explicitly
        # chosen as primary — adding it as an automatic fallback would cause a
        # "connection refused" crash (not a credit error) and abort the chain.
        return is_primary
    return False


def get_single_provider(name: str) -> LLMProvider:
    """Return a single provider by name (no fallback chain).
    Raises ValueError if the name is unknown or credentials are missing.
    """
    if name not in _BUILDERS:
        raise ValueError(f"Unknown provider '{name}' — expected one of: {', '.join(_BUILDERS)}")
    if not _has_credentials(name, is_primary=True):
        raise ValueError(f"Provider '{name}' has no credentials configured")
    return _BUILDERS[name]()


def list_providers() -> list[dict]:
    """Return metadata for all configured (credentialed) providers."""
    meta = {
        "gemini":    {"name": "Gemini 2.5 Flash Lite", "model": settings.gemini_model},
        "openai":    {"name": "GPT-4o Mini",           "model": settings.openai_model},
        "anthropic": {"name": "Claude Haiku",          "model": settings.anthropic_model},
        "ollama":    {"name": "Ollama (local)",         "model": settings.ollama_model},
    }
    result = []
    for provider_id, builder_meta in meta.items():
        if _has_credentials(provider_id, is_primary=(provider_id == settings.llm_provider)):
            result.append({
                "id": provider_id,
                "name": builder_meta["name"],
                "model": builder_meta["model"],
                "is_default": provider_id == settings.llm_provider,
            })
    return result


def get_provider_starting_with(primary: str, model_override: str | None = None) -> FallbackProvider:
    """Like get_provider() but promotes `primary` to the front of the fallback chain.

    `model_override` applies to the primary provider only — if the user picked
    "Gemini 2.0 Flash" we honour that exact model; fallback providers use their
    own configured defaults so they work without the caller knowing their models.
    """
    if primary not in _BUILDERS:
        raise ValueError(f"Unknown provider '{primary}' — expected one of: {', '.join(_BUILDERS)}")

    fallback_order = ["gemini", "openai", "anthropic", "ollama"]
    ordered = [primary] + [p for p in fallback_order if p != primary]

    providers: list[tuple[str, LLMProvider]] = []
    for name in ordered:
        if _has_credentials(name, is_primary=(name == primary)):
            model = model_override if name == primary else None
            providers.append((name, _BUILDERS[name](model)))

    return FallbackProvider(providers)


def get_provider() -> FallbackProvider:
    """Build a FallbackProvider that tries LLM_PROVIDER first, then any other
    provider that has credentials configured, in a fixed fallback order.

    Order: primary (LLM_PROVIDER) → gemini → openai → anthropic → ollama
    (primary is moved to front; duplicates skipped; Gemini leads the fallback
    chain because it is free-tier friendly).
    """
    primary = settings.llm_provider

    if primary not in _BUILDERS:
        raise ValueError(
            f"Unknown LLM_PROVIDER '{primary}' — expected 'openai', 'anthropic', 'gemini', or 'ollama'"
        )

    fallback_order = ["gemini", "openai", "anthropic", "ollama"]
    ordered = [primary] + [p for p in fallback_order if p != primary]

    providers: list[tuple[str, LLMProvider]] = []
    for name in ordered:
        if _has_credentials(name, is_primary=(name == primary)):
            providers.append((name, _BUILDERS[name]()))

    return FallbackProvider(providers)
