"""
Unit Tests for PerformanceEventInjector
=======================================

Run:
  source ~/kw_venv/bin/activate
  pytest langchain_agentx/agent/test_performance_event_injector.py -v
"""

import pytest
from dataclasses import dataclass
from unittest.mock import Mock

from langchain_agentx.agent.performance_event_injector import (
    PerformanceEventInjector,
    create_performance_event_injector,
    EVENT_TYPE_PERFORMANCE,
)


# ========== Mock Classes ==========

@dataclass
class MockPerformanceMetrics:
    """与 PerformanceMetrics 阈值逻辑对齐：按单次调用平均与次数。"""
    total_tool_calls: int
    tool_call_chain: list
    total_latency_ms: float
    unique_tools: set
    total_llm_calls: int = 0
    llm_call_chain: list = None  # type: ignore[assignment]
    total_llm_latency_ms: float = 0.0
    complete_call_chain: list = None  # type: ignore[assignment]
    context_tokens: int = 0
    TOOL_COUNT_THRESHOLD: int = 15
    TOOL_AVG_LATENCY_THRESHOLD_MS: int = 2000
    LLM_AVG_LATENCY_THRESHOLD_MS: int = 3000

    def __post_init__(self):
        if self.llm_call_chain is None:
            self.llm_call_chain = []
        if self.complete_call_chain is None:
            self.complete_call_chain = []

    def has_warning(self) -> bool:
        avg_tool = (
            self.total_latency_ms / self.total_tool_calls
            if self.total_tool_calls > 0
            else 0.0
        )
        avg_llm = (
            self.total_llm_latency_ms / self.total_llm_calls
            if self.total_llm_calls > 0
            else 0.0
        )
        return (
            self.total_tool_calls > self.TOOL_COUNT_THRESHOLD
            or avg_tool > self.TOOL_AVG_LATENCY_THRESHOLD_MS
            or avg_llm > self.LLM_AVG_LATENCY_THRESHOLD_MS
        )

    def get_warnings(self) -> list:
        warnings = []
        if self.total_tool_calls > self.TOOL_COUNT_THRESHOLD:
            warnings.append(f"⚠️ 工具调用次数过多(>{self.TOOL_COUNT_THRESHOLD}次)，建议优化策略")
        if self.total_tool_calls > 0:
            avg_tool = self.total_latency_ms / self.total_tool_calls
            if avg_tool > self.TOOL_AVG_LATENCY_THRESHOLD_MS:
                warnings.append(
                    f"⚠️ 工具单次调用平均耗时过高(>{self.TOOL_AVG_LATENCY_THRESHOLD_MS}ms)，可能存在网络/IO 瓶颈"
                )
        if self.total_llm_calls > 0:
            avg_llm = self.total_llm_latency_ms / self.total_llm_calls
            if avg_llm > self.LLM_AVG_LATENCY_THRESHOLD_MS:
                warnings.append(
                    f"⚠️ 单次 LLM 调用平均耗时过高(>{self.LLM_AVG_LATENCY_THRESHOLD_MS}ms)，建议检查模型选型或上下文大小"
                )
        return warnings


@dataclass
class MockToolCallRecord:
    tool_name: str
    cost_time_ms: float
    output_length: int
    error: str | None = None


# ========== Fixtures ==========

@pytest.fixture
def injector():
    return PerformanceEventInjector()


def _create_mock_config():
    mock_metrics = MockPerformanceMetrics(
        total_tool_calls=3,
        tool_call_chain=["bound_search_code", "grep", "read_file"],
        total_latency_ms=659.8,
        unique_tools={"bound_search_code", "grep", "read_file"},
    )
    mock_monitor = Mock()
    mock_monitor.records = [
        MockToolCallRecord("bound_search_code", 450.23, 2048),
        MockToolCallRecord("grep", 120.45, 512),
        MockToolCallRecord("read_file", 89.12, 1024),
    ]
    mock_monitor.llm_records = []
    mock_monitor.strategy = None
    mock_monitor.strategy_explanation = ""
    mock_monitor.calculate_metrics.return_value = mock_metrics

    mock_callback = Mock()
    mock_callback.performance_monitor = mock_monitor
    return {"callbacks": [mock_callback]}


# ========== Tests ==========

class TestPerformanceEventInjector:
    """测试 PerformanceEventInjector"""

    @pytest.mark.asyncio
    async def test_inject_yields_one_event(self, injector):
        """有 callback 和 performance_monitor 时 yield 一条事件"""
        config = _create_mock_config()
        events = []
        async for evt in injector.inject_performance_events(config):
            events.append(evt)
        assert len(events) == 1

    @pytest.mark.asyncio
    async def test_event_structure(self, injector):
        """事件结构为 event_type + data + timestamp"""
        config = _create_mock_config()
        events = []
        async for evt in injector.inject_performance_events(config):
            events.append(evt)
        evt = events[0]
        assert evt["event_type"] == EVENT_TYPE_PERFORMANCE
        assert "data" in evt
        assert "timestamp" in evt
        data = evt["data"]
        assert data["mode"] == "benchmark"
        assert data["total_tool_calls"] == 3
        assert data["tool_call_chain"] == ["bound_search_code", "grep", "read_file"]
        assert data["total_latency_ms"] == 659.8
        assert set(data["unique_tools"]) == {"bound_search_code", "grep", "read_file"}
        assert len(data["tool_details"]) == 3
        assert data["tool_details"][0]["tool_name"] == "bound_search_code"
        assert data["tool_details"][0]["cost_time_ms"] == 450.23
        assert "timestamp" in data
        # 平均耗时字段（659.8/3 ≈ 219.93，0 次 LLM）
        assert data["average_tool_latency_ms"] == 219.93
        assert data["average_llm_latency_ms"] == 0

    @pytest.mark.asyncio
    async def test_optimization_suggestions_when_avg_exceeds_threshold(self, injector):
        """当工具或 LLM 平均耗时超过阈值时，data 中应包含优化建议"""
        mock_metrics = MockPerformanceMetrics(
            total_tool_calls=2,
            tool_call_chain=["slow_tool", "slow_tool"],
            total_latency_ms=5000.0,  # 平均 2500ms > 2000
            unique_tools={"slow_tool"},
        )
        mock_monitor = Mock()
        mock_monitor.records = [
            MockToolCallRecord("slow_tool", 2500.0, 100),
            MockToolCallRecord("slow_tool", 2500.0, 100),
        ]
        mock_monitor.llm_records = []
        mock_monitor.strategy = None
        mock_monitor.strategy_explanation = ""
        mock_monitor.calculate_metrics.return_value = mock_metrics
        mock_callback = Mock()
        mock_callback.performance_monitor = mock_monitor
        config = {"callbacks": [mock_callback]}

        events = []
        async for evt in injector.inject_performance_events(config):
            events.append(evt)
        assert len(events) == 1
        data = events[0]["data"]
        assert data["has_warning"] is True
        assert len(data["optimization_suggestions"]) > 0
        assert any("工具" in s or "网络" in s or "缓存" in s for s in data["optimization_suggestions"])

    @pytest.mark.asyncio
    async def test_no_callback_no_events(self, injector):
        """config 无 callbacks 时不 yield"""
        config = {}
        events = []
        async for evt in injector.inject_performance_events(config):
            events.append(evt)
        assert len(events) == 0

    @pytest.mark.asyncio
    async def test_no_performance_monitor_no_events(self, injector):
        """callback 无 performance_monitor 时不 yield"""
        mock_callback = Mock()
        mock_callback.performance_monitor = None
        config = {"callbacks": [mock_callback]}
        events = []
        async for evt in injector.inject_performance_events(config):
            events.append(evt)
        assert len(events) == 0

    @pytest.mark.asyncio
    async def test_create_performance_event_injector(self):
        """工厂函数返回实例"""
        inj = create_performance_event_injector()
        assert isinstance(inj, PerformanceEventInjector)
