"""
Slot Extraction Integration Service.
槽位提取集成服务 - 将自动提取能力接入主链路

[AC-MRS-SLOT-EXTRACT-01] slot extraction 集成

职责：
1. 接入点：memory_recall 之后、KB 检索之前
2. 执行策略链：rule -> llm -> user_input
3. 抽取结果统一走 SlotManager 校验
4. 提供 trace：extracted_slots、validation_pass/fail、ask_back_triggered
"""

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

from sqlalchemy.ext.asyncio import AsyncSession

from app.models.mid.schemas import SlotSource
from app.services.mid.slot_backfill_service import (
    BackfillResult,
    BackfillStatus,
    SlotBackfillService,
)
from app.services.mid.slot_manager import SlotManager
from app.services.mid.slot_state_aggregator import SlotState, SlotStateAggregator
from app.services.mid.slot_strategy_executor import (
    ExtractContext,
    SlotStrategyExecutor,
    StrategyChainResult,
)
from app.services.slot_definition_service import SlotDefinitionService

logger = logging.getLogger(__name__)


@dataclass
class ExtractionTrace:
    """
    提取追踪信息
    
    Attributes:
        slot_key: 槽位键名
        strategy: 使用的策略
        extracted_value: 提取的值
        validation_passed: 校验是否通过
        final_value: 最终值（校验后）
        execution_time_ms: 执行时间
        failure_reason: 失败原因
    """
    slot_key: str
    strategy: str | None = None
    extracted_value: Any = None
    validation_passed: bool = False
    final_value: Any = None
    execution_time_ms: float = 0.0
    failure_reason: str | None = None
    
    def to_dict(self) -> dict[str, Any]:
        return {
            "slot_key": self.slot_key,
            "strategy": self.strategy,
            "extracted_value": self.extracted_value,
            "validation_passed": self.validation_passed,
            "final_value": self.final_value,
            "execution_time_ms": self.execution_time_ms,
            "failure_reason": self.failure_reason,
        }


@dataclass
class ExtractionResult:
    """
    提取结果
    
    Attributes:
        success: 是否成功
        extracted_slots: 成功提取的槽位
        failed_slots: 提取失败的槽位
        traces: 提取追踪信息列表
        total_execution_time_ms: 总执行时间
        ask_back_triggered: 是否触发追问
        ask_back_prompts: 追问提示列表
    """
    success: bool = False
    extracted_slots: dict[str, Any] = field(default_factory=dict)
    failed_slots: list[str] = field(default_factory=list)
    traces: list[ExtractionTrace] = field(default_factory=list)
    total_execution_time_ms: float = 0.0
    ask_back_triggered: bool = False
    ask_back_prompts: list[str] = field(default_factory=list)
    
    def to_dict(self) -> dict[str, Any]:
        return {
            "success": self.success,
            "extracted_slots": self.extracted_slots,
            "failed_slots": self.failed_slots,
            "traces": [t.to_dict() for t in self.traces],
            "total_execution_time_ms": self.total_execution_time_ms,
            "ask_back_triggered": self.ask_back_triggered,
            "ask_back_prompts": self.ask_back_prompts,
        }


class SlotExtractionIntegration:
    """
    [AC-MRS-SLOT-EXTRACT-01] 槽位提取集成服务
    
    将自动提取能力接入主链路：
    - 接入点：memory_recall 之后、KB 检索之前
    - 执行策略链：rule -> llm -> user_input
    - 抽取结果统一走 SlotManager 校验
    - 提供 trace
    """
    
    DEFAULT_STRATEGIES = ["rule", "llm"]
    
    def __init__(
        self,
        session: AsyncSession,
        tenant_id: str,
        session_id: str | None = None,
        slot_manager: SlotManager | None = None,
        strategy_executor: SlotStrategyExecutor | 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._strategy_executor = strategy_executor or SlotStrategyExecutor()
        self._slot_def_service = SlotDefinitionService(session)
        self._backfill_service: SlotBackfillService | None = None
    
    async def _get_backfill_service(self) -> SlotBackfillService:
        """获取回填服务"""
        if self._backfill_service is None:
            self._backfill_service = SlotBackfillService(
                session=self._session,
                tenant_id=self._tenant_id,
                session_id=self._session_id,
                slot_manager=self._slot_manager,
                strategy_executor=self._strategy_executor,
            )
        return self._backfill_service
    
    async def extract_and_fill(
        self,
        user_input: str,
        target_slots: list[str] | None = None,
        strategies: list[str] | None = None,
        slot_state: SlotState | None = None,
    ) -> ExtractionResult:
        """
        执行提取并填充槽位
        
        Args:
            user_input: 用户输入
            target_slots: 目标槽位列表（为空则提取所有必填槽位）
            strategies: 提取策略链（默认 rule -> llm）
            slot_state: 当前槽位状态（用于识别缺失槽位）
            
        Returns:
            ExtractionResult: 提取结果
        """
        start_time = time.time()
        
        strategies = strategies or self.DEFAULT_STRATEGIES
        
        if target_slots is None:
            target_slots = await self._get_missing_required_slots(slot_state)
        
        if not target_slots:
            return ExtractionResult(success=True)
        
        result = ExtractionResult()
        
        for slot_key in target_slots:
            trace = await self._extract_single_slot(
                slot_key=slot_key,
                user_input=user_input,
                strategies=strategies,
            )
            result.traces.append(trace)
            
            if trace.validation_passed and trace.final_value is not None:
                result.extracted_slots[slot_key] = trace.final_value
            else:
                result.failed_slots.append(slot_key)
                if trace.failure_reason:
                    ask_back_prompt = await self._slot_manager.get_ask_back_prompt(
                        self._tenant_id, slot_key
                    )
                    if ask_back_prompt:
                        result.ask_back_prompts.append(ask_back_prompt)
        
        result.total_execution_time_ms = (time.time() - start_time) * 1000
        result.success = len(result.extracted_slots) > 0
        result.ask_back_triggered = len(result.ask_back_prompts) > 0
        
        if result.extracted_slots and self._session_id:
            await self._save_extracted_slots(result.extracted_slots)
        
        logger.info(
            f"[AC-MRS-SLOT-EXTRACT-01] Extraction completed: "
            f"tenant={self._tenant_id}, extracted={len(result.extracted_slots)}, "
            f"failed={len(result.failed_slots)}, time_ms={result.total_execution_time_ms:.2f}"
        )
        
        return result
    
    async def _extract_single_slot(
        self,
        slot_key: str,
        user_input: str,
        strategies: list[str],
    ) -> ExtractionTrace:
        """提取单个槽位"""
        start_time = time.time()
        trace = ExtractionTrace(slot_key=slot_key)
        
        slot_def = await self._slot_def_service.get_slot_definition_by_key(
            self._tenant_id, slot_key
        )
        
        if not slot_def:
            trace.failure_reason = "Slot definition not found"
            trace.execution_time_ms = (time.time() - start_time) * 1000
            return trace
        
        context = ExtractContext(
            tenant_id=self._tenant_id,
            slot_key=slot_key,
            user_input=user_input,
            slot_type=slot_def.type,
            validation_rule=slot_def.validation_rule,
            session_id=self._session_id,
        )
        
        chain_result = await self._strategy_executor.execute_chain(
            strategies=strategies,
            context=context,
            ask_back_prompt=slot_def.ask_back_prompt,
        )
        
        trace.strategy = chain_result.final_strategy
        trace.extracted_value = chain_result.final_value
        trace.execution_time_ms = (time.time() - start_time) * 1000
        
        if not chain_result.success:
            trace.failure_reason = "Extraction failed"
            if chain_result.steps:
                last_step = chain_result.steps[-1]
                trace.failure_reason = last_step.failure_reason
            return trace
        
        backfill_service = await self._get_backfill_service()
        source = self._get_source_for_strategy(chain_result.final_strategy)
        
        backfill_result = await backfill_service.backfill_single_slot(
            slot_key=slot_key,
            candidate_value=chain_result.final_value,
            source=source,
            confidence=self._get_confidence_for_source(source),
        )
        
        trace.validation_passed = backfill_result.is_success()
        trace.final_value = backfill_result.normalized_value
        
        if not backfill_result.is_success():
            trace.failure_reason = backfill_result.error_message or "Validation failed"
        
        return trace
    
    async def _get_missing_required_slots(
        self,
        slot_state: SlotState | None,
    ) -> list[str]:
        """获取缺失的必填槽位"""
        if slot_state and slot_state.missing_required_slots:
            return [
                s.get("slot_key")
                for s in slot_state.missing_required_slots
                if s.get("slot_key")
            ]
        
        required_defs = await self._slot_def_service.list_slot_definitions(
            tenant_id=self._tenant_id,
            required=True,
        )
        
        return [d.slot_key for d in required_defs]
    
    async def _save_extracted_slots(
        self,
        extracted_slots: dict[str, Any],
    ) -> None:
        """保存提取的槽位到缓存"""
        if not self._session_id:
            return
        
        aggregator = SlotStateAggregator(
            session=self._session,
            tenant_id=self._tenant_id,
            session_id=self._session_id,
        )
        
        for slot_key, value in extracted_slots.items():
            await aggregator.update_slot(
                slot_key=slot_key,
                value=value,
                source=SlotSource.RULE_EXTRACTED.value,
                confidence=0.9,
            )
    
    def _get_source_for_strategy(self, strategy: str | None) -> str:
        """根据策略获取来源"""
        strategy_source_map = {
            "rule": SlotSource.RULE_EXTRACTED.value,
            "llm": SlotSource.LLM_INFERRED.value,
            "user_input": SlotSource.USER_CONFIRMED.value,
        }
        return strategy_source_map.get(strategy or "", "unknown")
    
    def _get_confidence_for_source(self, source: str) -> float:
        """根据来源获取置信度"""
        confidence_map = {
            SlotSource.USER_CONFIRMED.value: 1.0,
            SlotSource.RULE_EXTRACTED.value: 0.9,
            SlotSource.LLM_INFERRED.value: 0.7,
        }
        return confidence_map.get(source, 0.5)


async def integrate_slot_extraction(
    session: AsyncSession,
    tenant_id: str,
    session_id: str,
    user_input: str,
    slot_state: SlotState | None = None,
    strategies: list[str] | None = None,
) -> ExtractionResult:
    """
    便捷函数：集成槽位提取
    
    Args:
        session: 数据库会话
        tenant_id: 租户 ID
        session_id: 会话 ID
        user_input: 用户输入
        slot_state: 当前槽位状态
        strategies: 提取策略链
        
    Returns:
        ExtractionResult: 提取结果
    """
    integration = SlotExtractionIntegration(
        session=session,
        tenant_id=tenant_id,
        session_id=session_id,
    )
    
    return await integration.extract_and_fill(
        user_input=user_input,
        slot_state=slot_state,
        strategies=strategies,
    )
