"""
Slot State Aggregator Service.
槽位状态聚合服务 - 统一维护本轮槽位状态

职责：
1. 聚合来自 memory_recall 的槽位值
2. 叠加本轮输入的槽位值
3. 识别缺失的必填槽位
4. 支持槽位与元数据字段的关联映射
5. 为 KB 检索过滤提供统一的槽位值来源
6. [AC-MRS-SLOT-CACHE-01] 多轮状态持久化

[AC-MRS-SLOT-META-01] 槽位与元数据关联机制
"""

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

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.mid.schemas import MemorySlot, SlotSource
from app.services.cache.slot_state_cache import (
    CachedSlotState,
    CachedSlotValue,
    get_slot_state_cache,
)
from app.services.mid.slot_manager import SlotManager
from app.services.slot_definition_service import SlotDefinitionService

logger = logging.getLogger(__name__)


@dataclass
class SlotState:
    """
    槽位状态聚合结果
    
    Attributes:
        filled_slots: 已填充的槽位值字典 {slot_key: value}
        missing_required_slots: 缺失的必填槽位列表
        slot_sources: 槽位值来源字典 {slot_key: source}
        slot_confidence: 槽位置信度字典 {slot_key: confidence}
        slot_to_field_map: 槽位到元数据字段的映射 {slot_key: field_key}
    """
    filled_slots: dict[str, Any] = field(default_factory=dict)
    missing_required_slots: list[dict[str, str]] = field(default_factory=list)
    slot_sources: dict[str, str] = field(default_factory=dict)
    slot_confidence: dict[str, float] = field(default_factory=dict)
    slot_to_field_map: dict[str, str] = field(default_factory=dict)

    def get_value_for_filter(self, field_key: str) -> Any:
        """
        获取用于 KB 过滤的字段值
        
        优先从 slot_to_field_map 反向查找
        """
        # 直接匹配
        if field_key in self.filled_slots:
            return self.filled_slots[field_key]
        
        # 通过 slot_to_field_map 反向查找
        for slot_key, mapped_field_key in self.slot_to_field_map.items():
            if mapped_field_key == field_key and slot_key in self.filled_slots:
                return self.filled_slots[slot_key]
        
        return None

    def to_debug_info(self) -> dict[str, Any]:
        """转换为调试信息字典"""
        return {
            "filled_slots": self.filled_slots,
            "missing_required_slots": self.missing_required_slots,
            "slot_sources": self.slot_sources,
            "slot_to_field_map": self.slot_to_field_map,
        }


class SlotStateAggregator:
    """
    [AC-MRS-SLOT-META-01] 槽位状态聚合器
    
    统一维护本轮槽位状态，支持：
    - 从 memory_recall 初始化槽位
    - 叠加本轮输入的槽位值
    - 识别缺失的必填槽位
    - 建立槽位与元数据字段的关联
    - [AC-MRS-SLOT-CACHE-01] 多轮状态持久化
    """

    def __init__(
        self,
        session: AsyncSession,
        tenant_id: str,
        slot_manager: SlotManager | None = None,
        session_id: str | None = None,
    ):
        self._session = session
        self._tenant_id = tenant_id
        self._session_id = session_id
        self._slot_manager = slot_manager or SlotManager(session=session)
        self._slot_def_service = SlotDefinitionService(session)
        self._cache = get_slot_state_cache()

    async def aggregate(
        self,
        memory_slots: dict[str, MemorySlot] | None = None,
        current_input_slots: dict[str, Any] | None = None,
        context: dict[str, Any] | None = None,
        use_cache: bool = True,
        scene_slot_context: Any = None,  # [AC-SCENE-SLOT-02] 场景槽位上下文
    ) -> SlotState:
        """
        聚合槽位状态
        
        执行流程：
        1. [AC-MRS-SLOT-CACHE-01] 从缓存加载已有状态
        2. 从 memory_slots 初始化已填充槽位
        3. 叠加 current_input_slots（优先级更高）
        4. 从 context 提取槽位值
        5. 识别缺失的必填槽位
        6. 建立槽位与元数据字段的关联映射
        7. [AC-MRS-SLOT-CACHE-01] 回写缓存
        
        Args:
            memory_slots: 从 memory_recall 召回的槽位
            current_input_slots: 本轮输入的槽位值
            context: 上下文信息，可能包含槽位值
            use_cache: 是否使用缓存（默认 True）
            
        Returns:
            SlotState: 聚合后的槽位状态
        """
        state = SlotState()
        
        # [AC-MRS-SLOT-CACHE-01] 1. 从缓存加载已有状态
        cached_state = None
        if use_cache and self._session_id:
            cached_state = await self._cache.get(self._tenant_id, self._session_id)
            if cached_state:
                for slot_key, cached_value in cached_state.filled_slots.items():
                    state.filled_slots[slot_key] = cached_value.value
                    state.slot_sources[slot_key] = cached_value.source
                    state.slot_confidence[slot_key] = cached_value.confidence
                state.slot_to_field_map = cached_state.slot_to_field_map.copy()
                logger.info(
                    f"[AC-MRS-SLOT-CACHE-01] Loaded from cache: "
                    f"tenant={self._tenant_id}, session={self._session_id}, "
                    f"slots={list(state.filled_slots.keys())}"
                )
        
        # 2. 从 memory_slots 初始化
        if memory_slots:
            for slot_key, memory_slot in memory_slots.items():
                state.filled_slots[slot_key] = memory_slot.value
                state.slot_sources[slot_key] = memory_slot.source.value
                state.slot_confidence[slot_key] = memory_slot.confidence
            logger.info(
                f"[AC-MRS-SLOT-META-01] Initialized from memory: "
                f"tenant={self._tenant_id}, slots={list(memory_slots.keys())}"
            )
        
        # 3. 叠加本轮输入（优先级更高）
        if current_input_slots:
            for slot_key, value in current_input_slots.items():
                if value is not None:
                    state.filled_slots[slot_key] = value
                    state.slot_sources[slot_key] = SlotSource.USER_CONFIRMED.value
                    state.slot_confidence[slot_key] = 1.0
            logger.info(
                f"[AC-MRS-SLOT-META-01] Merged current input: "
                f"tenant={self._tenant_id}, slots={list(current_input_slots.keys())}"
            )
        
        # 4. 从 context 提取槽位值（优先级最低）
        if context:
            context_slots = self._extract_slots_from_context(context)
            for slot_key, value in context_slots.items():
                if slot_key not in state.filled_slots and value is not None:
                    state.filled_slots[slot_key] = value
                    state.slot_sources[slot_key] = "context"
                    state.slot_confidence[slot_key] = 0.5
        
        # 5. 加载槽位定义并建立关联
        await self._build_slot_mappings(state)
        
        # 6. 识别缺失的必填槽位
        await self._identify_missing_required_slots(state, scene_slot_context)
        
        # [AC-MRS-SLOT-CACHE-01] 7. 回写缓存
        if use_cache and self._session_id:
            await self._save_to_cache(state)
        
        logger.info(
            f"[AC-MRS-SLOT-META-01] Slot state aggregated: "
            f"tenant={self._tenant_id}, filled={len(state.filled_slots)}, "
            f"missing={len(state.missing_required_slots)}"
        )
        
        return state
    
    async def _save_to_cache(self, state: SlotState) -> None:
        """
        [AC-MRS-SLOT-CACHE-01] 保存槽位状态到缓存
        """
        if not self._session_id:
            return
        
        cached_slots = {}
        for slot_key, value in state.filled_slots.items():
            source = state.slot_sources.get(slot_key, "unknown")
            confidence = state.slot_confidence.get(slot_key, 1.0)
            cached_slots[slot_key] = CachedSlotValue(
                value=value,
                source=source,
                confidence=confidence,
            )
        
        cached_state = CachedSlotState(
            filled_slots=cached_slots,
            slot_to_field_map=state.slot_to_field_map.copy(),
        )
        
        await self._cache.set(self._tenant_id, self._session_id, cached_state)
        logger.debug(
            f"[AC-MRS-SLOT-CACHE-01] Saved to cache: "
            f"tenant={self._tenant_id}, session={self._session_id}"
        )
    
    async def update_slot(
        self,
        slot_key: str,
        value: Any,
        source: str = "user_confirmed",
        confidence: float = 1.0,
    ) -> SlotState | None:
        """
        [AC-MRS-SLOT-CACHE-01] 更新单个槽位值并保存到缓存
        
        Args:
            slot_key: 槽位键名
            value: 槽位值
            source: 值来源
            confidence: 置信度
            
        Returns:
            更新后的槽位状态，如果没有 session_id 则返回 None
        """
        if not self._session_id:
            return None
        
        cached_value = CachedSlotValue(
            value=value,
            source=source,
            confidence=confidence,
        )
        
        cached_state = await self._cache.merge_and_set(
            tenant_id=self._tenant_id,
            session_id=self._session_id,
            new_slots={slot_key: cached_value},
        )
        
        state = SlotState()
        state.filled_slots = cached_state.get_simple_filled_slots()
        state.slot_sources = cached_state.get_slot_sources()
        state.slot_confidence = cached_state.get_slot_confidence()
        state.slot_to_field_map = cached_state.slot_to_field_map.copy()
        
        await self._identify_missing_required_slots(state)
        
        return state
    
    async def clear_slot(self, slot_key: str) -> bool:
        """
        [AC-MRS-SLOT-CACHE-01] 清除单个槽位值
        
        Args:
            slot_key: 槽位键名
            
        Returns:
            是否成功
        """
        if not self._session_id:
            return False
        
        return await self._cache.clear_slot(
            tenant_id=self._tenant_id,
            session_id=self._session_id,
            slot_key=slot_key,
        )
    
    async def clear_all_slots(self) -> bool:
        """
        [AC-MRS-SLOT-CACHE-01] 清除所有槽位状态
        
        Returns:
            是否成功
        """
        if not self._session_id:
            return False
        
        return await self._cache.delete(
            tenant_id=self._tenant_id,
            session_id=self._session_id,
        )

    def _extract_slots_from_context(self, context: dict[str, Any]) -> dict[str, Any]:
        """
        从上下文中提取可能的槽位值
        
        常见的槽位值可能存在于：
        - scene
        - product_line
        - region
        - grade
        等字段
        """
        slots = {}
        slot_candidates = [
            "scene", "product_line", "region", "grade", 
            "category", "type", "status", "priority"
        ]
        
        for key in slot_candidates:
            if key in context and context[key] is not None:
                slots[key] = context[key]
        
        return slots

    async def _build_slot_mappings(self, state: SlotState) -> None:
        """
        建立槽位与元数据字段的关联映射
        
        通过 linked_field_id 关联 SlotDefinition 和 MetadataFieldDefinition
        """
        try:
            # 获取所有槽位定义
            slot_defs = await self._slot_def_service.list_slot_definitions(
                tenant_id=self._tenant_id
            )
            
            for slot_def in slot_defs:
                if slot_def.linked_field_id:
                    # 获取关联的元数据字段
                    from app.services.metadata_field_definition_service import (
                        MetadataFieldDefinitionService
                    )
                    field_service = MetadataFieldDefinitionService(self._session)
                    linked_field = await field_service.get_field_definition(
                        tenant_id=self._tenant_id,
                        field_id=str(slot_def.linked_field_id)
                    )
                    
                    if linked_field:
                        state.slot_to_field_map[slot_def.slot_key] = linked_field.field_key
                        
                        # 检查类型一致性并告警
                        await self._check_type_consistency(
                            slot_def, linked_field, state
                        )
        except Exception as e:
            logger.warning(
                f"[AC-MRS-SLOT-META-01] Failed to build slot mappings: {e}"
            )

    async def _check_type_consistency(
        self,
        slot_def: Any,
        linked_field: Any,
        state: SlotState,
    ) -> None:
        """
        检查槽位与关联元数据字段的类型一致性
        
        当不一致时记录告警日志（不强拦截）
        """
        # 检查类型一致性
        if slot_def.type != linked_field.type:
            logger.warning(
                f"[AC-MRS-SLOT-META-01] Type mismatch: "
                f"slot='{slot_def.slot_key}' type={slot_def.type} vs "
                f"field='{linked_field.field_key}' type={linked_field.type}"
            )
        
        # 检查 required 一致性
        if slot_def.required != linked_field.required:
            logger.warning(
                f"[AC-MRS-SLOT-META-01] Required mismatch: "
                f"slot='{slot_def.slot_key}' required={slot_def.required} vs "
                f"field='{linked_field.field_key}' required={linked_field.required}"
            )
        
        # 检查 options 一致性（对于 enum/array_enum 类型）
        if slot_def.type in ["enum", "array_enum"]:
            slot_options = set(slot_def.options or [])
            field_options = set(linked_field.options or [])
            if slot_options != field_options:
                logger.warning(
                    f"[AC-MRS-SLOT-META-01] Options mismatch: "
                    f"slot='{slot_def.slot_key}' options={slot_options} vs "
                    f"field='{linked_field.field_key}' options={field_options}"
                )

    async def _identify_missing_required_slots(
        self,
        state: SlotState,
        scene_slot_context: Any = None,  # [AC-SCENE-SLOT-02] 场景槽位上下文
    ) -> None:
        """
        识别缺失的必填槽位
        
        基于 SlotDefinition 的 required 字段和 linked_field 的 required 字段
        [AC-SCENE-SLOT-02] 当有场景槽位上下文时，优先使用场景定义的必填槽位
        """
        try:
            # [AC-SCENE-SLOT-02] 如果有场景槽位上下文，使用场景定义的必填槽位
            if scene_slot_context:
                scene_required_keys = set(scene_slot_context.get_required_slot_keys())
                logger.info(
                    f"[AC-SCENE-SLOT-02] Using scene required slots: "
                    f"scene={scene_slot_context.scene_key}, "
                    f"required_keys={scene_required_keys}"
                )
                
                # 获取场景中定义的所有槽位
                all_slot_defs = await self._slot_def_service.list_slot_definitions(
                    tenant_id=self._tenant_id,
                )
                slot_def_map = {slot.slot_key: slot for slot in all_slot_defs}
                
                for slot_key in scene_required_keys:
                    if slot_key not in state.filled_slots:
                        slot_def = slot_def_map.get(slot_key)
                        ask_back_prompt = slot_def.ask_back_prompt if slot_def else None
                        
                        if not ask_back_prompt:
                            ask_back_prompt = f"请提供{slot_key}信息"
                        
                        missing_info = {
                            "slot_key": slot_key,
                            "label": slot_key,
                            "reason": "scene_required_slot_missing",
                            "ask_back_prompt": ask_back_prompt,
                            "scene": scene_slot_context.scene_key,
                        }
                        
                        if slot_def and slot_def.linked_field_id:
                            missing_info["linked_field_id"] = str(slot_def.linked_field_id)
                        
                        state.missing_required_slots.append(missing_info)
                
                return
            
            # 获取所有 required 的槽位定义
            required_slot_defs = await self._slot_def_service.list_slot_definitions(
                tenant_id=self._tenant_id,
                required=True,
            )
            
            for slot_def in required_slot_defs:
                if slot_def.slot_key not in state.filled_slots:
                    # 获取追问提示
                    ask_back_prompt = slot_def.ask_back_prompt
                    
                    # 如果没有配置追问提示，使用通用模板
                    if not ask_back_prompt:
                        ask_back_prompt = f"请提供{slot_def.slot_key}信息"
                    
                    missing_info = {
                        "slot_key": slot_def.slot_key,
                        "label": slot_def.slot_key,
                        "reason": "required_slot_missing",
                        "ask_back_prompt": ask_back_prompt,
                    }
                    
                    # 如果有关联字段，使用字段的 label
                    if slot_def.linked_field_id:
                        from app.services.metadata_field_definition_service import (
                            MetadataFieldDefinitionService
                        )
                        field_service = MetadataFieldDefinitionService(self._session)
                        linked_field = await field_service.get_field_definition(
                            tenant_id=self._tenant_id,
                            field_id=str(slot_def.linked_field_id)
                        )
                        if linked_field:
                            missing_info["label"] = linked_field.label
                            missing_info["field_key"] = linked_field.field_key
                    
                    state.missing_required_slots.append(missing_info)
                    
                    logger.info(
                        f"[AC-MRS-SLOT-META-01] Missing required slot: "
                        f"slot_key={slot_def.slot_key}"
                    )
        except Exception as e:
            logger.warning(
                f"[AC-MRS-SLOT-META-01] Failed to identify missing slots: {e}"
            )

    async def generate_ask_back_response(
        self,
        state: SlotState,
        missing_slot_key: str | None = None,
    ) -> str | None:
        """
        生成追问响应文案
        
        Args:
            state: 当前槽位状态
            missing_slot_key: 指定要追问的槽位键名，为 None 时追问第一个缺失槽位
            
        Returns:
            追问文案或 None（如果没有缺失槽位）
        """
        if not state.missing_required_slots:
            return None
        
        missing_info = None
        
        # 如果指定了槽位键名，查找对应的追问提示
        if missing_slot_key:
            for missing in state.missing_required_slots:
                if missing.get("slot_key") == missing_slot_key:
                    missing_info = missing
                    break
        else:
            # 使用第一个缺失槽位
            missing_info = state.missing_required_slots[0]
        
        if missing_info is None:
            return None
        
        # 优先使用配置的 ask_back_prompt
        ask_back_prompt = missing_info.get("ask_back_prompt")
        if ask_back_prompt:
            return ask_back_prompt
        
        # 使用通用模板
        label = missing_info.get("label", missing_info.get("slot_key", "相关信息"))
        return f"为了更好地为您提供帮助，请告诉我您的{label}。"


def create_slot_state_aggregator(
    session: AsyncSession,
    tenant_id: str,
) -> SlotStateAggregator:
    """
    创建槽位状态聚合器实例
    
    Args:
        session: 数据库会话
        tenant_id: 租户 ID
        
    Returns:
        SlotStateAggregator: 槽位状态聚合器实例
    """
    return SlotStateAggregator(
        session=session,
        tenant_id=tenant_id,
    )
