"""
IncidentEngine - 事件處理引擎 (積木化版本)
==========================================

Phase 6.4e: 從 apps/api/src/services/incident_engine.py 搬遷

設計原則:
- 依賴注入: 透過建構子注入 IMemoryProvider
- 無外部耦合: 禁止直接引用 redis_client 或 db
- 可測試性: 可注入 Mock Provider 進行單元測試

統帥鐵律:
- 禁止告警風暴 (相關告警必須聚合)
- 禁止 O(N) 掃描 (所有查詢必須 O(1))
- 禁止 Race Condition (所有寫入必須原子操作)
"""

from datetime import datetime, timezone, timedelta
from typing import Any, Protocol, Callable
from uuid import uuid4
import hashlib
import json

from lewooogo_brain.interfaces.incident_processor import (
    IIncidentProcessor,
    Incident,
    IncidentStatus,
    Severity,
    Signal,
)


# =============================================================================
# Memory Provider Protocol (依賴注入用)
# =============================================================================

class IIncidentMemory(Protocol):
    """Incident 專用記憶體提供者協定"""

    async def load_incident(self, incident_id: str) -> Incident | None:
        """從 Working Memory 載入 Incident"""
        ...

    async def save_incident(self, incident: Incident, ttl_seconds: int = 604800) -> bool:
        """儲存 Incident 到 Working Memory (預設 7 天 TTL)"""
        ...

    async def persist_incident(self, incident: Incident) -> bool:
        """持久化到 Episodic Memory (PostgreSQL)"""
        ...

    async def find_related_incident(
        self,
        namespace: str,
        target: str,
        window_minutes: int = 30,
    ) -> Incident | None:
        """尋找相關的活躍 Incident (用於聚合)"""
        ...

    async def update_index(
        self,
        incident_id: str,
        namespace: str,
        target: str,
    ) -> bool:
        """更新反向索引 (namespace/target → incident_id)"""
        ...


class IBlastRadiusAnalyzer(Protocol):
    """爆炸半徑分析器協定"""

    def analyze(self, target: str) -> list[str]:
        """分析受影響的服務列表"""
        ...


# =============================================================================
# Constants
# =============================================================================

AGGREGATION_WINDOW_MINUTES = 30
WORKING_MEMORY_TTL = 604800  # 7 days


# =============================================================================
# IncidentEngine Implementation
# =============================================================================

class IncidentEngine(IIncidentProcessor):
    """
    事件處理引擎

    職責:
    1. 聚合相關告警到同一 Incident
    2. 分析爆炸半徑
    3. 雙層持久化 (Working + Episodic Memory)

    使用方式:
        memory = DualIncidentMemory(redis_client, db_session)
        analyzer = GraphBlastRadiusAnalyzer(topology_graph)
        engine = IncidentEngine(memory, analyzer)

        incident = await engine.process_signal(signal_data)
    """

    def __init__(
        self,
        memory: IIncidentMemory,
        blast_analyzer: IBlastRadiusAnalyzer | None = None,
        logger: Any | None = None,
    ):
        """
        初始化 IncidentEngine

        Args:
            memory: 記憶體提供者 (Working + Episodic)
            blast_analyzer: 爆炸半徑分析器 (可選)
            logger: 日誌記錄器 (可選)
        """
        self._memory = memory
        self._blast_analyzer = blast_analyzer
        self._logger = logger

    def _log(self, event: str, **kwargs) -> None:
        """記錄日誌 (如果有 logger)"""
        if self._logger:
            self._logger.info(event, **kwargs)

    async def process_signal(
        self,
        signal_data: dict[str, Any],
    ) -> Incident | None:
        """
        處理告警信號

        流程:
        1. 解析 Signal
        2. 計算 Fingerprint (去重用)
        3. 查找相關 Incident (聚合)
        4. 創建或更新 Incident
        5. 分析爆炸半徑
        6. 雙層持久化
        """
        try:
            # Step 1: 解析 Signal
            signal = self._parse_signal(signal_data)
            namespace = signal_data.get("namespace", "default")
            target = signal_data.get("target", "unknown")

            # Step 2: 計算 Fingerprint
            fingerprint = self._compute_fingerprint(signal_data)
            signal.fingerprint = fingerprint

            # Step 3: 查找相關 Incident
            existing = await self._memory.find_related_incident(
                namespace=namespace,
                target=target,
                window_minutes=AGGREGATION_WINDOW_MINUTES,
            )

            if existing:
                # 聚合到現有 Incident
                incident = await self._aggregate_signal(existing, signal)
            else:
                # 創建新 Incident
                incident = await self._create_incident(signal, namespace, target)

            # Step 4: 分析爆炸半徑
            if self._blast_analyzer and target not in incident.affected_services:
                affected = self._blast_analyzer.analyze(target)
                incident.affected_services = list(set(incident.affected_services + affected))

            # Step 5: 雙層持久化
            await self._memory.save_incident(incident, WORKING_MEMORY_TTL)
            await self._memory.update_index(incident.incident_id, namespace, target)
            persisted = await self._memory.persist_incident(incident)

            self._log(
                "signal_processed",
                incident_id=incident.incident_id,
                signal_count=len(incident.signals),
                persisted_to_pg=persisted,
            )

            return incident

        except Exception as e:
            self._log("signal_processing_error", error=str(e))
            return None

    async def get_incident(self, incident_id: str) -> Incident | None:
        """取得 Incident"""
        return await self._memory.load_incident(incident_id)

    async def update_status(
        self,
        incident_id: str,
        status: IncidentStatus,
    ) -> bool:
        """更新 Incident 狀態"""
        incident = await self._memory.load_incident(incident_id)
        if not incident:
            return False

        incident.status = status
        incident.updated_at = datetime.now(timezone.utc)

        if status == IncidentStatus.RESOLVED:
            incident.resolved_at = datetime.now(timezone.utc)
        elif status == IncidentStatus.CLOSED:
            incident.closed_at = datetime.now(timezone.utc)

        await self._memory.save_incident(incident, WORKING_MEMORY_TTL)
        await self._memory.persist_incident(incident)

        return True

    # =========================================================================
    # Private Methods
    # =========================================================================

    def _parse_signal(self, data: dict[str, Any]) -> Signal:
        """解析 Signal 資料"""
        severity_map = {
            "critical": Severity.P0,
            "warning": Severity.P2,
            "info": Severity.P3,
        }

        severity_str = data.get("severity", "warning")
        severity = severity_map.get(severity_str, Severity.P2)

        return Signal(
            alert_name=data.get("alert_name", "Unknown"),
            severity=severity,
            source=data.get("source", "unknown"),
            fired_at=datetime.now(timezone.utc),
            labels=data.get("labels", {}) if isinstance(data.get("labels"), dict) else {},
            annotations=data.get("annotations", {}) if isinstance(data.get("annotations"), dict) else {},
        )

    def _compute_fingerprint(self, data: dict[str, Any]) -> str:
        """計算 Signal Fingerprint (用於去重)"""
        key_parts = [
            data.get("source", ""),
            data.get("alert_name", ""),
            data.get("namespace", ""),
            data.get("target", ""),
        ]
        key_str = ":".join(key_parts)
        return hashlib.sha256(key_str.encode()).hexdigest()[:16]

    async def _create_incident(
        self,
        signal: Signal,
        namespace: str,
        target: str,
    ) -> Incident:
        """創建新 Incident"""
        incident_id = f"INC-{datetime.now(timezone.utc).strftime('%Y%m%d')}-{uuid4().hex[:6].upper()}"

        incident = Incident(
            incident_id=incident_id,
            status=IncidentStatus.INVESTIGATING,
            severity=signal.severity,
            signals=[signal],
            affected_services=[target] if target != "unknown" else [],
            created_at=datetime.now(timezone.utc),
            updated_at=datetime.now(timezone.utc),
        )

        self._log(
            "incident_created",
            incident_id=incident_id,
            severity=signal.severity.value,
            namespace=namespace,
            target=target,
        )

        return incident

    async def _aggregate_signal(
        self,
        incident: Incident,
        signal: Signal,
    ) -> Incident:
        """聚合 Signal 到現有 Incident"""
        # 檢查重複 (Fingerprint)
        existing_fingerprints = {s.fingerprint for s in incident.signals if s.fingerprint}
        if signal.fingerprint and signal.fingerprint in existing_fingerprints:
            self._log(
                "signal_deduplicated",
                incident_id=incident.incident_id,
                fingerprint=signal.fingerprint,
            )
            return incident

        # 聚合
        incident.signals.append(signal)
        incident.updated_at = datetime.now(timezone.utc)

        # 嚴重度升級 (取最高)
        if signal.severity.value < incident.severity.value:
            incident.severity = signal.severity

        self._log(
            "signal_aggregated",
            incident_id=incident.incident_id,
            signal_count=len(incident.signals),
            severity=incident.severity.value,
        )

        return incident
