"""
Rule → Playbook Migrator
========================
將 alert_rules.yaml 中的 25 條規則遷移為 DRAFT Playbook，讓飛輪 RAG 有料可查。

設計原則:
- status=DRAFT（不直接 APPROVED — 違反「禁寫死」鐵律）
- ai_confidence=0.3（誠實標示，非假 1.0 — 違反 feedback_confidence_truthfulness）
- source=PlaybookSource.YAML_RULE（現有 enum，不新增 RULE_MIGRATED）
- 冪等：name LIKE 'AutoMigrated: %' 已存在則跳過
- INSERT ON CONFLICT → repo.create() UPSERT（playbook_id 唯一鍵）
- 與 playbook_seed_service.py 完全解耦（不擾動既有 seed 機制）

name 格式: "AutoMigrated: {rule.id}" — 與 seed_service 用 description 作 name 的格式區隔

W1 PR-R1 — 規則 → Playbook 遷移
2026-04-28 ogt + Claude Sonnet 4.6
"""
from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import structlog
import yaml

logger = structlog.get_logger(__name__)

# 告警 severity → risk 等級
_SEVERITY_TO_RISK: dict[str, str] = {
    "low": "LOW",
    "medium": "MEDIUM",
    "high": "HIGH",
    "critical": "CRITICAL",
}

# yaml risk 欄位允許 "high" 但 RiskLevel enum 有 HIGH；seed_service 用的 map 少了 high
_YAML_RISK_MAP: dict[str, str] = {
    "low": "LOW",
    "medium": "MEDIUM",
    "high": "HIGH",
    "critical": "CRITICAL",
}


@dataclass
class MigrationReport:
    """遷移報告"""
    total_rules: int = 0
    created: int = 0
    skipped: int = 0
    failed: int = 0
    dry_run: bool = False
    errors: list[str] = field(default_factory=list)
    created_names: list[str] = field(default_factory=list)
    skipped_names: list[str] = field(default_factory=list)

    def summary(self) -> str:
        mode = "[DRY-RUN] " if self.dry_run else ""
        return (
            f"{mode}遷移完成 — "
            f"總計 {self.total_rules} 條規則，"
            f"建立 {self.created}，跳過 {self.skipped}，失敗 {self.failed}"
        )


# =============================================================================
# 命令類型判斷（不依賴 SPF-2 action_parser，用既有 regex 守門）
# =============================================================================

def _infer_action_type(kubectl_command: str) -> str:
    """
    從指令字串推斷 ActionType（字串形式，對應 ActionType enum 值）

    規則:
    - 空字串 → "manual"
    - 以 "ssh " 開頭 → "ssh_command"
    - 其他有指令 → "kubectl"
    """
    cmd = (kubectl_command or "").strip()
    if not cmd:
        return "manual"
    if cmd.startswith("ssh "):
        return "ssh_command"
    return "kubectl"


def _infer_risk_level(risk_str: str) -> str:
    """
    YAML risk 欄位 → RiskLevel 字串

    alert_rules.yaml 的 risk 欄位值: low / medium / high / critical
    """
    return _YAML_RISK_MAP.get((risk_str or "medium").lower(), "MEDIUM")


def _build_symptom_pattern(rule: dict[str, Any]) -> dict[str, Any]:
    """
    從規則 match block 推導 SymptomPattern dict

    symptom_pattern 包含:
    - alert_names: match.alertname list
    - affected_services: 從 id/description 推導關鍵字（保守策略：留空，讓 RAG 學習）
    - severity_range: 從 risk 反推 ["P1"] / ["P2"] / ["P3"]
    - keywords: match.message list（部分匹配關鍵字）
    """
    match_block = rule.get("match", {})
    alertnames: list[str] = match_block.get("alertname", [])
    messages: list[str] = match_block.get("message", [])
    alert_types: list[str] = match_block.get("alert_type", [])

    # risk → severity_range 反推
    risk_str = (rule.get("response", {}).get("risk", "medium") or "medium").lower()
    if risk_str == "critical":
        severity_range = ["P1", "P2"]
    elif risk_str in ("high", "medium"):
        severity_range = ["P2", "P3"]
    else:
        severity_range = ["P3"]

    # keywords: message + alert_type 列表合併（最多 15 個）
    keywords = list(messages) + list(alert_types)
    # 過濾萬用符（generic_fallback 有 "*"）
    keywords = [k for k in keywords if k != "*"][:15]

    # 2026-04-29 ogt + Claude Opus 4.7: critic Major #1 修
    # 過濾 alert_names 中的 "*" wildcard（generic_fallback）— 否則進 RAG 向量化
    # 後變成「告警: *」污染語料，每筆查詢都會跟它算相似度
    raw_names = alertnames if isinstance(alertnames, list) else [alertnames]
    filtered_names = [n for n in raw_names if n and n != "*"]
    return {
        "alert_names": filtered_names,
        "affected_services": [],
        "severity_range": severity_range,
        "keywords": keywords,
        "label_patterns": {},
    }


def _build_repair_steps(rule: dict[str, Any]) -> list[dict[str, Any]]:
    """
    從規則 response block 建立 RepairStep dict list

    策略:
    - kubectl_command 存在且非空 → step 1
    - 若 optimization list 存在 → 每項追加為額外步驟
    - 若 kubectl_command 空 (NO_ACTION) → step 1 action_type=manual，command=描述文字
    """
    resp = rule.get("response", {})
    kubectl_cmd = (resp.get("kubectl_command", "") or "").strip()
    risk_level = _infer_risk_level(resp.get("risk", "medium"))
    suggested_action = resp.get("suggested_action", "NO_ACTION") or "NO_ACTION"

    steps: list[dict[str, Any]] = []

    if kubectl_cmd:
        action_type = _infer_action_type(kubectl_cmd)
        steps.append({
            "step_number": 1,
            "action_type": action_type,
            "command": kubectl_cmd,
            "expected_result": resp.get("action_title", ""),
            "risk_level": risk_level,
            "requires_approval": risk_level == "CRITICAL" or suggested_action in ("RESTART_DEPLOYMENT", "DELETE_POD", "SCALE_DEPLOYMENT"),
            # 2026-04-29 ogt + Claude Opus 4.7: critic Major #3 修
            # yaml_rule 來源的 kubectl_command 未經 SPF-2 action_parser 驗證
            # promote 流程（DRAFT → APPROVED）必須強制走 action_parser，否則危險指令直達 prod
            "metadata": {
                "unverified_command": True,
                "needs_action_parser_review": True,
                "source": "yaml_rule_migration",
            },
        })
    else:
        # NO_ACTION — 記錄診斷描述為 manual step，讓 RAG 至少有症狀可查
        description_text = resp.get("description", rule.get("description", "人工診斷"))
        steps.append({
            "step_number": 1,
            "action_type": "manual",
            "command": description_text[:500],
            "expected_result": resp.get("action_title", ""),
            "risk_level": risk_level,
            "requires_approval": True,
        })

    # 追加 optimization steps（最多 3 個，step_number 2/3/4）
    # 2026-04-29 critic Minor 修：原 `if idx >= 4: break` 寫在 append 後易誤讀
    # 改用 [:3] slice 明確限制最多 3 個
    for idx, opt in enumerate((resp.get("optimization", []) or [])[:3], start=2):
        opt_cmd = (opt.get("command", "") or "").strip()
        if not opt_cmd or opt_cmd.startswith("#"):
            continue
        steps.append({
            "step_number": idx,
            "action_type": _infer_action_type(opt_cmd),
            "command": opt_cmd,
            "expected_result": opt.get("description", ""),
            "risk_level": "LOW",
            "requires_approval": False,
        })

    return steps


def _estimated_duration(risk_level: str, suggested_action: str) -> int:
    """估算修復時間（分鐘）"""
    if suggested_action in ("NO_ACTION",):
        return 15
    if risk_level == "CRITICAL":
        return 5
    return 3


def _build_tags(rule: dict[str, Any]) -> list[str]:
    """從規則提取標籤"""
    tags: set[str] = {"yaml_rule", "auto_migrated"}

    rule_id = rule.get("id", "")
    resp = rule.get("response", {})
    responsibility = resp.get("responsibility", "")
    if responsibility:
        tags.add(responsibility.lower())

    # 從 alertname 推導類型標籤
    alertnames = rule.get("match", {}).get("alertname", [])
    for name in alertnames:
        name_lower = (name or "").lower()
        if "cpu" in name_lower:
            tags.add("cpu")
        if "memory" in name_lower or "oom" in name_lower:
            tags.add("memory")
        if "disk" in name_lower or "storage" in name_lower:
            tags.add("disk")
        if "pod" in name_lower or "k8s" in name_lower or "kube" in name_lower:
            tags.add("kubernetes")
        if "ssl" in name_lower or "cert" in name_lower:
            tags.add("ssl")
        if "backup" in name_lower:
            tags.add("backup")
        if "postgresql" in name_lower or "postgres" in name_lower:
            tags.add("database")
        if "redis" in name_lower:
            tags.add("cache")
        if "ollama" in name_lower:
            tags.add("ai")

    return list(tags)[:10]


def parse_yaml_rules(yaml_path: Path) -> list[dict[str, Any]]:
    """
    讀取並解析 alert_rules.yaml，回傳 rules list

    Raises:
        FileNotFoundError: yaml 不存在
        yaml.YAMLError: yaml 格式錯誤
    """
    data = yaml.safe_load(yaml_path.read_text(encoding="utf-8"))
    rules = data.get("rules", [])
    return [r for r in rules if isinstance(r, dict)]


def build_playbook_dict(rule: dict[str, Any]) -> dict[str, Any]:
    """
    從單條規則建立 Playbook 初始化 dict（不寫 DB）

    Returns dict 可直接傳給 Playbook(**dict)
    """
    rule_id = rule.get("id", "unknown")
    resp = rule.get("response", {})
    description = resp.get("description", rule.get("description", f"規則 {rule_id} 自動遷移"))
    risk_str = (resp.get("risk", "medium") or "medium").lower()
    suggested_action = resp.get("suggested_action", "NO_ACTION") or "NO_ACTION"

    symptom_pattern = _build_symptom_pattern(rule)
    repair_steps = _build_repair_steps(rule)
    tags = _build_tags(rule)
    risk_level = _infer_risk_level(risk_str)
    duration = _estimated_duration(risk_level, suggested_action)

    return {
        "name": f"AutoMigrated: {rule_id}",
        "description": description[:2000],
        "status": "draft",
        "source": "yaml_rule",
        "symptom_pattern": symptom_pattern,
        "repair_steps": repair_steps,
        "estimated_duration_minutes": duration,
        "ai_confidence": 0.3,
        "trust_score": 0.3,
        "tags": tags,
        "notes": f"自動從 alert_rules.yaml rule.id={rule_id} 遷移。priority={rule.get('priority', 999)}",
        "created_by_agent": "migrator",
    }


# =============================================================================
# 核心遷移函式（async，依賴 DB）
# =============================================================================

async def migrate_yaml_rules_to_playbooks(
    yaml_path: Path,
    dry_run: bool = True,
    enable_migration: bool = True,
) -> MigrationReport:
    """
    將 alert_rules.yaml 遷移為 DRAFT Playbook

    Args:
        yaml_path: alert_rules.yaml 路徑
        dry_run: True=只印計畫不寫 DB，False=真實寫入
        enable_migration: feature flag（ENABLE_RULE_MIGRATION_DRAFT），False 時直接 return

    Returns:
        MigrationReport

    設計：
    - 冪等：name LIKE 'AutoMigrated: %' 已存在任何狀態的 playbook 即跳過
    - 不依賴 seed_service（source=yaml_rule 但 name prefix 不同，互不干擾）
    - generic_fallback 規則（id=generic_fallback）也遷移，讓 RAG 能學到「兜底症狀」
    """
    report = MigrationReport(dry_run=dry_run)

    if not enable_migration:
        logger.info("rule_migration_disabled_by_flag")
        return report

    if not yaml_path.exists():
        logger.error("rule_migration_yaml_not_found", path=str(yaml_path))
        report.errors.append(f"yaml 不存在: {yaml_path}")
        return report

    # 1. 解析 yaml
    try:
        rules = parse_yaml_rules(yaml_path)
    except Exception as e:
        logger.error("rule_migration_parse_error", error=str(e))
        report.errors.append(f"yaml 解析失敗: {e}")
        return report

    report.total_rules = len(rules)

    if dry_run:
        # Dry-run：只建立 dict，不查 DB、不寫 DB
        for rule in rules:
            rule_id = rule.get("id", "unknown")
            try:
                pb_dict = build_playbook_dict(rule)
                report.created_names.append(pb_dict["name"])
                report.created += 1
                logger.info(
                    "rule_migration_dry_run_would_create",
                    rule_id=rule_id,
                    name=pb_dict["name"],
                    alert_names=pb_dict["symptom_pattern"]["alert_names"],
                )
            except Exception as e:
                report.failed += 1
                report.errors.append(f"rule_id={rule_id} 建立 dict 失敗: {e}")
                logger.warning("rule_migration_dry_run_error", rule_id=rule_id, error=str(e))
        return report

    # 2. 查詢現有 AutoMigrated Playbook（冪等去重）
    from src.db.base import get_db_context
    from sqlalchemy import text as sa_text

    async with get_db_context() as db:
        rows = await db.execute(
            sa_text("SELECT name FROM playbooks WHERE name LIKE 'AutoMigrated: %'")
        )
        existing_names: set[str] = {r[0] for r in rows.fetchall()}

    # 3. 逐條遷移
    from src.models.playbook import Playbook
    from src.repositories.playbook_repository import get_playbook_repository

    repo = get_playbook_repository()

    for rule in rules:
        rule_id = rule.get("id", "unknown")
        try:
            pb_dict = build_playbook_dict(rule)
            name = pb_dict["name"]

            if name in existing_names:
                report.skipped += 1
                report.skipped_names.append(name)
                logger.debug("rule_migration_skip_existing", rule_id=rule_id, name=name)
                continue

            playbook = Playbook(**pb_dict)
            await repo.create(playbook)
            report.created += 1
            report.created_names.append(name)
            existing_names.add(name)  # 防止同 session 重複建立

            logger.info(
                "rule_migration_created",
                rule_id=rule_id,
                playbook_id=playbook.playbook_id,
                name=name,
            )

        except Exception as e:
            report.failed += 1
            report.errors.append(f"rule_id={rule_id} 失敗: {e}")
            logger.warning("rule_migration_create_error", rule_id=rule_id, error=str(e))

    logger.info(
        "rule_migration_complete",
        total=report.total_rules,
        created=report.created,
        skipped=report.skipped,
        failed=report.failed,
    )
    return report
