"""
Similarity Calculation Utils
=============================
Phase 22 P2: 將相似度計算邏輯從 Repository 移出

設計原則:
- 演算法邏輯應獨立於資料存取層
- Repository 只負責 CRUD，不負責演算法
- Service 層可以使用這些工具函數

版本: v1.1
建立: 2026-03-31 (台北時區)
建立者: Claude Code (首席架構師技術債修復)
更新: 2026-04-10 (台北時區) Claude Sonnet 4.6
  - Phase 3 飛輪修復: affected_services 空集合豁免
    Playbook.affected_services=[] 代表通用型基礎設施 Playbook，
    不針對特定服務（如 high-cpu-restart 適用所有主機 CPU 告警），
    給予 1.0 豁免分，不因服務名不匹配而拉低整體相似度。
  - severity 豁免: Playbook.severity_range=[] 代表適用所有嚴重度
"""

from src.models.playbook import SymptomPattern


def calculate_jaccard_similarity(set_a: set, set_b: set) -> float:
    """
    計算 Jaccard 相似度

    Jaccard = |A ∩ B| / |A ∪ B|

    Args:
        set_a: 集合 A
        set_b: 集合 B

    Returns:
        float: 0.0 ~ 1.0
    """
    if not set_a and not set_b:
        return 1.0  # 兩個空集合視為完全相同
    if not set_a or not set_b:
        return 0.0

    intersection = len(set_a & set_b)
    union = len(set_a | set_b)
    return intersection / union


def calculate_symptom_similarity(
    pattern_a: SymptomPattern,
    pattern_b: SymptomPattern,
) -> float:
    """
    計算症狀相似度

    算法: 加權 Jaccard 相似度 + 通用型 Playbook 豁免

    維度權重:
    - alert_names: 0.35 (最重要)
    - affected_services: 0.30
    - severity: 0.15
    - keywords: 0.20

    豁免規則 (Phase 3 飛輪修復, 2026-04-10):
    - pattern_b.affected_services 為空 → 通用型 Playbook，services 維度給 1.0
      （high-cpu-restart、crashloop-pod-delete 等基礎設施 Playbook 不針對特定服務）
    - pattern_b.severity_range 為空 → 適用所有嚴重度，severity 維度給 1.0

    Returns:
        float: 0.0 ~ 1.0 相似度分數
    """
    weights = {
        "alert_names": 0.35,
        "affected_services": 0.30,
        "severity": 0.15,
        "keywords": 0.20,
    }

    scores = {
        "alert_names": calculate_jaccard_similarity(
            set(pattern_a.alert_names),
            set(pattern_b.alert_names),
        ),
        # 通用型 Playbook 豁免：Playbook 沒有限定服務 → 任何服務都適用 → 1.0
        "affected_services": (
            1.0
            if not pattern_b.affected_services
            else calculate_jaccard_similarity(
                set(pattern_a.affected_services),
                set(pattern_b.affected_services),
            )
        ),
        # 通用型 Playbook 豁免：Playbook 沒有限定嚴重度 → 任何嚴重度都適用 → 1.0
        "severity": (
            1.0
            if not pattern_b.severity_range
            or bool(set(pattern_a.severity_range) & set(pattern_b.severity_range))
            else 0.0
        ),
        "keywords": calculate_jaccard_similarity(
            set(pattern_a.keywords),
            set(pattern_b.keywords),
        ),
    }

    return sum(weights[k] * scores[k] for k in weights)
