"""
High Risk Check Tool for Mid Platform.
[AC-IDMP-05, AC-IDMP-20] 高风险场景检测工具，支持元数据驱动配置。
[AC-MRS-13] 只消费 field_roles 包含 routing_signal 的字段

核心特性：
- 基于租户元数据配置进行风险判定（不是写死关键词）
- 支持关键词 + 正则 + 优先级
- 租户隔离（tenant_id 必须参与查询）
- 超时 <= 500ms（可配置）
- 返回结构化结果，不抛硬异常
- 工具失败时返回可降级结果
- 只消费 routing_signal 角色的字段

高风险场景最小集：
1. refund（退款）
2. complaint_escalation（投诉升级）
3. privacy_sensitive_promise（隐私敏感承诺）
4. transfer（转人工）
"""

from __future__ import annotations

import asyncio
import logging
import re
import time
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.entities import HighRiskPolicy
from app.models.entities import FieldRole
from app.models.mid.schemas import (
    ExecutionMode,
    HighRiskCheckResult,
    HighRiskScenario,
    ToolCallStatus,
    ToolCallTrace,
    ToolType,
)
from app.services.mid.role_based_field_provider import RoleBasedFieldProvider

if TYPE_CHECKING:
    from app.services.mid.tool_registry import ToolRegistry

logger = logging.getLogger(__name__)

HIGH_RISK_CHECK_TOOL_NAME = "high_risk_check"
DEFAULT_TIMEOUT_MS = 500
DEFAULT_CONFIDENCE = 0.9


@dataclass
class HighRiskCheckConfig:
    """高风险检测工具配置。"""
    enabled: bool = True
    timeout_ms: int = DEFAULT_TIMEOUT_MS
    default_confidence: float = DEFAULT_CONFIDENCE


@dataclass
class CompiledRule:
    """编译后的高风险规则。"""
    rule_id: str
    scenario: HighRiskScenario
    handler_mode: ExecutionMode
    flow_id: str | None
    transfer_message: str | None
    priority: int
    keywords: list[str] = field(default_factory=list)
    patterns: list[re.Pattern] = field(default_factory=list)


class HighRiskCheckTool:
    """
    [AC-IDMP-05, AC-IDMP-20] 高风险场景检测工具。
    [AC-MRS-13] 只消费 field_roles 包含 routing_signal 的字段

    通过元数据配置动态加载风险规则，支持：
    - 关键词匹配（大小写不敏感）
    - 正则表达式匹配
    - 优先级排序（优先匹配高优先级规则）
    - 租户隔离
    - 只消费 routing_signal 角色的字段

    工具输入：
    - message: str（用户消息）
    - tenant_id: str（租户ID）
    - domain?: str（领域，可选）
    - scene?: str（场景，可选）
    - context?: dict（上下文，可选，只消费 routing_signal 字段）

    工具输出：
    - matched: bool
    - risk_scenario: refund|complaint_escalation|privacy_sensitive_promise|transfer|none
    - confidence: float
    - recommended_mode: micro_flow|transfer|agent
    - rule_id?: str
    - reason?: str
    - fallback_reason_code?: str
    """

    def __init__(
        self,
        session: AsyncSession,
        config: HighRiskCheckConfig | None = None,
    ):
        self._session = session
        self._config = config or HighRiskCheckConfig()
        self._rules_cache: dict[str, list[CompiledRule]] = {}
        self._cache_time: dict[str, float] = {}
        self._cache_ttl_seconds = 60
        self._role_provider = RoleBasedFieldProvider(session)

    @property
    def name(self) -> str:
        return HIGH_RISK_CHECK_TOOL_NAME

    @property
    def description(self) -> str:
        return (
            "高风险场景检测工具。"
            "基于租户配置的风险规则，检测用户消息是否命中退款、投诉、隐私承诺、转人工等高风险场景。"
            "返回结构化风险结果供 policy_router 使用。"
        )

    def get_tool_schema(self) -> dict[str, Any]:
        return {
            "name": self.name,
            "description": self.description,
            "parameters": {
                "type": "object",
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "用户消息内容",
                    },
                    "tenant_id": {
                        "type": "string",
                        "description": "租户ID",
                    },
                    "domain": {
                        "type": "string",
                        "description": "领域标识（可选）",
                    },
                    "scene": {
                        "type": "string",
                        "description": "场景标识（可选）",
                    },
                    "context": {
                        "type": "object",
                        "description": "额外上下文信息",
                    },
                },
                "required": ["message", "tenant_id"],
            },
        }

    async def execute(
        self,
        message: str,
        tenant_id: str,
        domain: str | None = None,
        scene: str | None = None,
        context: dict[str, Any] | None = None,
    ) -> HighRiskCheckResult:
        """
        [AC-IDMP-05, AC-IDMP-20] 执行高风险检测。
        [AC-MRS-13] 只消费 routing_signal 角色的字段

        Args:
            message: 用户消息
            tenant_id: 租户ID
            domain: 领域（可选）
            scene: 场景（可选）
            context: 上下文（可选，只消费 routing_signal 字段）

        Returns:
            HighRiskCheckResult 结构化结果
        """
        if not self._config.enabled:
            logger.info(f"[AC-IDMP-05] High risk check disabled for tenant={tenant_id}")
            return HighRiskCheckResult(
                matched=False,
                fallback_reason_code="TOOL_DISABLED",
            )

        start_time = time.time()

        routing_signal_fields = await self._role_provider.get_routing_signal_field_keys(tenant_id)
        logger.info(
            f"[AC-MRS-13] Retrieved {len(routing_signal_fields)} routing_signal fields for tenant={tenant_id}: {routing_signal_fields}"
        )

        routing_context = {}
        if context:
            routing_context = {k: v for k, v in context.items() if k in routing_signal_fields}
            if routing_context:
                logger.info(
                    f"[AC-MRS-13] Applied routing_signal context: {list(routing_context.keys())}"
                )

        logger.info(
            f"[AC-IDMP-05] Starting high risk check: tenant={tenant_id}, "
            f"message={message[:50]}..."
        )

        try:
            timeout_seconds = self._config.timeout_ms / 1000.0

            rules = await asyncio.wait_for(
                self._get_rules(tenant_id),
                timeout=timeout_seconds,
            )

            if not rules:
                logger.info(f"[AC-IDMP-05] No high risk rules for tenant={tenant_id}")
                duration_ms = int((time.time() - start_time) * 1000)
                return HighRiskCheckResult(
                    matched=False,
                    duration_ms=duration_ms,
                )

            sorted_rules = sorted(rules, key=lambda r: r.priority, reverse=True)

            for rule in sorted_rules:
                match_result = self._match_rule(message, rule)
                if match_result:
                    duration_ms = int((time.time() - start_time) * 1000)
                    recommended_mode = rule.handler_mode

                    logger.info(
                        f"[AC-IDMP-05] High risk matched: tenant={tenant_id}, "
                        f"scenario={rule.scenario.value}, rule_id={rule.rule_id}, "
                        f"duration_ms={duration_ms}"
                    )

                    return HighRiskCheckResult(
                        matched=True,
                        risk_scenario=rule.scenario,
                        confidence=self._config.default_confidence,
                        recommended_mode=recommended_mode,
                        rule_id=rule.rule_id,
                        reason=f"匹配到高风险规则: {rule.scenario.value}",
                        duration_ms=duration_ms,
                        matched_text=match_result.get("text"),
                        matched_pattern=match_result.get("pattern"),
                    )

            duration_ms = int((time.time() - start_time) * 1000)
            logger.info(
                f"[AC-IDMP-05] No high risk matched: tenant={tenant_id}, "
                f"duration_ms={duration_ms}"
            )

            return HighRiskCheckResult(
                matched=False,
                duration_ms=duration_ms,
            )

        except asyncio.TimeoutError:
            duration_ms = int((time.time() - start_time) * 1000)
            logger.warning(
                f"[AC-IDMP-05] High risk check timeout: tenant={tenant_id}, "
                f"duration_ms={duration_ms}"
            )
            return HighRiskCheckResult(
                matched=False,
                fallback_reason_code="CHECK_TIMEOUT",
                duration_ms=duration_ms,
            )

        except Exception as e:
            duration_ms = int((time.time() - start_time) * 1000)
            logger.error(
                f"[AC-IDMP-05] High risk check error: tenant={tenant_id}, "
                f"error={e}"
            )
            return HighRiskCheckResult(
                matched=False,
                fallback_reason_code="CHECK_ERROR",
                duration_ms=duration_ms,
            )

    async def _get_rules(self, tenant_id: str) -> list[CompiledRule]:
        """
        获取租户的高风险规则（带缓存）。

        只返回：
        - is_enabled = True
        - 状态生效的规则
        """
        current_time = time.time()

        if (
            tenant_id in self._rules_cache
            and tenant_id in self._cache_time
            and current_time - self._cache_time[tenant_id] < self._cache_ttl_seconds
        ):
            return self._rules_cache[tenant_id]

        rules = await self._load_rules_from_db(tenant_id)

        self._rules_cache[tenant_id] = rules
        self._cache_time[tenant_id] = current_time

        return rules

    async def _load_rules_from_db(self, tenant_id: str) -> list[CompiledRule]:
        """从数据库加载租户的高风险规则。"""
        stmt = select(HighRiskPolicy).where(
            HighRiskPolicy.tenant_id == tenant_id,
            HighRiskPolicy.is_enabled.is_(True),
        ).order_by(HighRiskPolicy.priority.desc())

        result = await self._session.execute(stmt)
        policies = result.scalars().all()

        rules = []
        for policy in policies:
            try:
                scenario = HighRiskScenario(policy.scenario)
            except ValueError:
                logger.warning(
                    f"[AC-IDMP-05] Unknown scenario: {policy.scenario}, "
                    f"policy_id={policy.id}"
                )
                continue

            handler_mode = ExecutionMode.MICRO_FLOW
            if policy.handler_mode == "transfer":
                handler_mode = ExecutionMode.TRANSFER

            compiled_patterns = []
            if policy.patterns:
                for pattern_str in policy.patterns:
                    try:
                        compiled = re.compile(pattern_str, re.IGNORECASE)
                        compiled_patterns.append(compiled)
                    except re.error as e:
                        logger.warning(
                            f"[AC-IDMP-05] Invalid pattern: {pattern_str}, "
                            f"error={e}"
                        )

            rule = CompiledRule(
                rule_id=str(policy.id),
                scenario=scenario,
                handler_mode=handler_mode,
                flow_id=str(policy.flow_id) if policy.flow_id else None,
                transfer_message=policy.transfer_message,
                priority=policy.priority,
                keywords=policy.keywords or [],
                patterns=compiled_patterns,
            )
            rules.append(rule)

        logger.info(
            f"[AC-IDMP-05] Loaded {len(rules)} high risk rules for tenant={tenant_id}"
        )

        return rules

    def _match_rule(self, message: str, rule: CompiledRule) -> dict[str, Any] | None:
        """
        检查消息是否匹配规则。

        优先匹配关键词，再匹配正则。

        Returns:
            匹配结果字典 {"text": str, "pattern": str} 或 None
        """
        message_lower = message.lower()

        for keyword in rule.keywords:
            if keyword.lower() in message_lower:
                return {
                    "text": keyword,
                    "pattern": f"keyword:{keyword}",
                }

        for pattern in rule.patterns:
            match = pattern.search(message)
            if match:
                return {
                    "text": match.group(),
                    "pattern": f"regex:{pattern.pattern}",
                }

        return None

    def create_trace(
        self,
        result: HighRiskCheckResult,
        tenant_id: str,
    ) -> ToolCallTrace:
        """创建工具调用追踪记录。"""
        status = ToolCallStatus.OK
        error_code = None

        if result.fallback_reason_code:
            if "TIMEOUT" in result.fallback_reason_code:
                status = ToolCallStatus.TIMEOUT
            else:
                status = ToolCallStatus.ERROR
            error_code = result.fallback_reason_code

        return ToolCallTrace(
            tool_name=self.name,
            tool_type=ToolType.INTERNAL,
            duration_ms=result.duration_ms,
            status=status,
            error_code=error_code,
            args_digest=f"tenant={tenant_id}",
            result_digest=f"matched={result.matched},scenario={result.risk_scenario}",
        )


def register_high_risk_check_tool(
    registry: ToolRegistry,
    session: AsyncSession,
    config: HighRiskCheckConfig | None = None,
) -> None:
    """
    [AC-IDMP-05, AC-IDMP-20] 将 high_risk_check 注册到 ToolRegistry。
    [AC-MRS-13] 只消费 routing_signal 角色的字段

    Args:
        registry: ToolRegistry 实例
        session: 数据库会话
        config: 工具配置
    """
    from app.services.mid.tool_registry import ToolType as RegistryToolType

    async def handler(
        message: str,
        tenant_id: str = "",
        domain: str | None = None,
        scene: str | None = None,
        context: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        tool = HighRiskCheckTool(
            session=session,
            config=config,
        )

        result = await tool.execute(
            message=message,
            tenant_id=tenant_id,
            domain=domain,
            scene=scene,
            context=context,
        )

        return result.model_dump()

    registry.register(
        name=HIGH_RISK_CHECK_TOOL_NAME,
        description="[AC-IDMP-05, AC-IDMP-20, AC-MRS-13] 高风险场景检测工具，基于租户配置检测退款、投诉、隐私承诺、转人工等高风险场景 (only consumes routing_signal fields)",
        handler=handler,
        tool_type=RegistryToolType.INTERNAL,
        version="1.0.0",
        auth_required=False,
        timeout_ms=config.timeout_ms if config else DEFAULT_TIMEOUT_MS,
        enabled=True,
        metadata={
            "supports_metadata_driven": True,
            "min_scenarios": ["refund", "complaint_escalation", "privacy_sensitive_promise", "transfer"],
            "supports_routing_signal_filter": True,
            "when_to_use": "当用户消息可能涉及退款、投诉升级、隐私承诺、转人工等高风险场景时使用。",
            "when_not_to_use": "当已完成高风险判定且结果未变化，或当前仅需知识检索时不要重复调用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "message": {"type": "string", "description": "用户消息原文"},
                    "tenant_id": {"type": "string", "description": "租户 ID"},
                    "domain": {"type": "string", "description": "业务域（可选）"},
                    "scene": {"type": "string", "description": "场景标识（可选）"},
                    "context": {"type": "object", "description": "上下文（仅 routing_signal 字段会被消费）"}
                },
                "required": ["message", "tenant_id"]
            },
            "example_action_input": {
                "message": "我要投诉你们并且现在就给我退款，不然我去12315",
                "tenant_id": "default",
                "scene": "open_consult"
            },
            "result_interpretation": "matched=true 时优先按 recommended_mode 执行；关注 risk_scenario、rule_id、fallback_reason_code。"
        },
    )

    logger.info(f"[AC-IDMP-05] Tool registered: {HIGH_RISK_CHECK_TOOL_NAME}")
