"""
Scene Slot Bundle Loader Service.
[AC-SCENE-SLOT-02] 运行时场景槽位包加载器
[AC-SCENE-SLOT-03] 支持缓存层

职责：
1. 根据场景标识加载槽位包配置
2. 聚合槽位定义详情
3. 计算缺失槽位
4. 生成追问提示
"""

import logging
from dataclasses import dataclass, field
from typing import Any

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.entities import SceneSlotBundleStatus
from app.services.scene_slot_bundle_service import SceneSlotBundleService
from app.services.slot_definition_service import SlotDefinitionService
from app.services.cache.scene_slot_bundle_cache import (
    CachedSceneSlotBundle,
    get_scene_slot_bundle_cache,
)

logger = logging.getLogger(__name__)


@dataclass
class SlotInfo:
    """槽位信息"""
    slot_key: str
    type: str
    required: bool
    ask_back_prompt: str | None = None
    validation_rule: str | None = None
    linked_field_id: str | None = None
    default_value: Any = None


@dataclass
class SceneSlotContext:
    """
    [AC-SCENE-SLOT-02] 场景槽位上下文
    
    运行时使用的场景槽位包信息
    """
    scene_key: str
    scene_name: str
    required_slots: list[SlotInfo] = field(default_factory=list)
    optional_slots: list[SlotInfo] = field(default_factory=list)
    slot_priority: list[str] = field(default_factory=list)
    completion_threshold: float = 1.0
    ask_back_order: str = "priority"
    
    def get_all_slot_keys(self) -> list[str]:
        """获取所有槽位键名"""
        return [s.slot_key for s in self.required_slots] + [s.slot_key for s in self.optional_slots]
    
    def get_required_slot_keys(self) -> list[str]:
        """获取必填槽位键名"""
        return [s.slot_key for s in self.required_slots]
    
    def get_optional_slot_keys(self) -> list[str]:
        """获取可选槽位键名"""
        return [s.slot_key for s in self.optional_slots]
    
    def get_missing_slots(
        self,
        filled_slots: dict[str, Any],
    ) -> list[dict[str, Any]]:
        """
        获取缺失的必填槽位信息
        
        Args:
            filled_slots: 已填充的槽位值
            
        Returns:
            缺失槽位信息列表
        """
        missing = []
        
        for slot_info in self.required_slots:
            if slot_info.slot_key not in filled_slots:
                missing.append({
                    "slot_key": slot_info.slot_key,
                    "type": slot_info.type,
                    "required": True,
                    "ask_back_prompt": slot_info.ask_back_prompt,
                    "validation_rule": slot_info.validation_rule,
                    "linked_field_id": slot_info.linked_field_id,
                })
        
        return missing
    
    def get_ordered_missing_slots(
        self,
        filled_slots: dict[str, Any],
    ) -> list[dict[str, Any]]:
        """
        按优先级顺序获取缺失的必填槽位
        
        Args:
            filled_slots: 已填充的槽位值
            
        Returns:
            按优先级排序的缺失槽位信息列表
        """
        missing = self.get_missing_slots(filled_slots)
        
        if not missing:
            return []
        
        if self.ask_back_order == "required_first":
            return missing
        
        if self.ask_back_order == "priority" and self.slot_priority:
            priority_map = {slot_key: idx for idx, slot_key in enumerate(self.slot_priority)}
            missing.sort(key=lambda x: priority_map.get(x["slot_key"], 999))
        
        return missing
    
    def get_completion_ratio(
        self,
        filled_slots: dict[str, Any],
    ) -> float:
        """
        计算完成比例
        
        Args:
            filled_slots: 已填充的槽位值
            
        Returns:
            完成比例 (0.0 - 1.0)
        """
        if not self.required_slots:
            return 1.0
        
        filled_count = sum(
            1 for slot_info in self.required_slots
            if slot_info.slot_key in filled_slots
        )
        
        return filled_count / len(self.required_slots)
    
    def is_complete(
        self,
        filled_slots: dict[str, Any],
    ) -> bool:
        """
        检查是否完成
        
        Args:
            filled_slots: 已填充的槽位值
            
        Returns:
            是否达到完成阈值
        """
        return self.get_completion_ratio(filled_slots) >= self.completion_threshold


class SceneSlotBundleLoader:
    """
    [AC-SCENE-SLOT-02] 场景槽位包加载器
    [AC-SCENE-SLOT-03] 支持缓存层
    
    运行时加载场景槽位包配置
    """
    
    def __init__(self, session: AsyncSession, use_cache: bool = True):
        self._session = session
        self._bundle_service = SceneSlotBundleService(session)
        self._slot_service = SlotDefinitionService(session)
        self._use_cache = use_cache
        self._cache = get_scene_slot_bundle_cache() if use_cache else None
    
    async def load_scene_context(
        self,
        tenant_id: str,
        scene_key: str,
    ) -> SceneSlotContext | None:
        """
        加载场景槽位上下文
        
        Args:
            tenant_id: 租户 ID
            scene_key: 场景标识
            
        Returns:
            场景槽位上下文或 None
        """
        # [AC-SCENE-SLOT-03] 尝试从缓存获取
        if self._use_cache and self._cache:
            cached = await self._cache.get(tenant_id, scene_key)
            if cached and cached.status == SceneSlotBundleStatus.ACTIVE.value:
                logger.debug(
                    f"[AC-SCENE-SLOT-03] Cache hit for scene: {scene_key}"
                )
                return await self._build_context_from_cached(tenant_id, cached)
        
        bundle = await self._bundle_service.get_active_bundle_by_scene(
            tenant_id=tenant_id,
            scene_key=scene_key,
        )
        
        if not bundle:
            logger.debug(
                f"[AC-SCENE-SLOT-02] No active bundle found for scene: {scene_key}"
            )
            return None
        
        # [AC-SCENE-SLOT-03] 写入缓存
        if self._use_cache and self._cache:
            cached_bundle = CachedSceneSlotBundle(
                scene_key=bundle.scene_key,
                scene_name=bundle.scene_name,
                description=bundle.description,
                required_slots=bundle.required_slots,
                optional_slots=bundle.optional_slots,
                slot_priority=bundle.slot_priority,
                completion_threshold=bundle.completion_threshold,
                ask_back_order=bundle.ask_back_order,
                status=bundle.status,
                version=bundle.version,
            )
            await self._cache.set(tenant_id, scene_key, cached_bundle)
        
        return await self._build_context_from_bundle(tenant_id, bundle)
    
    async def _build_context_from_cached(
        self,
        tenant_id: str,
        cached: CachedSceneSlotBundle,
    ) -> SceneSlotContext:
        """从缓存构建场景槽位上下文"""
        all_slots = await self._slot_service.list_slot_definitions(tenant_id)
        slot_map = {slot.slot_key: slot for slot in all_slots}
        
        return self._build_context(cached, slot_map)
    
    async def _build_context_from_bundle(
        self,
        tenant_id: str,
        bundle: Any,
    ) -> SceneSlotContext:
        """从数据库模型构建场景槽位上下文"""
        all_slots = await self._slot_service.list_slot_definitions(tenant_id)
        slot_map = {slot.slot_key: slot for slot in all_slots}
        
        cached = CachedSceneSlotBundle(
            scene_key=bundle.scene_key,
            scene_name=bundle.scene_name,
            description=bundle.description,
            required_slots=bundle.required_slots,
            optional_slots=bundle.optional_slots,
            slot_priority=bundle.slot_priority,
            completion_threshold=bundle.completion_threshold,
            ask_back_order=bundle.ask_back_order,
            status=bundle.status,
            version=bundle.version,
        )
        
        return self._build_context(cached, slot_map)
    
    def _build_context(
        self,
        cached: CachedSceneSlotBundle,
        slot_map: dict[str, Any],
    ) -> SceneSlotContext:
        """构建场景槽位上下文"""
        required_slot_infos = []
        for slot_key in cached.required_slots:
            if slot_key in slot_map:
                slot_def = slot_map[slot_key]
                required_slot_infos.append(SlotInfo(
                    slot_key=slot_def.slot_key,
                    type=slot_def.type,
                    required=True,
                    ask_back_prompt=slot_def.ask_back_prompt,
                    validation_rule=slot_def.validation_rule,
                    linked_field_id=str(slot_def.linked_field_id) if slot_def.linked_field_id else None,
                    default_value=slot_def.default_value,
                ))
            else:
                logger.warning(
                    f"[AC-SCENE-SLOT-02] Required slot not found: {slot_key}"
                )
        
        optional_slot_infos = []
        for slot_key in cached.optional_slots:
            if slot_key in slot_map:
                slot_def = slot_map[slot_key]
                optional_slot_infos.append(SlotInfo(
                    slot_key=slot_def.slot_key,
                    type=slot_def.type,
                    required=slot_def.required,
                    ask_back_prompt=slot_def.ask_back_prompt,
                    validation_rule=slot_def.validation_rule,
                    linked_field_id=str(slot_def.linked_field_id) if slot_def.linked_field_id else None,
                    default_value=slot_def.default_value,
                ))
            else:
                logger.warning(
                    f"[AC-SCENE-SLOT-02] Optional slot not found: {slot_key}"
                )
        
        context = SceneSlotContext(
            scene_key=cached.scene_key,
            scene_name=cached.scene_name,
            required_slots=required_slot_infos,
            optional_slots=optional_slot_infos,
            slot_priority=cached.slot_priority or [],
            completion_threshold=cached.completion_threshold,
            ask_back_order=cached.ask_back_order,
        )
        
        logger.info(
            f"[AC-SCENE-SLOT-02] Loaded scene context: scene={cached.scene_key}, "
            f"required={len(required_slot_infos)}, optional={len(optional_slot_infos)}, "
            f"threshold={cached.completion_threshold}"
        )
        
        return context
    
    async def invalidate_cache(
        self,
        tenant_id: str,
        scene_key: str,
    ) -> bool:
        """
        [AC-SCENE-SLOT-03] 使缓存失效
        
        Args:
            tenant_id: 租户 ID
            scene_key: 场景标识
            
        Returns:
            是否成功
        """
        if self._cache:
            return await self._cache.invalidate_on_update(tenant_id, scene_key)
        return True
    
    async def get_missing_slots_for_scene(
        self,
        tenant_id: str,
        scene_key: str,
        filled_slots: dict[str, Any],
    ) -> list[dict[str, Any]]:
        """
        获取场景缺失的必填槽位
        
        Args:
            tenant_id: 租户 ID
            scene_key: 场景标识
            filled_slots: 已填充的槽位值
            
        Returns:
            缺失槽位信息列表
        """
        context = await self.load_scene_context(tenant_id, scene_key)
        
        if not context:
            return []
        
        return context.get_ordered_missing_slots(filled_slots)
    
    async def generate_ask_back_prompt(
        self,
        tenant_id: str,
        scene_key: str,
        filled_slots: dict[str, Any],
        max_slots: int = 2,
    ) -> str | None:
        """
        生成追问提示
        
        Args:
            tenant_id: 租户 ID
            scene_key: 场景标识
            filled_slots: 已填充的槽位值
            max_slots: 最多追问的槽位数量
            
        Returns:
            追问提示或 None
        """
        missing_slots = await self.get_missing_slots_for_scene(
            tenant_id=tenant_id,
            scene_key=scene_key,
            filled_slots=filled_slots,
        )
        
        if not missing_slots:
            return None
        
        context = await self.load_scene_context(tenant_id, scene_key)
        ask_back_order = context.ask_back_order if context else "priority"
        
        if ask_back_order == "parallel":
            prompts = []
            for missing in missing_slots[:max_slots]:
                if missing.get("ask_back_prompt"):
                    prompts.append(missing["ask_back_prompt"])
                else:
                    slot_key = missing.get("slot_key", "相关信息")
                    prompts.append(f"您的{slot_key}")
            
            if len(prompts) == 1:
                return prompts[0]
            elif len(prompts) == 2:
                return f"为了更好地为您服务，请告诉我{prompts[0]}和{prompts[1]}。"
            else:
                all_but_last = "、".join(prompts[:-1])
                return f"为了更好地为您服务，请告诉我{all_but_last}，以及{prompts[-1]}。"
        else:
            first_missing = missing_slots[0]
            ask_back_prompt = first_missing.get("ask_back_prompt")
            if ask_back_prompt:
                return ask_back_prompt
            
            slot_key = first_missing.get("slot_key", "相关信息")
            return f"为了更好地为您提供帮助，请告诉我您的{slot_key}。"
