"""
AWOOOI AIOps Phase 2 — 多 Agent 協作訊息協定
==============================================
定義 5 個 Agent 間傳遞的不可變資料型別。

設計原則：
1. 每個 Agent 有明確的 Input / Output 型別（不共用 dict）
2. 所有型別都是 dataclass（快速、可序列化、無外部依賴）
3. 降級 / 棄權用明確 AgentVote.ABSTAIN，不用 None 代替
4. 全程 immutable — Agent 不得修改彼此的輸出（防 prompt 污染）

ADR-082: 多 Agent 協作架構（Phase 2）
2026-04-15 ogt + Claude Sonnet 4.6（亞太）: Phase 2 初始建立
2026-04-27 Claude Sonnet 4.6: B1 — 新增 RecommendedAction schema（北極星 §1.1 修復多樣性 ≥ 40%）
2026-04-27 Claude Sonnet 4.6: H1+B1 Fix Round — ActionPlan.recommended_actions_status enum（可觀測性）
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Literal

# 2026-04-27 Claude Sonnet 4.6: H1+B1 Fix Round — recommended_actions_status 型別別名
# 方便 solver_agent.py 使用；Literal 比 Enum 輕量且不需要額外 import
RecommendedActionsStatus = Literal[
    "ok",                  # LLM 推出 ≥ 1 個通過 registry + validator 的 action
    "empty",               # LLM 推 0 個 recommended_actions
    "schema_failed",       # LLM 推但全被 schema / registry 驗證 reject
    "registry_unavailable",# registry 載入失敗（{}）
]


# ─────────────────────────────────────────────────────────────────────────────
# Enums
# ─────────────────────────────────────────────────────────────────────────────

class AgentRole(str, Enum):
    """Phase 2 五角色標識"""
    DIAGNOSTICIAN = "diagnostician"
    SOLVER = "solver"
    REVIEWER = "reviewer"
    CRITIC = "critic"
    COORDINATOR = "coordinator"


class AgentVote(str, Enum):
    """Agent 投票結果"""
    APPROVE = "approve"
    REJECT = "reject"
    REQUEST_REVISION = "request_revision"
    ABSTAIN = "abstain"          # 熔斷 / 超時 / 無足夠資訊
    DEGRADED = "degraded"        # 降級路徑（rule-based mock）


class AgentSessionStatus(str, Enum):
    """AgentSession 整體狀態"""
    RUNNING = "running"
    COMPLETED = "completed"
    DEGRADED = "degraded"        # 部分 Agent 熔斷但仍完成
    FAILED = "failed"            # Coordinator 無法輸出任何結論
    TIMEOUT = "timeout"          # 全流程 > 30s


# ─────────────────────────────────────────────────────────────────────────────
# Diagnostician Output
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class Hypothesis:
    """單一根因假設"""
    description: str
    confidence: float              # 0.0 ~ 1.0
    evidence_chain: list[str]      # 支持此假設的 evidence key
    category: str = ""             # alert_category（KubePod / HostDisk 等）


@dataclass
class DiagnosisReport:
    """
    Diagnostician Agent 輸出

    包含多個根因假設（按信心排序），
    top-1 confidence < 0.4 觸發 Coordinator 回退 Investigator 重抓。
    """
    hypotheses: list[Hypothesis]
    evidence_snapshot_id: str
    latency_ms: int
    vote: AgentVote = AgentVote.APPROVE     # 資訊足夠 = APPROVE；不足 = ABSTAIN
    degraded: bool = False                   # 熔斷降級標記

    @property
    def top_hypothesis(self) -> Hypothesis | None:
        """最高信心假設"""
        return self.hypotheses[0] if self.hypotheses else None

    @property
    def top_confidence(self) -> float:
        return self.top_hypothesis.confidence if self.top_hypothesis else 0.0


# ─────────────────────────────────────────────────────────────────────────────
# Solver Output
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class CandidateAction:
    """單一候選修復動作"""
    action: str                    # 動作描述（e.g. "restart_service:awoooi-api"）
    blast_radius: int              # 0-100：影響範圍評分
    rollback_cost: int             # 0-100：回滾難度
    confidence: float              # 0.0 ~ 1.0
    rationale: str = ""            # 為什麼選此方案


# 2026-04-27 Claude Sonnet 4.6: B1 — Solver 結構化動作 (北極星 §1.1 修復多樣性 ≥ 40%)
# RecommendedAction 是 ActionPlan.recommended_actions 的元素，供 B3 Telegram 按鈕動態生成用。
# 與 CandidateAction（kubectl 命令字串）不同：RecommendedAction 指向 MCP tool（可被 B2 allowlist 審核）。
@dataclass
class RecommendedAction:
    """
    結構化推薦修復動作（B1 新增，供 Telegram 按鈕動態生成）

    與 CandidateAction 的差異：
    - CandidateAction：kubectl 命令字串（供 Coordinator 判斷）
    - RecommendedAction：MCP tool 呼叫規格（供 B3 Telegram 按鈕動態渲染）

    mcp_provider 必須在 callback_action_spec.yaml 的 provider 清單內。
    mcp_tool 必須在 B2 allowlist（待 B2 任務建立）。
    params 支援模板替換：{labels.xxx} / {incident_id}。
    """
    name: str                           # action 識別（如 check_pod_logs）
    label: str                          # UI 顯示文字（如「查 Pod 日誌」）
    emoji: str                          # UI 圖示（如「📋」）
    mcp_provider: Literal[             # MCP provider 限制在已知清單
        "k8s", "ssh", "prometheus", "signoz", "database", "internal"
    ]
    mcp_tool: str                       # MCP tool 名（必須在 B2 allowlist）
    params: dict[str, str]              # 參數模板（支援 {labels.xxx} / {incident_id}）
    risk: Literal["low", "medium", "high", "critical"]  # 風險等級
    reasoning: str                      # 為何推薦此動作（讓 critic 能審）


@dataclass
class ActionPlan:
    """
    Solver Agent 輸出

    對每個根因假設提出 ≥1 個候選方案（含 blast_radius / rollback_cost）。
    blast_radius > 50 → Reviewer 必須標 `request_revision`。

    2026-04-27 Claude Sonnet 4.6: B1 新增 recommended_actions（結構化動作清單）
    - recommended_actions 為空 list 代表降級（degraded=True）或 LLM 無法輸出合法動作
    - Coordinator 舊邏輯只讀 candidates，不受影響
    2026-04-27 Claude Sonnet 4.6: H1+B1 Fix Round — recommended_actions_status 新增
    - 可觀測性：B3 Telegram / 監控 dashboard 可讀取此欄位判斷 Solver 品質
    """
    candidates: list[CandidateAction]
    diagnosis_report: DiagnosisReport
    latency_ms: int
    vote: AgentVote = AgentVote.APPROVE
    degraded: bool = False
    # 2026-04-27 Claude Sonnet 4.6: B1 — 結構化推薦動作（0-3 個，降級時為 []）
    recommended_actions: list[RecommendedAction] = field(default_factory=list)
    # 2026-04-27 Claude Sonnet 4.6: H1+B1 Fix Round — recommended_actions 提取結果狀態
    # ok=正常, empty=LLM 未輸出, schema_failed=全部驗證失敗, registry_unavailable=registry 載入失敗
    # 欄位加在尾部，default="ok"，不破壞既有 callsite
    recommended_actions_status: RecommendedActionsStatus = "ok"

    @property
    def top_candidate(self) -> CandidateAction | None:
        """最高信心候選方案"""
        return max(self.candidates, key=lambda c: c.confidence) if self.candidates else None


# ─────────────────────────────────────────────────────────────────────────────
# Reviewer Output
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class ReviewVerdict:
    """
    Reviewer Agent 輸出（安全審查）

    硬核拒絕 HARD_RULES 觸碰動作（delete node / DROP TABLE / force push 等）。
    vote = REJECT 時，Coordinator 不得執行任何候選方案。
    """
    vote: AgentVote
    reason: str
    blocked_actions: list[str]     # 被拒絕的動作清單
    safe_actions: list[str]        # 通過安全審查的動作
    latency_ms: int
    degraded: bool = False

    @property
    def is_safe(self) -> bool:
        return self.vote == AgentVote.APPROVE and bool(self.safe_actions)


# ─────────────────────────────────────────────────────────────────────────────
# Critic Output
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class Challenge:
    """Critic 的單一質疑"""
    target: str              # "diagnosis" | "action:{action_str}"
    argument: str            # 質疑的具體理由
    severity: str            # "minor" | "major" | "critical"


@dataclass
class CriticReport:
    """
    Critic Agent 輸出（刻意唱反調）

    challenge_count > 0 是 Phase 2 退出條件之一。
    major/critical challenge 觸發 Coordinator 降低對 Solver 方案的信心。
    """
    challenges: list[Challenge]
    overall_assessment: str
    latency_ms: int
    vote: AgentVote = AgentVote.APPROVE   # APPROVE=無重大反對；REJECT=有 critical challenge
    degraded: bool = False

    @property
    def challenge_count(self) -> int:
        return len(self.challenges)

    @property
    def has_critical_challenge(self) -> bool:
        return any(c.severity == "critical" for c in self.challenges)


# ─────────────────────────────────────────────────────────────────────────────
# Coordinator Output
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class DecisionPackage:
    """
    Coordinator Agent 輸出（最終決策包）

    包含：
    - recommended_action: 最終推薦動作（None = 棄權 / 升級人工）
    - confidence: 綜合信心（Solver × Reviewer × Critic 加權）
    - requires_human_approval: 是否需要人工審核
    - debate_summary: 辯證歷程摘要（供 Audit Trail + 學習閉環）
    - session_status: 整體辯證狀態
    """
    recommended_action: str | None
    confidence: float
    requires_human_approval: bool
    debate_summary: str
    session_status: AgentSessionStatus
    latency_ms: int

    # 保留各 Agent 原始輸出（供學習閉環查詢）
    diagnosis: DiagnosisReport | None = None
    action_plan: ActionPlan | None = None
    reviewer_verdict: ReviewVerdict | None = None
    critic_report: CriticReport | None = None

    # 棄選方案（含原因）
    rejected_actions: list[dict[str, Any]] = field(default_factory=list)

    # 阻擋原因（requires_human_approval=True 時說明）
    blocked_reason: str | None = None

    # 全部 Agent 都降級（更嚴格的人工審核信號）
    all_agents_degraded: bool = False

    @property
    def is_actionable(self) -> bool:
        """可以執行（有推薦動作且信心 > 0.4 且 Reviewer 通過）"""
        if not self.recommended_action:
            return False
        if self.confidence < 0.4:
            return False
        if self.reviewer_verdict and self.reviewer_verdict.vote == AgentVote.REJECT:
            return False
        return True


# ─────────────────────────────────────────────────────────────────────────────
# Agent Session Record（DB 寫入用）
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class AgentTurn:
    """
    單次 Agent 發言記錄

    寫入 `agent_sessions` 表的一行，
    session_id + agent_role 唯一確定一次辯證發言。
    """
    session_id: str
    incident_id: str
    agent_role: AgentRole
    input_hash: str              # sha256(input_json)[:16]
    output_json: dict[str, Any]  # Agent 原始輸出
    latency_ms: int
    vote: AgentVote
    degraded: bool = False
