from __future__ import annotations

from typing import Any, cast

from fastapi import HTTPException, status
from langchain_core.language_models.chat_models import BaseChatModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.llm import Model, ModelCategoryKey, ModelSettings, Provider
from app.services.common import entity_not_found


def _settings_model_id(settings_row: ModelSettings | None, category: ModelCategoryKey) -> str | None:
    if settings_row is None:
        return None
    if category == ModelCategoryKey.text:
        return settings_row.default_text_model_id
    if category == ModelCategoryKey.image:
        return settings_row.default_image_model_id
    return settings_row.default_video_model_id


async def get_provider_by_id_or_obj(db: AsyncSession, provider_or_id: Provider | str) -> Provider:
    """通过 Provider 实体或 provider_id 获取 Provider。"""
    return await _resolve_provider(db, provider_or_id)


async def get_model_by_category(
    db: AsyncSession,
    category: ModelCategoryKey,
    *,
    model_or_id: Model | str | None = None,
    allow_default_fallback: bool = True,
) -> Model:
    """按类别解析模型，可传入显式模型（或 id），也可从默认设置解析。"""
    if model_or_id is not None:
        model = await _resolve_model(db, model_or_id)
        if model.category != category:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail=f"Configured model category mismatch: {model.id} (category={model.category})",
            )
        return model

    settings_row = await db.get(ModelSettings, 1)
    settings_model_id = _settings_model_id(settings_row, category)
    if settings_model_id:
        try:
            return await get_model_by_category(db, category, model_or_id=settings_model_id)
        except HTTPException as e:
            if e.status_code == status.HTTP_404_NOT_FOUND:
                raise HTTPException(
                    status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                    detail=f"Configured default model not found: {settings_model_id}",
                ) from e
            raise

    if not allow_default_fallback:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"No default model configured for category={category.value}",
        )

    stmt = (
        select(Model)
        .where(Model.category == category, Model.is_default.is_(True))
        .order_by(Model.updated_at.desc())
        .limit(1)
    )
    model = (await db.execute(stmt)).scalars().first()
    if model is None:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"No default model configured for category={category.value}",
        )
    return model


async def get_default_model_by_category(db: AsyncSession, category: ModelCategoryKey) -> Model:
    """按类别解析默认模型，优先读取单例 ModelSettings，缺失时回退 is_default。"""
    return await get_model_by_category(db, category, allow_default_fallback=True)


async def _resolve_model(db: AsyncSession, model_or_id: Model | str) -> Model:
    if not isinstance(model_or_id, str):
        return model_or_id
    model = await db.get(Model, model_or_id)
    if model is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=entity_not_found("Model"))
    return model if isinstance(model, Model) else cast(Model, cast(Any, model))


async def _resolve_provider(db: AsyncSession, provider_or_id: Provider | str) -> Provider:
    if not isinstance(provider_or_id, str):
        return provider_or_id
    provider = await db.get(Provider, provider_or_id)
    if provider is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=entity_not_found("Provider"))
    return provider if isinstance(provider, Provider) else cast(Provider, cast(Any, provider))


async def get_provider_by_model_or_id(db: AsyncSession, model_or_id: Model | str) -> Provider:
    """通过 Model 实体或 model_id 获取 Provider。"""
    model = await _resolve_model(db, model_or_id)
    try:
        provider = await get_provider_by_id_or_obj(db, model.provider_id)
    except HTTPException as e:
        if e.status_code == status.HTTP_404_NOT_FOUND:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail=f"Provider not found for model_id={model.id}",
            ) from e
        raise
    return provider


async def build_chat_model_from_provider(
    db: AsyncSession,
    provider_or_id: Provider | str,
) -> BaseChatModel:
    """根据 Provider 配置构造文本对话模型（ChatOpenAI）。"""
    provider = await _resolve_provider(db, provider_or_id)

    stmt = (
        select(Model)
        .where(Model.provider_id == provider.id, Model.category == ModelCategoryKey.text)
        .order_by(Model.is_default.desc(), Model.updated_at.desc())
        .limit(1)
    )
    model = (await db.execute(stmt)).scalars().first()
    if model is None:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"No text model configured for provider_id={provider.id}",
        )

    api_key = (provider.api_key or "").strip()
    if not api_key:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"Provider api_key is empty for provider_id={provider.id}",
        )

    try:
        from langchain_openai import ChatOpenAI
    except ImportError as e:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Install langchain-openai to build chat model from provider config",
        ) from e

    kwargs: dict[str, Any] = dict(model.params or {})
    kwargs["model"] = model.name
    kwargs["api_key"] = api_key
    kwargs.setdefault("temperature", 0)

    base_url = (provider.base_url or "").strip()
    if base_url:
        kwargs.setdefault("base_url", base_url)

    return ChatOpenAI(**kwargs)
