"""
IncidentEngine 單元測試
========================

Phase 6.4e 驗證點 2：
使用 Mock MemoryProvider 驗證 IncidentEngine 能正確處理告警信號
"""

import sys
from pathlib import Path
from datetime import datetime, timezone
from typing import Any

# 添加 src 到 Python Path
src_path = Path(__file__).parent.parent / "src"
sys.path.insert(0, str(src_path))


# =============================================================================
# Mock Memory Provider (完全隔離，不依賴外部)
# =============================================================================

class MockIncidentMemory:
    """Mock 記憶體提供者 - 純記憶體實作"""

    def __init__(self):
        self._incidents: dict[str, Any] = {}
        self._ns_index: dict[str, str] = {}  # namespace → incident_id
        self._target_index: dict[str, str] = {}  # target → incident_id

    async def load_incident(self, incident_id: str):
        """載入 Incident"""
        return self._incidents.get(incident_id)

    async def save_incident(self, incident, ttl_seconds: int = 604800) -> bool:
        """儲存 Incident"""
        self._incidents[incident.incident_id] = incident
        return True

    async def persist_incident(self, incident) -> bool:
        """持久化 (Mock 直接返回成功)"""
        return True

    async def find_related_incident(
        self,
        namespace: str,
        target: str,
        window_minutes: int = 30,
    ):
        """尋找相關 Incident"""
        # 檢查 namespace 索引
        if namespace in self._ns_index:
            incident_id = self._ns_index[namespace]
            incident = self._incidents.get(incident_id)
            if incident and incident.status.value in ["investigating", "mitigating"]:
                return incident

        # 檢查 target 索引
        if target in self._target_index:
            incident_id = self._target_index[target]
            incident = self._incidents.get(incident_id)
            if incident and incident.status.value in ["investigating", "mitigating"]:
                return incident

        return None

    async def update_index(
        self,
        incident_id: str,
        namespace: str,
        target: str,
    ) -> bool:
        """更新索引"""
        self._ns_index[namespace] = incident_id
        self._target_index[target] = incident_id
        return True


class MockBlastRadiusAnalyzer:
    """Mock 爆炸半徑分析器"""

    def analyze(self, target: str) -> list[str]:
        """返回受影響服務 (Mock 固定回應)"""
        return [target, f"{target}-dependent"]


# =============================================================================
# 測試案例
# =============================================================================

def test_incident_engine_import():
    """測試：能正確 import IncidentEngine"""
    from lewooogo_brain.engines.incident_engine import IncidentEngine
    from lewooogo_brain.interfaces.incident_processor import IIncidentProcessor

    assert issubclass(IncidentEngine, IIncidentProcessor)
    print("✅ IncidentEngine import 成功，實作 IIncidentProcessor")


def test_incident_engine_create_incident():
    """測試：處理新告警時創建 Incident"""
    import asyncio
    from lewooogo_brain.engines.incident_engine import IncidentEngine

    memory = MockIncidentMemory()
    analyzer = MockBlastRadiusAnalyzer()
    engine = IncidentEngine(memory=memory, blast_analyzer=analyzer)

    signal_data = {
        "source": "prometheus",
        "alert_name": "HighCPUUsage",
        "severity": "critical",
        "namespace": "awoooi-prod",
        "target": "awoooi-api",
        "message": "CPU usage exceeded 90%",
        "labels": {"app": "awoooi-api"},
    }

    async def run_test():
        incident = await engine.process_signal(signal_data)
        return incident

    incident = asyncio.get_event_loop().run_until_complete(run_test())

    assert incident is not None, "Failed to create incident"
    assert incident.incident_id.startswith("INC-"), f"Invalid incident ID: {incident.incident_id}"
    assert incident.severity.value == "P0", f"Expected P0, got {incident.severity.value}"
    assert len(incident.signals) == 1, f"Expected 1 signal, got {len(incident.signals)}"
    assert "awoooi-api" in incident.affected_services

    print(f"✅ Incident 創建成功:")
    print(f"   - ID: {incident.incident_id}")
    print(f"   - Severity: {incident.severity.value}")
    print(f"   - Signals: {len(incident.signals)}")
    print(f"   - Affected: {incident.affected_services}")


def test_incident_engine_aggregate_signals():
    """測試：相關告警聚合到同一 Incident"""
    import asyncio
    from lewooogo_brain.engines.incident_engine import IncidentEngine

    memory = MockIncidentMemory()
    engine = IncidentEngine(memory=memory)

    # 第一個告警
    signal1 = {
        "source": "prometheus",
        "alert_name": "HighCPUUsage",
        "severity": "warning",
        "namespace": "awoooi-prod",
        "target": "awoooi-api",
        "message": "CPU at 80%",
    }

    # 相同 namespace/target 的第二個告警
    signal2 = {
        "source": "grafana",
        "alert_name": "HighMemoryUsage",
        "severity": "critical",
        "namespace": "awoooi-prod",
        "target": "awoooi-api",
        "message": "Memory at 95%",
    }

    async def run_test():
        incident1 = await engine.process_signal(signal1)
        incident2 = await engine.process_signal(signal2)
        return incident1, incident2

    incident1, incident2 = asyncio.get_event_loop().run_until_complete(run_test())

    assert incident1 is not None
    assert incident2 is not None
    assert incident1.incident_id == incident2.incident_id, "Signals should aggregate"
    assert len(incident2.signals) == 2, f"Expected 2 signals, got {len(incident2.signals)}"
    # 嚴重度應升級為 P0 (critical)
    assert incident2.severity.value == "P0", f"Severity should escalate to P0"

    print(f"✅ 告警聚合成功:")
    print(f"   - Incident ID: {incident2.incident_id}")
    print(f"   - Total Signals: {len(incident2.signals)}")
    print(f"   - Final Severity: {incident2.severity.value}")


def test_incident_engine_deduplication():
    """測試：相同 Fingerprint 的告警去重"""
    import asyncio
    from lewooogo_brain.engines.incident_engine import IncidentEngine

    memory = MockIncidentMemory()
    engine = IncidentEngine(memory=memory)

    # 兩個完全相同的告警
    signal = {
        "source": "prometheus",
        "alert_name": "PodCrashLooping",
        "severity": "critical",
        "namespace": "awoooi-prod",
        "target": "awoooi-worker",
        "message": "Pod restart count > 5",
    }

    async def run_test():
        incident1 = await engine.process_signal(signal)
        incident2 = await engine.process_signal(signal)  # 重複
        return incident1, incident2

    incident1, incident2 = asyncio.get_event_loop().run_until_complete(run_test())

    assert incident1 is not None
    assert incident2 is not None
    assert incident1.incident_id == incident2.incident_id
    # 重複告警應被去重，signal 數量仍為 1
    assert len(incident2.signals) == 1, f"Expected 1 signal (dedup), got {len(incident2.signals)}"

    print(f"✅ 告警去重成功:")
    print(f"   - Signals after dedup: {len(incident2.signals)}")


def test_incident_engine_update_status():
    """測試：更新 Incident 狀態"""
    import asyncio
    from lewooogo_brain.engines.incident_engine import IncidentEngine
    from lewooogo_brain.interfaces.incident_processor import IncidentStatus

    memory = MockIncidentMemory()
    engine = IncidentEngine(memory=memory)

    signal = {
        "source": "test",
        "alert_name": "TestAlert",
        "severity": "warning",
        "namespace": "test",
        "target": "test-service",
    }

    async def run_test():
        incident = await engine.process_signal(signal)
        assert incident.status == IncidentStatus.INVESTIGATING

        success = await engine.update_status(incident.incident_id, IncidentStatus.RESOLVED)
        assert success, "Failed to update status"

        updated = await engine.get_incident(incident.incident_id)
        return updated

    updated = asyncio.get_event_loop().run_until_complete(run_test())

    assert updated is not None
    assert updated.status == IncidentStatus.RESOLVED
    assert updated.resolved_at is not None

    print(f"✅ 狀態更新成功:")
    print(f"   - Status: {updated.status.value}")
    print(f"   - Resolved At: {updated.resolved_at}")


def test_incident_engine_no_external_deps():
    """測試：IncidentEngine 不依賴任何外部模組"""
    import importlib
    import lewooogo_brain.engines.incident_engine as module

    # 取得所有 import
    source = Path(module.__file__).read_text()

    # 禁止的 import patterns
    forbidden = [
        "from src.core",
        "from src.db",
        "from src.services",
        "import redis",
        "from redis",
        "import sqlalchemy",
        "from sqlalchemy",
    ]

    violations = []
    for pattern in forbidden:
        if pattern in source:
            violations.append(pattern)

    assert len(violations) == 0, f"Found forbidden imports: {violations}"
    print("✅ 無外部依賴，完全積木化")


if __name__ == "__main__":
    print("=" * 60)
    print("🧪 IncidentEngine 單元測試")
    print("=" * 60)

    tests = [
        test_incident_engine_import,
        test_incident_engine_create_incident,
        test_incident_engine_aggregate_signals,
        test_incident_engine_deduplication,
        test_incident_engine_update_status,
        test_incident_engine_no_external_deps,
    ]

    passed = 0
    failed = 0

    for test in tests:
        print(f"\n🔬 {test.__name__}")
        try:
            test()
            passed += 1
        except AssertionError as e:
            print(f"❌ FAILED: {e}")
            failed += 1
        except Exception as e:
            print(f"❌ ERROR: {type(e).__name__}: {e}")
            failed += 1

    print("\n" + "=" * 60)
    print(f"📊 結果: {passed} 通過, {failed} 失敗")
    print("=" * 60)

    if failed > 0:
        sys.exit(1)
