"""
AWOOOI AIOps Phase 5 — Blast Radius Calculator（爆炸半徑計算器）
===============================================================
職責：計算修復動作的爆炸半徑分數（0-100），決定執行分級。

分級邏輯：
  ≤ 10  → auto       自動執行（低衝擊）
  11-50 → human      需一人審核（中衝擊）
  51-99 → dual       需雙人審核 + GitOps PR（高衝擊）
  100   → blocked    HARD_RULES 永擋（任何情況不執行）

設計原則：
- 保守計分：不確定情境一律視為高分（> 50）
- HARD_RULES 優先：任何永擋 pattern 立刻返回 100，不繼續計算
- 純函數（Stateless）：不依賴 DB/Redis，確保呼叫端可同步執行
- 可審計：每次計算回傳 reason 記錄計分依據

ADR-086: Phase 5 Declarative 修復與 Blast Radius 分控
2026-04-15 ogt + Claude Sonnet 4.6（亞太）: Phase 5 初始建立
"""

from __future__ import annotations

import re
from dataclasses import dataclass

import structlog

logger = structlog.get_logger(__name__)

# ── 分級閾值 ──────────────────────────────────────────────────────────────────
TIER_AUTO_MAX = 10        # ≤ 10 → auto
TIER_HUMAN_MAX = 50       # 11-50 → human
TIER_DUAL_MAX = 99        # 51-99 → dual
BLAST_BLOCKED = 100       # = 100 → permanent block

# ── 基礎分（Kubectl 動作類型）────────────────────────────────────────────────
_BASE_SCORES: list[tuple[str, int, str]] = [
    # (regex pattern, base_score, reason)
    # 唯讀查詢指令（零衝擊，優先匹配避免被 default=50 吃掉）
    # 2026-04-24 ogt + Claude Sonnet 4.6: Gate 9 修復 — kubectl get/top/describe/logs 補 score=1
    (r"kubectl\s+get\b",                      1, "get 唯讀查詢（零衝擊）"),
    (r"kubectl\s+top\b",                      1, "top 唯讀查詢（零衝擊）"),
    (r"kubectl\s+describe\b",                 1, "describe 唯讀查詢（零衝擊）"),
    (r"kubectl\s+logs\b",                     1, "logs 唯讀查詢（零衝擊）"),
    (r"kubectl\s+version\b",                  1, "version 唯讀（零衝擊）"),
    (r"kubectl\s+rollout\s+restart",         10, "rollout restart 低衝擊"),
    (r"kubectl\s+rollout\s+undo",            25, "rollout undo 中衝擊（版本回退）"),
    (r"kubectl\s+scale.*--replicas=[1-9]",   15, "scale up/down 低中衝擊"),
    (r"kubectl\s+scale.*--replicas=0",       60, "scale to zero 高衝擊（停服）"),
    (r"kubectl\s+apply",                     40, "apply 中高衝擊（配置變更）"),
    (r"kubectl\s+patch",                     45, "patch 中高衝擊"),
    (r"kubectl\s+set\s+image",               35, "set image 中衝擊"),
    (r"kubectl\s+delete\s+pod",              30, "delete pod 中衝擊（Pod 重建）"),
    (r"kubectl\s+delete\s+deployment",       75, "delete deployment 高衝擊"),
    (r"kubectl\s+delete\s+service",          70, "delete service 高衝擊（流量中斷）"),
    (r"kubectl\s+delete\s+namespace",        BLAST_BLOCKED, "delete namespace 永擋"),
    (r"kubectl\s+delete\s+pv\b",             BLAST_BLOCKED, "delete PV 永擋（資料遺失）"),
    (r"kubectl\s+delete\s+pvc\b",            BLAST_BLOCKED, "delete PVC 永擋（資料遺失）"),
    (r"kubectl\s+delete\s+clusterrole",      BLAST_BLOCKED, "delete ClusterRole 永擋（RBAC 毀損）"),
    (r"kubectl\s+delete\s+secret",           80, "delete secret 高衝擊"),
    (r"kubectl\s+delete\s+configmap",        55, "delete configmap 高衝擊"),
    (r"kubectl\s+exec",                      65, "exec 高衝擊（互動式執行）"),
    (r"kubectl\s+cp\b",                      50, "cp 中高衝擊"),
    (r"kubectl\s+drain",                     80, "drain node 高衝擊"),
    (r"kubectl\s+cordon",                    55, "cordon node 高衝擊"),
    (r"kubectl\s+taint",                     60, "taint 高衝擊"),
]

# ── 永擋命令清單（不含 kubectl 的危險操作）───────────────────────────────────
_HARD_BLOCK_PATTERNS: list[tuple[str, str]] = [
    (r"rm\s+-rf",                      "rm -rf 永擋（資料刪除）"),
    (r"DROP\s+TABLE",                  "DROP TABLE 永擋（DB 資料刪除）"),
    (r"DROP\s+DATABASE",               "DROP DATABASE 永擋"),
    (r"TRUNCATE\s+TABLE",              "TRUNCATE TABLE 永擋"),
    (r"kubectl\s+exec.*?--.*?rm\b",    "kubectl exec rm 永擋"),
    (r"kubectl\s+exec.*?--.*?kill\b",  "kubectl exec kill 永擋"),
]

# ── 高風險命名空間 ──────────────────────────────────────────────────────────
_CRITICAL_NAMESPACES = {"kube-system", "kube-public", "kube-node-lease", "monitoring", "gitea"}


# ─────────────────────────────────────────────────────────────────────────────
# Data Types
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class BlastRadiusResult:
    """爆炸半徑計算結果"""
    score: int                  # 0-100（100 = 永擋）
    tier: str                   # "auto" / "human" / "dual" / "blocked"
    reason: str                 # 計分依據（可審計）
    hard_blocked: bool          # True = HARD_RULES 永擋
    blocked_reason: str | None  # 永擋時的具體原因


# ─────────────────────────────────────────────────────────────────────────────
# Calculator
# ─────────────────────────────────────────────────────────────────────────────

class BlastRadiusCalculator:
    """
    爆炸半徑計算器

    Usage:
        calc = BlastRadiusCalculator()
        result = calc.calculate(action="kubectl rollout restart deployment/awoooi-api",
                                namespace="awoooi-prod")
        if result.tier == "auto":
            # 可自動執行
    """

    def calculate(
        self,
        action: str,
        namespace: str = "awoooi-prod",
        target: str = "",
    ) -> BlastRadiusResult:
        """
        計算動作的爆炸半徑分數。

        Args:
            action:    修復命令（kubectl command or description）
            namespace: 目標命名空間
            target:    目標資源名稱

        Returns:
            BlastRadiusResult（包含分數、分級、計分依據）
        """
        action_lower = action.lower()
        reasons: list[str] = []

        # 1. HARD_RULES 永擋優先檢查
        for pattern, block_reason in _HARD_BLOCK_PATTERNS:
            if re.search(pattern, action, re.IGNORECASE):
                logger.warning(
                    "blast_radius_hard_blocked",
                    action=action[:120],
                    reason=block_reason,
                )
                return BlastRadiusResult(
                    score=BLAST_BLOCKED,
                    tier="blocked",
                    reason=block_reason,
                    hard_blocked=True,
                    blocked_reason=block_reason,
                )

        # 2. 基礎分（依 kubectl 動作類型）
        base_score = 50  # 保守預設：未知動作 = 50（human tier）
        matched_base_reason = "未知 kubectl 動作，保守 50 分"

        for pattern, score, reason in _BASE_SCORES:
            if re.search(pattern, action, re.IGNORECASE):
                if score == BLAST_BLOCKED:
                    return BlastRadiusResult(
                        score=BLAST_BLOCKED,
                        tier="blocked",
                        reason=reason,
                        hard_blocked=True,
                        blocked_reason=reason,
                    )
                base_score = score
                matched_base_reason = reason
                break

        reasons.append(f"基礎分 {base_score}：{matched_base_reason}")

        # 3. 命名空間倍率
        ns_multiplier = 1.0
        if namespace in _CRITICAL_NAMESPACES:
            ns_multiplier = 2.5
            reasons.append(f"命名空間 {namespace} × 2.5（系統級）")
        elif namespace == "default":
            ns_multiplier = 1.8
            reasons.append("default 命名空間 × 1.8（全域影響）")

        # 4. 追加修正（replicas=0 已在 BASE_SCORES，其他特殊情境）
        bonus = 0
        if "--force" in action_lower:
            bonus += 20
            reasons.append("+20：--force flag 危險")
        if "kube-system" in action_lower:
            bonus += 40
            reasons.append("+40：kube-system 目標")
        if "all" in action_lower and "kubectl delete" in action_lower:
            bonus += 30
            reasons.append("+30：delete all 批量刪除")

        # 5. 最終分數
        raw_score = base_score * ns_multiplier + bonus
        final_score = min(int(raw_score), 99)  # 保留 100 給 HARD_RULES

        tier = _score_to_tier(final_score)
        reasons.append(f"最終分 {final_score} → {tier}")

        reason_str = "；".join(reasons)
        logger.debug(
            "blast_radius_calculated",
            score=final_score,
            tier=tier,
            action=action[:80],
            reason=reason_str,
        )
        return BlastRadiusResult(
            score=final_score,
            tier=tier,
            reason=reason_str,
            hard_blocked=False,
            blocked_reason=None,
        )


# ─────────────────────────────────────────────────────────────────────────────
# Helpers
# ─────────────────────────────────────────────────────────────────────────────

def _score_to_tier(score: int) -> str:
    if score <= TIER_AUTO_MAX:
        return "auto"
    elif score <= TIER_HUMAN_MAX:
        return "human"
    elif score <= TIER_DUAL_MAX:
        return "dual"
    else:
        return "blocked"


# ─────────────────────────────────────────────────────────────────────────────
# Singleton
# ─────────────────────────────────────────────────────────────────────────────

_calculator: BlastRadiusCalculator | None = None


def get_blast_radius_calculator() -> BlastRadiusCalculator:
    global _calculator
    if _calculator is None:
        _calculator = BlastRadiusCalculator()
    return _calculator
