"""Runtime-configurable sentiment provider.

Lets an admin switch the sentiment LLM (provider, model, API key) at runtime
via SystemSetting, without a redeploy. Precedence for each value:
    DB setting  >  environment variable  >  hardcoded default.

The API key is write-only: it is persisted but never returned through any
read path. Reads report only whether a key is configured and its source.
"""

from __future__ import annotations

import logging

from sqlalchemy.ext.asyncio import AsyncSession

from app.config import settings
from app.exceptions import ProviderError, ValidationError
from app.services import settings_store
from app.services.admin_service import update_setting

logger = logging.getLogger(__name__)

VALID_PROVIDERS = {"openai", "gemini", "deepseek", "xai", "openai_compatible"}

PROVIDER_DEFAULT_MODELS: dict[str, str] = {
    "openai": "gpt-4o-mini",
    "gemini": "gemini-2.0-flash",
    "deepseek": "deepseek-chat",
    "xai": "grok-3",
    "openai_compatible": "",
}

# Fixed base URLs for named OpenAI-compatible providers.
PROVIDER_BASE_URLS: dict[str, str] = {
    "deepseek": "https://api.deepseek.com",
    "xai": "https://api.x.ai/v1",
}

# Providers grounded in live web search. The rest score from model knowledge.
# xAI and OpenAI ground via the Responses API web-search tool; Gemini via its
# own search grounding.
WEB_SEARCH_PROVIDERS = {"openai", "gemini", "xai"}

# Providers needing a user-supplied base URL (generic compatible endpoints).
CUSTOM_BASE_URL_PROVIDERS = {"openai_compatible"}

# SystemSetting keys
KEY_PROVIDER = "sentiment_provider"
KEY_MODEL = "sentiment_model"
KEY_API_KEY = "sentiment_api_key"
KEY_BASE_URL = "sentiment_base_url"


def _env_key_for(provider: str) -> str:
    if provider == "openai":
        return settings.openai_api_key or ""
    if provider == "gemini":
        return settings.gemini_api_key or ""
    if provider == "deepseek":
        return getattr(settings, "deepseek_api_key", "") or ""
    if provider == "xai":
        return getattr(settings, "xai_api_key", "") or ""
    return ""


def _env_model_for(provider: str) -> str:
    if provider == "openai":
        return settings.openai_model or PROVIDER_DEFAULT_MODELS["openai"]
    if provider == "gemini":
        return settings.gemini_model or PROVIDER_DEFAULT_MODELS["gemini"]
    return PROVIDER_DEFAULT_MODELS.get(provider, "")


def _base_url_for(provider: str, stored_base_url: str) -> str:
    if provider in PROVIDER_BASE_URLS:
        return PROVIDER_BASE_URLS[provider]
    return stored_base_url


async def _resolve(db: AsyncSession) -> dict:
    """Resolve effective config from DB > env > default."""
    stored = await settings_store.get_map(db, [KEY_PROVIDER, KEY_MODEL, KEY_API_KEY, KEY_BASE_URL])

    provider = (stored.get(KEY_PROVIDER) or "").strip().lower()
    if provider not in VALID_PROVIDERS:
        provider = "openai" if settings.openai_api_key or not settings.gemini_api_key else "gemini"

    model = (stored.get(KEY_MODEL) or "").strip() or _env_model_for(provider)
    base_url = _base_url_for(provider, (stored.get(KEY_BASE_URL) or "").strip())

    db_key = (stored.get(KEY_API_KEY) or "").strip()
    if db_key:
        api_key, source = db_key, "database"
    elif _env_key_for(provider):
        api_key, source = _env_key_for(provider), "environment"
    else:
        api_key, source = "", "none"

    return {
        "provider": provider,
        "model": model,
        "base_url": base_url,
        "api_key": api_key,
        "api_key_source": source,
    }


async def get_sentiment_config(db: AsyncSession) -> dict:
    """Public config — never includes the raw API key."""
    r = await _resolve(db)
    return {
        "provider": r["provider"],
        "model": r["model"],
        "base_url": r["base_url"],
        "api_key_configured": bool(r["api_key"]),
        "api_key_source": r["api_key_source"],
        "web_search": r["provider"] in WEB_SEARCH_PROVIDERS,
        "valid_providers": sorted(VALID_PROVIDERS),
        "default_models": PROVIDER_DEFAULT_MODELS,
        "web_search_providers": sorted(WEB_SEARCH_PROVIDERS),
        "custom_base_url_providers": sorted(CUSTOM_BASE_URL_PROVIDERS),
    }


async def update_sentiment_config(
    db: AsyncSession,
    provider: str | None = None,
    model: str | None = None,
    api_key: str | None = None,
    base_url: str | None = None,
) -> dict:
    """Persist config. An empty/omitted api_key leaves the stored key untouched."""
    if provider is not None:
        provider = provider.strip().lower()
        if provider not in VALID_PROVIDERS:
            raise ValidationError(
                f"Unknown sentiment provider '{provider}'. Valid: {', '.join(sorted(VALID_PROVIDERS))}"
            )
        await update_setting(db, KEY_PROVIDER, provider)

    if model is not None:
        model = model.strip()
        if model:
            await update_setting(db, KEY_MODEL, model)

    if base_url is not None:
        await update_setting(db, KEY_BASE_URL, base_url.strip())

    if api_key:  # only overwrite when a non-empty key is supplied
        await update_setting(db, KEY_API_KEY, api_key.strip())

    return await get_sentiment_config(db)


async def build_sentiment_provider(db: AsyncSession):
    """Construct the active sentiment provider from current config.

    Raises ProviderError if credentials/base_url are missing.
    """
    r = await _resolve(db)
    provider, model, base_url, api_key = r["provider"], r["model"], r["base_url"], r["api_key"]
    if not api_key:
        raise ProviderError(f"No API key configured for sentiment provider '{provider}'")

    if provider == "openai":
        from app.providers.openai_sentiment import OpenAISentimentProvider
        return OpenAISentimentProvider(api_key, model)
    if provider == "gemini":
        from app.providers.gemini_sentiment import GeminiSentimentProvider
        return GeminiSentimentProvider(api_key, model)
    if provider == "xai":
        # xAI grounds via the Responses API web_search tool (the former Live
        # Search / search_parameters API is deprecated).
        from app.providers.openai_sentiment import OpenAISentimentProvider
        return OpenAISentimentProvider(
            api_key, model, base_url=base_url, tool_type="web_search", source="xai",
        )
    if provider in {"deepseek", "openai_compatible"}:
        if not base_url:
            raise ProviderError(f"No base_url configured for sentiment provider '{provider}'")
        from app.providers.openai_compatible_sentiment import OpenAICompatibleSentimentProvider
        return OpenAICompatibleSentimentProvider(api_key, model, base_url, source=provider)

    raise ProviderError(f"Unsupported sentiment provider '{provider}'")


async def test_sentiment_provider(db: AsyncSession, ticker: str = "AAPL") -> dict:
    """Build the active provider and fetch one ticker as a live credentials check."""
    r = await _resolve(db)
    try:
        prov = await build_sentiment_provider(db)
        data = await prov.fetch_sentiment(ticker.strip().upper())
        return {
            "ok": True,
            "provider": r["provider"],
            "model": r["model"],
            "ticker": ticker.strip().upper(),
            "classification": data.classification,
            "confidence": data.confidence,
            "reasoning": data.reasoning or None,
        }
    except Exception as exc:
        logger.warning("Sentiment provider test failed: %s", exc)
        return {
            "ok": False,
            "provider": r["provider"],
            "model": r["model"],
            "error": str(exc),
        }
