"""
规则引擎和检测规则
==================

提供规则引擎和重复调用检测规则：
- DetectionRule：检测规则抽象基类
- DuplicateCallsRule：重复调用检测（同工具+同参数）
- RuleEngine：规则引擎（注册和执行规则）

注：工具调用最大步数已由底层主循环处理，此处仅保留重复检测。
"""
import time
import json
import hashlib
from abc import ABC, abstractmethod
from typing import List, TYPE_CHECKING

from .context_monitor import Warning

import logging
logger = logging.getLogger(__name__)


from langchain_agentx import ToolCallRecord, PerformanceMetrics


class DetectionRule(ABC):
    """
    检测规则抽象基类

    所有检测规则必须继承此类并实现 check() 方法。
    """

    def __init__(self):
        """初始化规则"""
        self.enabled = True

    @abstractmethod
    def check(
        self,
        tool_records: List['ToolCallRecord'],
        metrics: 'PerformanceMetrics'
    ) -> List[Warning]:
        """
        检查规则，返回警告列表

        Args:
            tool_records: 原始工具调用记录
            metrics: 统计指标

        Returns:
            警告列表（可能为空）
        """
        pass

    def disable(self):
        """禁用规则"""
        self.enabled = False

    def enable(self):
        """启用规则"""
        self.enabled = True


class DuplicateCallsRule(DetectionRule):
    """
    重复调用检测规则（同工具+同参数）

    检测整个调用历史中：
    - 相同工具 + 相同参数被调用多次（≥ 3）

    改进点：
    1. 参数归一化：处理参数顺序不同但语义相同的情况
       - 递归处理嵌套字典和列表
       - 统一排序键值对
       - 处理特殊类型（None, bool, int, float, str）

    示例：
        read_file("/README.md")  ← 第1次
        search_code("登录")
        read_file("/README.md")  ← 第2次，重复！
    """

    def _normalize_args(self, args: dict) -> str:
        """
        归一化参数（处理参数顺序、嵌套结构等）

        改进点：
        1. 递归处理嵌套字典和列表
        2. 统一排序键值对
        3. 处理特殊类型（None, bool, int, float, str）

        Args:
            args: 原始参数字典

        Returns:
            归一化后的 JSON 字符串
        """
        def normalize_value(value):
            """递归归一化值"""
            if value is None:
                return None
            elif isinstance(value, bool):
                return value
            elif isinstance(value, (int, float, str)):
                return value
            elif isinstance(value, dict):
                # 递归处理字典，并按键排序
                return {
                    k: normalize_value(v)
                    for k, v in sorted(value.items())
                }
            elif isinstance(value, list):
                # 列表需要特殊处理：如果元素都是可排序的，则排序
                normalized_list = [normalize_value(item) for item in value]
                # 尝试排序（如果所有元素都是可比较的）
                try:
                    # 只对简单类型列表排序
                    if all(isinstance(item, (int, float, str, bool, type(None))) 
                           for item in normalized_list):
                        normalized_list.sort()
                except TypeError:
                    # 无法排序（如包含字典），保持原序
                    pass
                return normalized_list
            else:
                # 其他类型转为字符串
                return str(value)

        try:
            normalized = normalize_value(args)
            return json.dumps(normalized, sort_keys=True, ensure_ascii=False)
        except (TypeError, ValueError) as e:
            # 如果归一化失败，回退到原始序列化
            logger.debug(f"Failed to normalize args, using fallback: {e}")
            return json.dumps(args, sort_keys=True, ensure_ascii=False)

    def _generate_signature(self, tool_name: str, input_args: dict) -> str:
        """
        生成调用签名（工具名 + 参数哈希）

        Args:
            tool_name: 工具名称
            input_args: 输入参数

        Returns:
            签名字符串
        """
        if input_args:
            try:
                # 使用归一化后的参数计算哈希
                normalized_args_str = self._normalize_args(input_args)
                args_hash = hashlib.md5(normalized_args_str.encode()).hexdigest()[:8]
                return f"{tool_name}_{args_hash}"
            except Exception as e:
                # 无法序列化参数，使用工具名
                logger.debug(f"Failed to generate signature for {tool_name}: {e}")
                return f"{tool_name}_unknown_args"
        else:
            return f"{tool_name}_no_args"

    def check(
        self,
        tool_records: List['ToolCallRecord'],
        metrics: 'PerformanceMetrics'
    ) -> List[Warning]:
        """
        检测重复调用

        Args:
            tool_records: 工具调用记录
            metrics: 统计指标（本规则不使用）

        Returns:
            警告列表
        """
        warnings = []

        # 构建调用签名 → 调用记录的映射
        call_signatures = {}

        for call in tool_records:
            # 生成签名：tool_name + 归一化后的 args hash
            signature = self._generate_signature(
                call.tool_name,
                call.input_args if call.input_args else {}
            )

            if signature not in call_signatures:
                call_signatures[signature] = []

            call_signatures[signature].append(call)

        # 检查重复调用（阈值：3次）
        for signature, calls in call_signatures.items():
            if len(calls) >= 3:
                tool_name = calls[0].tool_name
                # 强制性告警文案（opencode 风格）
                message = (
                    f"⚠️ 上下文感知告警：检测到工具 '{tool_name}' 使用完全相同参数被重复调用 {len(calls)} 次（≥ 3）。\n"
                    f"⛔ 这通常意味着你在浪费工具配额或忽略了上一次调用的结果。\n"
                    f"✅ 你现在必须：\n"
                    f"     1. 立即复用最近一次 '{tool_name}' 的调用结果，而不是再次调用同一工具。\n"
                    f"     2. 只有在参数确实不同且有明确新需求时，才重新调用该工具。\n"
                    f"❌ 禁止：在没有任何参数变化或新理由说明的情况下，再次以相同参数调用 '{tool_name}'。"
                )

                warnings.append(Warning(
                    warning_id=f"duplicate_{signature}",
                    warning_type="duplicate_calls",
                    severity="warning",
                    message=message,
                    timestamp=time.time(),
                    details={
                        "tool_name": tool_name,
                        "call_count": len(calls),
                        "signature": signature,
                        "timestamps": [call.start_time for call in calls]
                    }
                ))

        return warnings


class RuleEngine:
    """
    规则引擎

    职责：
    - 注册检测规则
    - 执行规则检测
    - 返回警告列表
    """

    def __init__(self):
        """
        初始化规则引擎
        """
        self.rules: List[DetectionRule] = []

        # 注册内置规则
        self._register_builtin_rules()

        logger.info(
            f"RuleEngine initialized | "
            f"registered {len(self.rules)} rules"
        )

    def _register_builtin_rules(self):
        """注册内置规则"""
        # 注：工具调用最大步数已由底层主循环处理，此处仅保留重复调用检测
        # context_size/loop/tool_count 已下沉或移除，仅保留 DuplicateCallsRule

        self.rules.append(DuplicateCallsRule())
        logger.debug("Registered DuplicateCallsRule")

    def register_rule(self, rule: DetectionRule):
        """
        注册自定义规则

        Args:
            rule: 自定义规则实例
        """
        self.rules.append(rule)
        logger.info(f"Registered custom rule: {rule.__class__.__name__}")

    def check(
        self,
        tool_records: List['ToolCallRecord'],
        metrics: 'PerformanceMetrics'
    ) -> List[Warning]:
        """
        执行所有规则检测

        Args:
            tool_records: 原始工具调用记录
            metrics: 统计指标

        Returns:
            警告列表
        """
        warnings = []

        for rule in self.rules:
            if rule.enabled:
                try:
                    rule_warnings = rule.check(tool_records, metrics)
                    warnings.extend(rule_warnings)

                    if rule_warnings:
                        logger.debug(
                            f"{rule.__class__.__name__} detected "
                            f"{len(rule_warnings)} warning(s)"
                        )
                except Exception as e:
                    logger.error(
                        f"Error in {rule.__class__.__name__}.check(): {e}",
                        exc_info=True
                    )

        return warnings


if __name__ == "__main__":
    # 测试代码
    print("=" * 60)
    print("测试：RuleEngine 和 DuplicateCallsRule")
    print("=" * 60)

    from dataclasses import dataclass

    @dataclass
    class MockToolCallRecord:
        tool_name: str
        start_time: float
        input_args: dict = None

    @dataclass
    class MockPerformanceMetrics:
        context_tokens: int = 0

    # 测试：DuplicateCallsRule
    print("\n1. 测试 DuplicateCallsRule")
    rule = DuplicateCallsRule()

    # 场景1：重复调用（相同参数）
    records = [
        MockToolCallRecord("read_file", time.time(), {"path": "/README.md"}),
        MockToolCallRecord("search_code", time.time(), {"query": "登录"}),
        MockToolCallRecord("read_file", time.time(), {"path": "/README.md"}),  # 重复
    ]
    warnings = rule.check(records, MockPerformanceMetrics())
    print(f"  重复调用（相同参数）: {len(warnings)} 个警告")
    if warnings:
        print(f"    → {warnings[0].message}")

    # 场景2：不同参数
    records = [
        MockToolCallRecord("read_file", time.time(), {"path": "/README.md"}),
        MockToolCallRecord("read_file", time.time(), {"path": "/LICENSE"}),
    ]
    warnings = rule.check(records, MockPerformanceMetrics())
    print(f"  不同参数: {len(warnings)} 个警告")

    # 测试：RuleEngine
    print("\n2. 测试 RuleEngine")
    engine = RuleEngine()
    print(f"  已注册 {len(engine.rules)} 个规则")

    # 综合测试（重复调用）
    records = [
        MockToolCallRecord("read_file", time.time(), {"path": "/README.md"}),
        MockToolCallRecord("search_code", time.time(), {"query": "test"}),
        MockToolCallRecord("read_file", time.time(), {"path": "/README.md"}),  # 重复
    ]
    metrics = MockPerformanceMetrics(context_tokens=0)

    all_warnings = engine.check(records, metrics)
    print(f"  综合检测: {len(all_warnings)} 个警告")
    for w in all_warnings:
        print(f"    - [{w.warning_type}] {w.severity}: {w.message}")

    print("\n" + "=" * 60)
    print("测试完成")
    print("=" * 60)

