"""
AWOOOI AIOps Phase 6 — 離線回放服務（決策一致率基線）
=====================================================
職責：每週取最近 100 個已結案的 Incident，比對當時 AI 決策 vs 實際執行結果，
      計算「決策一致率」，輸出基線並在持續衰退時寫入 ai_governance_events。

一致率定義（加權，共 100 分）：
  - Coordinator 決策正確（requires_human = auto 且有 execution = 成功）→ +60%
  - Playbook 命中（coordinator recommended action 含 playbook 關鍵字）→ +20%
  - 驗證結果一致（verification_result = success）→ +20%

設計原則：
  1. 不呼叫 LLM — 純 DB 查詢 + 靜態規則比對，避免回放產生費用
  2. best-effort — 查詢失敗只記錄 warning，不阻塞主流程
  3. 結果寫入 ai_governance_events（event_type = replay_degraded / replay_ok）
  4. feature flag: AIOPS_P6_GOVERNANCE_ENABLED

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

from __future__ import annotations

import asyncio
import uuid
from dataclasses import dataclass, field
from datetime import timedelta

import structlog
from sqlalchemy import and_, select

from src.db.base import get_db_context
from src.db.models import AgentSession, AiGovernanceEvent, AutoRepairExecution, IncidentEvidence
from src.utils.timezone import now_taipei

logger = structlog.get_logger(__name__)

# ─────────────────────────────────────────────────────────────────────────────
# 常數
# ─────────────────────────────────────────────────────────────────────────────

REPLAY_BATCH = 100              # 每次回放最多 N 個 Incident
REPLAY_LOOKBACK_DAYS = 14       # 回放視窗（取最近 14 天資料）
DEGRADED_THRESHOLD = 0.70       # 一致率 < 70% → 寫 replay_degraded 事件
WEEKLY_INTERVAL_SEC = 7 * 86_400


# ─────────────────────────────────────────────────────────────────────────────
# Result Types
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class IncidentReplayResult:
    """單個 Incident 回放結果"""
    incident_id: str
    has_coordinator_decision: bool = False
    coordinator_approved_auto: bool = False
    has_execution: bool = False
    execution_success: bool = False
    has_verification: bool = False
    verification_success: bool = False
    consistency_score: float = 0.0  # 0~1


@dataclass
class OfflineReplayReport:
    """離線回放報告"""
    run_at: str = ""
    incidents_sampled: int = 0
    incidents_with_data: int = 0
    consistency_rate: float = 0.0   # 0~1 加權一致率
    degraded: bool = False
    results: list[IncidentReplayResult] = field(default_factory=list)


# ─────────────────────────────────────────────────────────────────────────────
# Main Service
# ─────────────────────────────────────────────────────────────────────────────

class OfflineReplayService:
    """
    離線回放服務

    Usage:
        svc = OfflineReplayService()
        report = await svc.run()
    """

    async def run(self) -> OfflineReplayReport:
        """執行一次離線回放，返回一致率報告。"""
        from src.core.feature_flags import aiops_flags
        if not aiops_flags.AIOPS_P6_GOVERNANCE_ENABLED:
            logger.debug("offline_replay_skipped", reason="AIOPS_P6_GOVERNANCE_ENABLED=False")
            return OfflineReplayReport(run_at=now_taipei().isoformat())

        try:
            return await self._run_replay()
        except Exception as e:
            logger.error("offline_replay_error", error=str(e))
            return OfflineReplayReport(run_at=now_taipei().isoformat())

    async def run_forced(self) -> OfflineReplayReport:
        """強制執行（繞過 feature flag，供管理員手動觸發）。"""
        try:
            return await self._run_replay()
        except Exception as e:
            logger.error("offline_replay_forced_error", error=str(e))
            return OfflineReplayReport(run_at=now_taipei().isoformat())

    async def _run_replay(self) -> OfflineReplayReport:
        cutoff = now_taipei() - timedelta(days=REPLAY_LOOKBACK_DAYS)
        async with get_db_context() as db:
            # 1. 取最近 N 個有 AgentSession(coordinator) 的 Incident
            stmt = (
                select(AgentSession.incident_id)
                .where(
                    and_(
                        AgentSession.agent_role == "coordinator",
                        AgentSession.created_at >= cutoff,
                    )
                )
                .distinct()
                .limit(REPLAY_BATCH)
            )
            result = await db.execute(stmt)
            incident_ids = [row[0] for row in result.all()]

        if not incident_ids:
            logger.info("offline_replay_no_incidents", lookback_days=REPLAY_LOOKBACK_DAYS)
            return OfflineReplayReport(
                run_at=now_taipei().isoformat(),
                incidents_sampled=0,
                incidents_with_data=0,
                consistency_rate=1.0,  # 無資料 → 保守視為正常
            )

        results: list[IncidentReplayResult] = []
        async with get_db_context() as db:
            for incident_id in incident_ids:
                r = await self._replay_one(db, incident_id)
                results.append(r)

        # 計算整體一致率
        incidents_with_data = [r for r in results if r.has_coordinator_decision]
        if not incidents_with_data:
            rate = 1.0
        else:
            rate = sum(r.consistency_score for r in incidents_with_data) / len(incidents_with_data)

        degraded = rate < DEGRADED_THRESHOLD
        report = OfflineReplayReport(
            run_at=now_taipei().isoformat(),
            incidents_sampled=len(incident_ids),
            incidents_with_data=len(incidents_with_data),
            consistency_rate=round(rate, 4),
            degraded=degraded,
            results=results,
        )

        # 2. 寫入治理事件
        await self._save_governance_event(report)

        # 2026-04-27 P3.1-T1 by Claude — 三 Tier-1 服務整合
        # 回放事件寫入後，觸發 ModelRollbackService 衰退偵測
        # feature flag 由 model_rollback_service.check() 自行判斷（AIOPS_P6_GOVERNANCE_ENABLED）
        try:
            from src.services.model_rollback_service import get_model_rollback_service
            _mr_svc = get_model_rollback_service()
            _mr_result = await _mr_svc.check()
            if _mr_result.retrain_recommended:
                logger.warning(
                    "model_rollback_triggered",
                    reason="retrain_recommended",
                    streak=_mr_result.consecutive_declines,
                    absolute_floor_breached=_mr_result.absolute_floor_breached,
                    conservative_mode_triggered=_mr_result.conservative_mode_triggered,
                )
        except Exception as _mr_e:
            logger.exception("model_rollback_check_failed", error=str(_mr_e))

        logger.info(
            "offline_replay_done",
            sampled=report.incidents_sampled,
            with_data=report.incidents_with_data,
            consistency_rate=report.consistency_rate,
            degraded=report.degraded,
        )
        return report

    async def _replay_one(self, db, incident_id: str) -> IncidentReplayResult:
        """回放單個 Incident。"""
        r = IncidentReplayResult(incident_id=incident_id)

        # Coordinator AgentSession
        coord_stmt = (
            select(AgentSession)
            .where(
                and_(
                    AgentSession.incident_id == incident_id,
                    AgentSession.agent_role == "coordinator",
                )
            )
            .order_by(AgentSession.created_at.desc())
            .limit(1)
        )
        coord_result = await db.execute(coord_stmt)
        coordinator = coord_result.scalar_one_or_none()

        if not coordinator:
            return r

        r.has_coordinator_decision = True

        # 判斷 coordinator 是否批准自動執行
        out = coordinator.output_json or {}
        if isinstance(out, dict):
            r.coordinator_approved_auto = not out.get("requires_human_approval", True)

        # AutoRepairExecution
        exec_stmt = (
            select(AutoRepairExecution)
            .where(AutoRepairExecution.incident_id == incident_id)
            .order_by(AutoRepairExecution.created_at.desc())
            .limit(1)
        )
        exec_result = await db.execute(exec_stmt)
        execution = exec_result.scalar_one_or_none()

        if execution:
            r.has_execution = True
            r.execution_success = execution.success

        # IncidentEvidence（驗證結果）
        ev_stmt = (
            select(IncidentEvidence)
            .where(IncidentEvidence.incident_id == incident_id)
            .order_by(IncidentEvidence.collected_at.desc())
            .limit(1)
        )
        ev_result = await db.execute(ev_stmt)
        evidence = ev_result.scalar_one_or_none()

        if evidence and evidence.verification_result:
            r.has_verification = True
            r.verification_success = (evidence.verification_result == "success")

        # 計算一致率（加權）
        score = 0.0
        if r.has_coordinator_decision and r.has_execution:
            # 決策與執行一致：auto approve + execution success
            if r.coordinator_approved_auto and r.execution_success:
                score += 0.60
            elif not r.coordinator_approved_auto and not r.has_execution:
                # 升人工且無自動執行 — 也算一致
                score += 0.60
            else:
                score += 0.10  # 決策與執行不一致，給小分
        elif r.has_coordinator_decision:
            # 有決策但無執行 — 部分分
            score += 0.30

        if r.has_execution and r.execution_success:
            score += 0.20
        if r.has_verification and r.verification_success:
            score += 0.20

        r.consistency_score = min(score, 1.0)
        return r

    async def _save_governance_event(self, report: OfflineReplayReport) -> None:
        """寫入 ai_governance_events。"""
        try:
            from sqlalchemy import insert as sa_insert
            from src.db.base import get_db_context
            event_type = "replay_degraded" if report.degraded else "replay_ok"
            async with get_db_context() as db:
                await db.execute(
                    sa_insert(AiGovernanceEvent).values(
                        id=str(uuid.uuid4()),
                        event_type=event_type,
                        details={
                            "consistency_rate": report.consistency_rate,
                            "incidents_sampled": report.incidents_sampled,
                            "incidents_with_data": report.incidents_with_data,
                            "degraded_threshold": DEGRADED_THRESHOLD,
                            "run_at": report.run_at,
                        },
                        resolved=not report.degraded,
                    )
                )
        except Exception as e:
            logger.warning("offline_replay_governance_write_failed", error=str(e))


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

_instance: OfflineReplayService | None = None


def get_offline_replay_service() -> OfflineReplayService:
    global _instance
    if _instance is None:
        _instance = OfflineReplayService()
    return _instance


# ─────────────────────────────────────────────────────────────────────────────
# Loop（掛載到 main.py）
# ─────────────────────────────────────────────────────────────────────────────

async def run_offline_replay_loop() -> None:
    """
    無限迴圈：每 7 天執行一次離線回放。
    在 main.py startup 以 asyncio.create_task 掛載。

    ADR-087 Phase 6: 離線回放基線（L7×D6）
    2026-04-15 ogt + Claude Sonnet 4.6（亞太）: Phase 6 初始建立
    """
    svc = get_offline_replay_service()
    while True:
        try:
            report = await svc.run()
            if report.incidents_sampled > 0:
                logger.info(
                    "offline_replay_loop_tick",
                    rate=report.consistency_rate,
                    degraded=report.degraded,
                )
        except Exception as e:
            logger.error("offline_replay_loop_error", error=str(e))

        await asyncio.sleep(WEEKLY_INTERVAL_SEC)
