"""
Tests for Mid Platform services.
[AC-IDMP-05/07/08/09/18/20] 中台服务测试
"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from datetime import datetime

from app.services.mid import (
    HighRiskHandler,
    HighRiskMatchResult,
    PolicyRouter,
    PolicyRouteResult,
    TraceLogger,
    MetricsCollector,
    MetricsRecord,
    SessionModeService,
    DEFAULT_HIGH_RISK_SCENARIOS,
)
from app.models.entities import (
    HighRiskPolicy,
    HighRiskScenarioType,
    SessionModeRecord,
    MidAuditLog,
)
from app.models.mid import Mode, SessionMode


class TestHighRiskHandler:
    """[AC-IDMP-05/20] 高风险场景处理器测试"""

    @pytest.fixture
    def mock_session(self):
        session = AsyncMock()
        return session

    @pytest.fixture
    def handler(self, mock_session):
        return HighRiskHandler(mock_session)

    @pytest.mark.asyncio
    async def test_get_active_scenario_set_returns_default_when_empty(self, handler, mock_session):
        """[AC-IDMP-20] 空集保护：数据库无配置时返回默认最小集"""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        scenarios = await handler.get_active_scenario_set("test_tenant")
        
        assert scenarios == DEFAULT_HIGH_RISK_SCENARIOS
        assert "refund" in scenarios
        assert "complaint_escalation" in scenarios
        assert "privacy_sensitive_promise" in scenarios
        assert "transfer" in scenarios

    @pytest.mark.asyncio
    async def test_detect_high_risk_refund_keyword(self, handler, mock_session):
        """[AC-IDMP-05] 检测退款场景"""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        result = await handler.detect_high_risk("test_tenant", "我要退款")
        
        assert result.matched is True
        assert result.scenario == HighRiskScenarioType.REFUND.value
        assert result.handler_mode == "micro_flow"

    @pytest.mark.asyncio
    async def test_detect_high_risk_transfer_keyword(self, handler, mock_session):
        """[AC-IDMP-05] 检测转人工场景"""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        result = await handler.detect_high_risk("test_tenant", "转人工")
        
        assert result.matched is True
        assert result.scenario == HighRiskScenarioType.TRANSFER.value
        assert result.handler_mode == "transfer"

    @pytest.mark.asyncio
    async def test_detect_high_risk_complaint_keyword(self, handler, mock_session):
        """[AC-IDMP-05] 检测投诉升级场景"""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        result = await handler.detect_high_risk("test_tenant", "我要投诉你们")
        
        assert result.matched is True
        assert result.scenario == HighRiskScenarioType.COMPLAINT_ESCALATION.value

    @pytest.mark.asyncio
    async def test_detect_high_risk_privacy_keyword(self, handler, mock_session):
        """[AC-IDMP-05] 检测隐私敏感承诺场景"""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        result = await handler.detect_high_risk("test_tenant", "你能保证我的隐私安全吗")
        
        assert result.matched is True
        assert result.scenario == HighRiskScenarioType.PRIVACY_SENSITIVE_PROMISE.value

    @pytest.mark.asyncio
    async def test_detect_high_risk_no_match(self, handler, mock_session):
        """[AC-IDMP-05] 正常消息不触发高风险"""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = []
        mock_session.execute.return_value = mock_result

        result = await handler.detect_high_risk("test_tenant", "你好，请问有什么可以帮助我的")
        
        assert result.matched is False

    @pytest.mark.asyncio
    async def test_detect_high_risk_with_policy(self, handler, mock_session):
        """[AC-IDMP-05] 使用数据库策略检测高风险"""
        policy = HighRiskPolicy(
            tenant_id="test_tenant",
            scenario=HighRiskScenarioType.REFUND.value,
            handler_mode="micro_flow",
            keywords=["退货退款"],
            priority=100,
            is_enabled=True,
        )
        
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [policy]
        mock_session.execute.return_value = mock_result

        result = await handler.detect_high_risk("test_tenant", "我要退货退款")
        
        assert result.matched is True
        assert result.scenario == HighRiskScenarioType.REFUND.value
        assert result.matched_keyword == "退货退款"


class TestPolicyRouter:
    """[AC-IDMP-05] 策略路由器测试"""

    @pytest.fixture
    def mock_high_risk_handler(self):
        return AsyncMock(spec=HighRiskHandler)

    @pytest.fixture
    def router(self, mock_high_risk_handler):
        return PolicyRouter(mock_high_risk_handler)

    @pytest.mark.asyncio
    async def test_route_human_mode_returns_transfer(self, router, mock_high_risk_handler):
        """[AC-IDMP-05] 人工模式返回 transfer"""
        mock_high_risk_handler.detect_high_risk.return_value = HighRiskMatchResult(matched=False)

        result = await router.route(
            tenant_id="test_tenant",
            user_message="你好",
            session_mode="HUMAN_ACTIVE",
        )
        
        assert result.mode == "transfer"

    @pytest.mark.asyncio
    async def test_route_high_risk_returns_micro_flow(self, router, mock_high_risk_handler):
        """[AC-IDMP-05] 高风险场景返回 micro_flow 或 transfer"""
        mock_high_risk_handler.detect_high_risk.return_value = HighRiskMatchResult(
            matched=True,
            scenario="refund",
            handler_mode="micro_flow",
        )

        result = await router.route(
            tenant_id="test_tenant",
            user_message="我要退款",
            session_mode="BOT_ACTIVE",
        )
        
        assert result.mode == "micro_flow"
        assert result.high_risk_scenario == "refund"

    @pytest.mark.asyncio
    async def test_route_low_confidence_returns_fixed(self, router, mock_high_risk_handler):
        """[AC-IDMP-05] 低置信度返回 fixed"""
        mock_high_risk_handler.detect_high_risk.return_value = HighRiskMatchResult(matched=False)

        result = await router.route(
            tenant_id="test_tenant",
            user_message="你好",
            session_mode="BOT_ACTIVE",
            confidence=0.2,
        )
        
        assert result.mode == "fixed"
        assert result.fallback_reason_code == "low_confidence"

    @pytest.mark.asyncio
    async def test_route_normal_returns_agent(self, router, mock_high_risk_handler):
        """[AC-IDMP-05] 正常场景返回 agent"""
        mock_high_risk_handler.detect_high_risk.return_value = HighRiskMatchResult(matched=False)

        result = await router.route(
            tenant_id="test_tenant",
            user_message="你好",
            session_mode="BOT_ACTIVE",
            confidence=0.8,
        )
        
        assert result.mode == "agent"


class TestMetricsCollector:
    """[AC-IDMP-18] 指标采集器测试"""

    @pytest.fixture
    def collector(self):
        return MetricsCollector()

    def test_record_and_get_metrics(self, collector):
        """[AC-IDMP-18] 记录并获取指标"""
        record1 = MetricsRecord(
            tenant_id="tenant1",
            session_id="session1",
            request_id="req1",
            task_completed=True,
            slots_filled=3,
            slots_total=5,
            was_transferred=False,
            had_recall=True,
            latency_ms=100,
        )
        
        record2 = MetricsRecord(
            tenant_id="tenant1",
            session_id="session2",
            request_id="req2",
            task_completed=False,
            slots_filled=2,
            slots_total=5,
            was_transferred=True,
            had_recall=False,
            latency_ms=200,
        )
        
        collector.record(record1)
        collector.record(record2)
        
        metrics = collector.get_metrics_snapshot("tenant1")
        
        assert metrics["task_completion_rate"] == 0.5
        assert metrics["slot_completion_rate"] == 0.5
        assert metrics["wrong_transfer_rate"] == 0.5
        assert metrics["no_recall_rate"] == 0.5
        assert metrics["avg_latency_ms"] == 150.0

    def test_get_metrics_empty_tenant(self, collector):
        """[AC-IDMP-18] 空租户返回零值指标"""
        metrics = collector.get_metrics_snapshot("unknown_tenant")
        
        assert metrics["task_completion_rate"] == 0.0
        assert metrics["slot_completion_rate"] == 0.0
        assert metrics["wrong_transfer_rate"] == 0.0
        assert metrics["no_recall_rate"] == 0.0
        assert metrics["avg_latency_ms"] == 0.0


class TestSessionModeService:
    """[AC-IDMP-09] 会话模式服务测试"""

    @pytest.fixture
    def mock_session(self):
        return AsyncMock()

    @pytest.fixture
    def service(self, mock_session):
        return SessionModeService(mock_session)

    @pytest.mark.asyncio
    async def test_get_mode_returns_default(self, service, mock_session):
        """[AC-IDMP-09] 获取默认模式"""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result

        mode = await service.get_mode("tenant1", "session1")
        
        assert mode == "BOT_ACTIVE"

    @pytest.mark.asyncio
    async def test_switch_mode_creates_new(self, service, mock_session):
        """[AC-IDMP-09] 切换模式创建新记录"""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result

        result = await service.switch_mode(
            tenant_id="tenant1",
            session_id="session1",
            mode="HUMAN_ACTIVE",
            reason="user_request",
        )
        
        assert result.mode == "HUMAN_ACTIVE"
        assert result.reason == "user_request"
        mock_session.add.assert_called_once()
        mock_session.commit.assert_called_once()


class TestTraceLogger:
    """[AC-IDMP-07] Trace 日志服务测试"""

    @pytest.fixture
    def mock_session(self):
        return AsyncMock()

    @pytest.fixture
    def logger(self, mock_session):
        return TraceLogger(mock_session)

    @pytest.mark.asyncio
    async def test_log_creates_audit_record(self, logger, mock_session):
        """[AC-IDMP-07] 记录审计日志"""
        result = await logger.log(
            tenant_id="tenant1",
            session_id="session1",
            request_id="req1",
            generation_id="gen1",
            mode="agent",
            intent="greeting",
            tool_calls=[{"tool": "search", "duration_ms": 100}],
            guardrail_triggered=False,
            latency_ms=500,
        )
        
        mock_session.add.assert_called_once()
        mock_session.commit.assert_called_once()
