"""Model availability hints for tools that depend on provider add-ons."""

from __future__ import annotations

import os

from run_utils import _load_openswarm_dotenv

from shared_tools.openai_client_utils import get_caller_openai_credentials


def _refresh_runtime_env() -> None:
    """Reload add-on keys written through the TUI into the running process."""
    _load_openswarm_dotenv(override=True)


def _configured(value: bool) -> str:
    return "available" if value else "missing key / unavailable"


def direct_openai_available(tool=None) -> bool:
    """Return whether OpenAI media endpoints are usable for this tool call.

    Browser/Codex auth supplies an OpenAI-compatible client, but media endpoints
    such as Images and Videos are not supported through that base URL.
    """
    _refresh_runtime_env()
    creds = get_caller_openai_credentials(tool) if tool is not None else None
    if creds:
        return creds[1].startswith("https://api.openai.com")
    return bool(os.getenv("OPENAI_API_KEY"))


def google_available() -> bool:
    _refresh_runtime_env()
    return bool(os.getenv("GOOGLE_API_KEY"))


def fal_available() -> bool:
    _refresh_runtime_env()
    return bool(os.getenv("FAL_KEY"))


def deepseek_available() -> bool:
    _refresh_runtime_env()
    return bool(os.getenv("DEEPSEEK_API_KEY"))


def minimax_available() -> bool:
    _refresh_runtime_env()
    return bool(os.getenv("MINIMAX_API_KEY"))


def image_model_availability_message(tool=None, *, failed_requirement: str | None = None) -> str:
    lines = []
    if failed_requirement:
        lines.append(f"{failed_requirement}")
    lines.extend(
        [
            "",
            "Available image models/providers in this environment:",
            f"- gemini-2.5-flash-image: {_configured(google_available())} (requires GOOGLE_API_KEY add-on)",
            f"- gemini-3-pro-image-preview: {_configured(google_available())} (requires GOOGLE_API_KEY add-on)",
            f"- gpt-image-1.5: {_configured(direct_openai_available(tool))} (requires OpenAI API key auth; not Codex browser auth)",
            f"- background removal / Pixelcut: {_configured(fal_available())} (requires FAL_KEY add-on)",
            "",
            "If the requested model is unavailable, switch to an available model above or ask the user to run /auth and add the missing add-on key.",
        ]
    )
    return "\n".join(lines)


def video_model_availability_message(tool=None, *, failed_requirement: str | None = None) -> str:
    lines = []
    if failed_requirement:
        lines.append(f"{failed_requirement}")
    lines.extend(
        [
            "",
            "Available video models/providers in this environment:",
            f"- veo-3.1-generate-preview: {_configured(google_available())} (requires GOOGLE_API_KEY add-on)",
            f"- veo-3.1-fast-generate-preview: {_configured(google_available())} (requires GOOGLE_API_KEY add-on)",
            f"- seedance-1.5-pro: {_configured(fal_available())} (requires FAL_KEY add-on)",
            f"- sora-2: {_configured(direct_openai_available(tool))} (requires OpenAI API key auth; not Codex browser auth)",
            f"- sora-2-pro: {_configured(direct_openai_available(tool))} (requires OpenAI API key auth; not Codex browser auth)",
            "",
            "If the requested model is unavailable, switch to an available model above or ask the user to run /auth and add the missing add-on key.",
        ]
    )
    return "\n".join(lines)


def text_model_availability_message(tool=None, *, failed_requirement: str | None = None) -> str:
    lines = []
    if failed_requirement:
        lines.append(f"{failed_requirement}")
    lines.extend(
        [
            "",
            "Available text models/providers in this environment:",
            f"- Deepseek: {_configured(deepseek_available())} (requires DEEPSEEK_API_KEY add-on)",
            f"  · deepseek-v4-flash, deepseek-v4-pro, deepseek-reasoner",
            f"- Minimax: {_configured(minimax_available())} (requires MINIMAX_API_KEY add-on)",
            f"  · MiniMax-M3, MiniMax-M2.7, MiniMax-M2.7-highspeed, MiniMax-M2.5, MiniMax-M2.5-highspeed, MiniMax-M2.1, MiniMax-M2.1-highspeed, MiniMax-M2",
            f"- OpenAI (direct): {_configured(direct_openai_available(tool))} (requires OPENAI_API_KEY add-on)",
            f"- Ollama: available for local free models only (ollama run llama3, etc.)",
            f"- OpenRouter: available for free tier models only (`:free` suffix required)",
            "",
            "If the requested model is unavailable, switch to an available model above or ask the user to run /auth and add the missing add-on key.",
        ]
    )
    return "\n".join(lines)


# ── Free-tier gating for Ollama / OpenRouter ──────────────────────────────────
# These lists gate the model_selector to free models only. Users who set a
# non-free model manually via env var are not blocked (the underlying APIs
# would reject paid models anyway), but the model_selector will not offer them.

# Local Ollama models — anything on ollama.com/library is free.
# A representative list; the model selector can extend this with `ollama list`.
FREE_OLLAMA_MODELS = frozenset({
    "llama3", "llama3.1", "llama3.2", "llama3.3",
    "mistral", "mistral-nemo", "mixtral",
    "qwen2", "qwen2.5", "qwen3",
    "gemma2", "gemma3",
    "phi3", "phi3.5", "phi4",
    "codellama", "deepseek-coder-v2", "deepseek-r1",
    "command-r", "command-r-plus",
    "nomic-embed-text", "mxbai-embed-large",
})

# OpenRouter free-tier models — must end with `:free`.
FREE_OPENROUTER_SUFFIX = ":free"


def is_free_ollama_model(model: str) -> bool:
    """Return whether a bare Ollama model name (e.g. 'llama3') is free."""
    bare = model.split(":", 1)[0] if ":" in model else model
    return bare in FREE_OLLAMA_MODELS


def is_free_openrouter_model(model: str) -> bool:
    """Return whether an OpenRouter model ID is a free-tier model.

    OpenRouter's free tier uses the `:free` suffix on model IDs
    (e.g. `meta-llama/llama-3.3-70b-instruct:free`).
    """
    return model.endswith(FREE_OPENROUTER_SUFFIX)
