"""utils/automation_contracts.py — task-2509+2 공통 계약 freeze.

후속 4 모듈(replacement_pr_runner, auto_gemini_triage,
post_merge_smoke_runner, critical_escalation_reporter)에서
이 모듈을 import해 사용한다.

회장 명시: "Critical 7종 외 회장 보고 금지"

enum 이름은 task-2509+2 회장 정확 매칭.
기존 task-2509+1 이름과 차이는 후속 wiring task에서 정렬.
"""
from __future__ import annotations

import dataclasses
import json
from dataclasses import dataclass
from enum import Enum
from typing import Optional

__all__ = [
    "CriticalEscalationType",
    "RiskLevel",
    "GeminiStatus",
    "AutomationDecision",
    "ReviewGateStatus",
    "FallbackReviewResult",
    "ReplacementResult",
    "GeminiTriageResult",
    "SmokeResult",
    "QueueAuditRecord",
    "AutoMergeResult",
    "EscalationPacket",
    "to_json",
]


# ---------------------------------------------------------------------------
# 1. CriticalEscalationType — 정확히 7종
# ---------------------------------------------------------------------------

class CriticalEscalationType(str, Enum):
    """Critical escalation 종류. 회장 보고 대상 7종."""

    FORBIDDEN_PATH_INTRUSION = "FORBIDDEN_PATH_INTRUSION"
    REPLACEMENT_PR_AUTO_CREATION_FAILED_FOR_CONTAMINATED_DIFF = (
        "REPLACEMENT_PR_AUTO_CREATION_FAILED_FOR_CONTAMINATED_DIFF"
    )
    GEMINI_REAL_BUG_REQUIRES_SCOPE_EXPANSION = (
        "GEMINI_REAL_BUG_REQUIRES_SCOPE_EXPANSION"
    )
    BLOCK_OVERRIDE_REQUIRED_OR_REASON_INSUFFICIENT = (
        "BLOCK_OVERRIDE_REQUIRED_OR_REASON_INSUFFICIENT"
    )
    DEPENDENCY_CYCLE_OR_SERIAL_ONLY_COLLISION = (
        "DEPENDENCY_CYCLE_OR_SERIAL_ONLY_COLLISION"
    )
    REPLACEMENT_PR_FAILED = "REPLACEMENT_PR_FAILED"
    POST_MERGE_SMOKE_FAILED = "POST_MERGE_SMOKE_FAILED"


# ---------------------------------------------------------------------------
# 2. RiskLevel
# ---------------------------------------------------------------------------

class RiskLevel(str, Enum):
    LOW = "LOW"
    MEDIUM = "MEDIUM"
    HIGH = "HIGH"
    HIGH_CORE = "HIGH_CORE"


# ---------------------------------------------------------------------------
# 3. GeminiStatus — 7종
# ---------------------------------------------------------------------------

class GeminiStatus(str, Enum):
    GEMINI_COMPLETED = "GEMINI_COMPLETED"
    GEMINI_UNRESOLVED = "GEMINI_UNRESOLVED"
    GEMINI_UNAVAILABLE_QUOTA = "GEMINI_UNAVAILABLE_QUOTA"
    GEMINI_TIMEOUT = "GEMINI_TIMEOUT"
    GEMINI_STALE = "GEMINI_STALE"
    GEMINI_REAL_BUG = "GEMINI_REAL_BUG"
    GEMINI_SCOPE_EXPANSION = "GEMINI_SCOPE_EXPANSION"


# ---------------------------------------------------------------------------
# 4. AutomationDecision
# ---------------------------------------------------------------------------

@dataclass
class AutomationDecision:
    decision: str
    reason_codes: list[str]
    critical_escalation_type: Optional[CriticalEscalationType]
    auto_handled: bool
    requires_chair: bool
    audit: dict

    def __post_init__(self) -> None:
        if self.critical_escalation_type is not None and not self.requires_chair:
            raise ValueError(
                "requires_chair must be True when critical_escalation_type is set"
            )


# ---------------------------------------------------------------------------
# 5. ReviewGateStatus
# ---------------------------------------------------------------------------

@dataclass
class ReviewGateStatus:
    gemini_status: GeminiStatus
    unresolved_threads: int
    fallback_review_used: bool
    fallback_review_passed: bool
    review_gate_passed: bool
    reason: str


# ---------------------------------------------------------------------------
# 6. FallbackReviewResult
# ---------------------------------------------------------------------------

@dataclass
class FallbackReviewResult:
    used: bool
    passed: bool
    risk_level: RiskLevel
    checks: dict
    reason: str


# ---------------------------------------------------------------------------
# 7. ReplacementResult
# ---------------------------------------------------------------------------

@dataclass
class ReplacementResult:
    source_pr: int
    replacement_pr: Optional[int]
    original_pr_preserved: bool
    expected_files: list[str]
    effective_diff_files: list[str]
    forbidden_paths: list[str]
    success: bool
    failure_reason: Optional[str]

    def __post_init__(self) -> None:
        if not self.success and (
            self.failure_reason is None or self.failure_reason == ""
        ):
            raise ValueError(
                "failure_reason must be provided when success=False"
            )


# ---------------------------------------------------------------------------
# 8. GeminiTriageResult
# ---------------------------------------------------------------------------

@dataclass
class GeminiTriageResult:
    status: GeminiStatus
    false_positive_count: int
    style_only_count: int
    real_bug_small_count: int
    scope_expansion_count: int
    unresolved_count: int
    actions_taken: list[str]


# ---------------------------------------------------------------------------
# 9. SmokeResult
# ---------------------------------------------------------------------------

@dataclass
class SmokeResult:
    command: str
    passed: bool
    exit_code: int
    stdout_tail: str
    stderr_tail: str
    failure_reason: Optional[str]


# ---------------------------------------------------------------------------
# 10. QueueAuditRecord
# ---------------------------------------------------------------------------

@dataclass
class QueueAuditRecord:
    task_id: str
    pr_number: int
    queue_position: int
    head_sha: str
    base_sha: str
    decision: AutomationDecision
    checks: dict
    review_gate: ReviewGateStatus
    smoke: Optional[SmokeResult]
    critical_escalation: Optional[CriticalEscalationType]
    timestamp: str


# ---------------------------------------------------------------------------
# 11. AutoMergeResult
# ---------------------------------------------------------------------------

@dataclass
class AutoMergeResult:
    merged: bool
    merge_commit: Optional[str]
    smoke_result: Optional[SmokeResult]
    following_prs_rechecked: list[int]
    critical_escalation: Optional[CriticalEscalationType]

    def __post_init__(self) -> None:
        if self.merged and (
            self.merge_commit is None or self.merge_commit == ""
        ):
            raise ValueError(
                "merge_commit must be provided when merged=True"
            )


# ---------------------------------------------------------------------------
# 12. EscalationPacket
# ---------------------------------------------------------------------------

@dataclass
class EscalationPacket:
    task_id: str
    pr_number: int
    escalation_type: CriticalEscalationType
    reason: str
    why_auto_cannot_continue: str
    safe_options: list[str]
    recommended_option: str
    evidence: dict

    def __post_init__(self) -> None:
        if isinstance(self.escalation_type, CriticalEscalationType):
            return
        try:
            self.escalation_type = CriticalEscalationType(self.escalation_type)
        except ValueError as exc:
            raise ValueError(
                f"escalation_type must be one of Critical 7 enum members, "
                f"got invalid value: {self.escalation_type!r}"
            ) from exc
        except TypeError as exc:
            raise ValueError(
                f"escalation_type must be CriticalEscalationType (or matching string), "
                f"got {type(self.escalation_type).__name__}"
            ) from exc


# ---------------------------------------------------------------------------
# Helper: to_json
# ---------------------------------------------------------------------------

def to_json(obj) -> str:
    """dataclass 인스턴스를 JSON 문자열로 직렬화한다.

    Enum 필드가 str 상속이므로 자동으로 직렬화된다.
    """
    return json.dumps(dataclasses.asdict(obj))
