"""
AWOOOI AIOps Phase 6 — KB 腐爛清理 Job
=======================================
職責：月度巡檢知識庫（knowledge_entries）中腐爛的知識條目，
      標記引用了已廢棄資源的條目為 stale，並寫入 ai_governance_events。

「腐爛」的三種形態：
  ROT-1  廢棄 K8s API 版本引用（extensions/v1beta1、apps/v1beta1、v1beta2）
  ROT-2  過時 Prometheus query pattern（已知廢棄 metric 名稱前綴）
  ROT-3  超過 180 天未被引用且成功率為 0 的 incident_case 條目

設計原則：
1. 只讀掃描 + 標記（不刪除任何 entry，符合 archive_not_delete 鐵律）
2. 標記方式：status = 'archived' + tags 追加 'kb_rot_detected'
3. 掃描失敗 → 記錄 error，不拋出，不影響主路徑
4. 每次執行結果寫 ai_governance_events（event_type=kb_stale）

ADR-087: AI 自我治理閉環
2026-04-15 ogt + Claude Sonnet 4.6（亞太）: Phase 6 初始建立
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from datetime import timedelta

import structlog
from sqlalchemy import select, update

from src.db.base import get_db_context
from src.db.models import AiGovernanceEvent, KnowledgeEntryRecord
from src.utils.timezone import now_taipei

logger = structlog.get_logger(__name__)

# ─────────────────────────────────────────────────────────────────────────────
# 腐爛偵測規則（不可寫死 action，只標記 stale）
# ─────────────────────────────────────────────────────────────────────────────

# ROT-1: 廢棄 K8s API 版本（Kubernetes 1.16+ 已移除）
DEPRECATED_K8S_APIS = [
    "extensions/v1beta1",
    "apps/v1beta1",
    "apps/v1beta2",
    "networking.k8s.io/v1beta1",
    "policy/v1beta1",
    "rbac.authorization.k8s.io/v1beta1",
]

# ROT-2: 廢棄 Prometheus metric 前綴（已知改名的 metric pattern）
DEPRECATED_PROM_PATTERNS = [
    r"container_cpu_used_total",          # → container_cpu_usage_seconds_total
    r"kube_pod_container_status_restarts$",  # → kube_pod_container_status_restarts_total
    r"http_requests_total\{.*le=",        # 錯誤 histogram 用法
]

# ROT-3: 未引用 + 零成功率條目的老化天數
STALE_AGE_DAYS = 180


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

@dataclass
class RotScanResult:
    """KB 腐爛掃描結果"""
    total_scanned: int
    stale_ids: list[str] = field(default_factory=list)
    rot_reasons: dict[str, list[str]] = field(default_factory=dict)
    # rot_reasons: {entry_id: ["ROT-1: extensions/v1beta1", ...]}
    scanned_at: str = field(default_factory=lambda: now_taipei().isoformat())

    @property
    def stale_count(self) -> int:
        return len(self.stale_ids)

    def to_dict(self) -> dict:
        return {
            "total_scanned": self.total_scanned,
            "stale_count": self.stale_count,
            "stale_ids": self.stale_ids[:50],  # 最多記錄前 50 個
            "rot_reasons_sample": {k: v for k, v in list(self.rot_reasons.items())[:10]},
            "scanned_at": self.scanned_at,
        }


# ─────────────────────────────────────────────────────────────────────────────
# Main Job
# ─────────────────────────────────────────────────────────────────────────────

class KbRotCleaner:
    """
    KB 腐爛清理 Job（月度執行）

    Usage:
        cleaner = KbRotCleaner()
        result = await cleaner.run()
    """

    async def run(self) -> RotScanResult:
        """
        完整執行：掃描 → 標記 stale → 寫 governance event。

        Returns:
            RotScanResult
        """
        from src.core.feature_flags import aiops_flags
        if not aiops_flags.is_sub_flag_enabled("AIOPS_P6_KB_ROT_CLEANER"):
            logger.info("kb_rot_cleaner_skipped_feature_flag")
            return RotScanResult(total_scanned=0)

        try:
            result = await self._scan()
            if result.stale_count > 0:
                await self._mark_stale(result)
                await self._save_event(result)
            else:
                logger.info("kb_rot_scan_clean", total_scanned=result.total_scanned)
            return result
        except Exception as e:
            logger.error("kb_rot_cleaner_error", error=str(e))
            return RotScanResult(total_scanned=0)

    async def _scan(self) -> RotScanResult:
        """掃描所有 approved / draft 條目，找出腐爛項目。"""
        stale_ids: list[str] = []
        rot_reasons: dict[str, list[str]] = {}
        total = 0

        async with get_db_context() as session:
            # 只掃 active 狀態（非 archived）
            q = await session.execute(
                select(KnowledgeEntryRecord).where(
                    KnowledgeEntryRecord.status.in_(["approved", "draft", "review"])
                )
            )
            entries = q.scalars().all()
            total = len(entries)

            stale_cutoff = now_taipei() - timedelta(days=STALE_AGE_DAYS)

            for entry in entries:
                reasons: list[str] = []

                content = (entry.content or "").lower()
                title = (entry.title or "").lower()
                combined = content + " " + title

                # ROT-1: 廢棄 K8s API
                for api in DEPRECATED_K8S_APIS:
                    if api.lower() in combined:
                        reasons.append(f"ROT-1: 廢棄 K8s API {api}")

                # ROT-2: 廢棄 Prometheus pattern
                for pattern in DEPRECATED_PROM_PATTERNS:
                    if re.search(pattern, combined):
                        reasons.append(f"ROT-2: 廢棄 Prom metric pattern {pattern[:40]}")

                # ROT-3: 老化未引用（incident_case 且 180 天未更新）
                if (
                    entry.entry_type == "incident_case"
                    and entry.updated_at < stale_cutoff
                    and entry.view_count == 0
                ):
                    reasons.append(
                        f"ROT-3: 超過 {STALE_AGE_DAYS}d 未引用 "
                        f"(last_updated={entry.updated_at.strftime('%Y-%m-%d')})"
                    )

                if reasons:
                    stale_ids.append(entry.id)
                    rot_reasons[entry.id] = reasons

        logger.info(
            "kb_rot_scan_complete",
            total=total,
            stale_count=len(stale_ids),
        )
        return RotScanResult(
            total_scanned=total,
            stale_ids=stale_ids,
            rot_reasons=rot_reasons,
        )

    async def _mark_stale(self, result: RotScanResult) -> None:
        """
        將腐爛條目標記為 archived，並追加 kb_rot_detected tag。

        符合 archive_not_delete 鐵律：只封存，不刪除。
        """
        if not result.stale_ids:
            return

        async with get_db_context() as session:
            # 逐條更新（避免 bulk update 覆蓋 tags JSONB）
            q = await session.execute(
                select(KnowledgeEntryRecord).where(
                    KnowledgeEntryRecord.id.in_(result.stale_ids)
                )
            )
            entries = q.scalars().all()

            for entry in entries:
                entry.status = "archived"
                tags = list(entry.tags or [])
                if "kb_rot_detected" not in tags:
                    tags.append("kb_rot_detected")
                entry.tags = tags

            await session.commit()

        logger.warning(
            "kb_rot_entries_archived",
            count=len(result.stale_ids),
            entry_ids=result.stale_ids[:10],
        )

    async def _save_event(self, result: RotScanResult) -> None:
        """寫 kb_stale 事件到 ai_governance_events。"""
        try:
            async with get_db_context() as session:
                event = AiGovernanceEvent(
                    event_type="kb_stale",
                    details=result.to_dict(),
                    resolved=False,
                )
                session.add(event)
                await session.commit()
                logger.info("kb_rot_event_saved", stale_count=result.stale_count)
        except Exception as e:
            logger.error("kb_rot_event_save_error", error=str(e))


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

_cleaner: KbRotCleaner | None = None


def get_kb_rot_cleaner() -> KbRotCleaner:
    global _cleaner
    if _cleaner is None:
        _cleaner = KbRotCleaner()
    return _cleaner
