from __future__ import annotations

from dataclasses import dataclass
from typing import Literal, cast

from sqlalchemy import desc, select
from sqlalchemy.orm import Session

from app.models.ai_config import ModelConfig, SystemConfig

ModelScenario = Literal["report", "summary", "fixed_info", "simple_knowledge", "deep_chat"]
ChatRoutingMode = Literal["off", "fixed_only", "conservative"]

SCENARIO_LABELS: dict[ModelScenario, str] = {
    "report": "周期报告",
    "summary": "摘要沉淀",
    "fixed_info": "固定信息",
    "simple_knowledge": "简单知识问答",
    "deep_chat": "深度对话",
}

_SCENARIO_FIELDS = {
    "report": ModelConfig.allow_report,
    "summary": ModelConfig.allow_summary,
    "fixed_info": ModelConfig.allow_fixed_info,
    "simple_knowledge": ModelConfig.allow_simple_knowledge,
    "deep_chat": ModelConfig.allow_deep_chat,
}

_SIMPLE_KNOWLEDGE_TYPES = {"course", "qa", "general"}
_SIMPLE_QUERY_MARKERS = (
    "是什么",
    "有哪些",
    "有什么",
    "包括什么",
    "定义",
    "含义",
    "区别",
    "时间",
    "几点",
    "在哪里",
    "多少",
    "多久",
    "步骤",
    "怎么操作",
    "怎么练",
    "注意事项",
    "作业",
    "功课",
)
_DEEP_QUERY_MARKERS = (
    "我",
    "自己",
    "感觉",
    "感受",
    "情绪",
    "身体",
    "害怕",
    "担心",
    "焦虑",
    "难受",
    "痛苦",
    "卡住",
    "怎么办",
    "为什么",
    "关系",
    "孩子",
    "父母",
    "伴侣",
    "创伤",
    "建议",
    "分析",
    "帮我",
    "适合我",
    "对不对",
    "确认一下",
)


@dataclass(frozen=True)
class ModelRoute:
    model: ModelConfig | None
    scenario: ModelScenario
    reason: str
    fallback_used: bool
    question_type: str | None = None


class ModelRoutingService:
    """Centralizes deterministic, auditable model selection and safe fallback."""

    @staticmethod
    def default_model(db: Session) -> ModelConfig | None:
        model = db.scalar(
            select(ModelConfig)
            .where(ModelConfig.enabled == 1, ModelConfig.is_default == 1)
            .order_by(ModelConfig.id.desc())
            .limit(1)
        )
        if model is not None:
            return model
        # Migration/partial rollout safety: old databases may have an available
        # model before one has been explicitly marked as default.
        return db.scalar(
            select(ModelConfig)
            .where(ModelConfig.enabled == 1)
            .order_by(ModelConfig.id.desc())
            .limit(1)
        )

    @classmethod
    def resolve(cls, db: Session, scenario: ModelScenario) -> ModelRoute:
        default = cls.default_model(db)
        capability = _SCENARIO_FIELDS[scenario]
        candidate = db.scalar(
            select(ModelConfig)
            .where(ModelConfig.enabled == 1, capability == 1)
            .order_by(desc(ModelConfig.is_default), ModelConfig.id.desc())
            .limit(1)
        )
        label = SCENARIO_LABELS[scenario]
        if candidate is not None:
            source = "默认主模型" if candidate.is_default == 1 else "场景可用模型"
            return ModelRoute(
                model=candidate,
                scenario=scenario,
                reason=f"场景分流：{label}；选择{source}",
                fallback_used=False,
            )
        if default is not None:
            return ModelRoute(
                model=default,
                scenario=scenario,
                reason=f"场景分流：{label}无匹配模型；回退默认主模型",
                fallback_used=True,
            )
        return ModelRoute(
            model=None,
            scenario=scenario,
            reason=f"场景分流：{label}无可用模型",
            fallback_used=True,
        )

    @classmethod
    def resolve_chat(
        cls,
        db: Session,
        knowledge_types: list[str],
        question: str = "",
    ) -> ModelRoute:
        normalized_types = {item.strip().lower() for item in knowledge_types if item and item.strip()}
        mode = cls.chat_routing_mode(db)
        if normalized_types == {"fixed"}:
            if mode == "off":
                return ModelRoute(
                    model=cls.default_model(db),
                    scenario="deep_chat",
                    reason="正式聊天：仅召回固定信息类知识库，但正式问答模型分流开关已关闭；使用默认主模型",
                    fallback_used=False,
                    question_type="fixed_info",
                )
            route = cls.resolve(db, "fixed_info")
            return ModelRoute(
                model=route.model,
                scenario=route.scenario,
                reason=f"正式聊天：仅召回固定信息类知识库；{route.reason}",
                fallback_used=route.fallback_used,
                question_type="fixed_info",
            )

        if mode == "conservative" and cls._is_simple_knowledge_query(question, normalized_types):
            route = cls.resolve(db, "simple_knowledge")
            return ModelRoute(
                model=route.model,
                scenario=route.scenario,
                reason=f"正式聊天：命中知识库且符合保守简单知识规则；{route.reason}",
                fallback_used=route.fallback_used,
                question_type="simple_knowledge",
            )

        default = cls.default_model(db)
        if not normalized_types:
            reason = "正式聊天：未召回知识库；使用默认主模型"
            question_type = "general_chat"
        elif "fixed" in normalized_types:
            reason = "正式聊天：混合类型知识库召回；为保证综合判断使用默认主模型"
            question_type = "knowledge_grounded"
        else:
            reason = "正式聊天：非固定信息类知识召回；使用默认主模型"
            question_type = "knowledge_grounded"
        return ModelRoute(
            model=default,
            scenario="deep_chat",
            reason=reason if default is not None else f"{reason}，但当前无可用模型",
            fallback_used=False,
            question_type=question_type,
        )

    @classmethod
    def chat_routing_mode(cls, db: Session) -> ChatRoutingMode:
        value = cls._config_value(db, "chat_model_routing_mode")
        if value in {"off", "fixed_only", "conservative"}:
            return cast(ChatRoutingMode, value)
        # Compatibility with the previous emergency switch.
        return "fixed_only" if cls._config_bool(db, "fixed_info_model_routing_enabled", True) else "off"

    @staticmethod
    def _is_simple_knowledge_query(question: str, knowledge_types: set[str]) -> bool:
        normalized_question = "".join(question.split()).lower()
        if not normalized_question or len(normalized_question) > 80:
            return False
        if not knowledge_types or not knowledge_types.issubset(_SIMPLE_KNOWLEDGE_TYPES):
            return False
        if any(marker in normalized_question for marker in _DEEP_QUERY_MARKERS):
            return False
        return any(marker in normalized_question for marker in _SIMPLE_QUERY_MARKERS)

    @staticmethod
    def _config_bool(db: Session, key: str, default: bool) -> bool:
        value = ModelRoutingService._config_value(db, key)
        if value is None:
            return default
        return value.lower() in {"1", "true", "yes", "on", "启用"}

    @staticmethod
    def _config_value(db: Session, key: str) -> str | None:
        config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == key))
        if config is None or not config.config_value.strip():
            return None
        return config.config_value.strip()
