"""Closeout Grade Auto-Classifier — task-2643 Track D 산출물 10.

회장 verbatim doctrine: **사람이 수동 선언한 closeout grade 는 authoritative 아님.**
batch coordinator 가 changed_files / evidence / dry-run / rollback 으로 자동 산정한다.

4 enum (spec §5.2):
- `DOCUMENTED_ONLY` — feedback md / event json / spec md 만 (실 enforcement 0)
- `REGRESSION_GUARDED` — regression test 추가 (정적 검사 수준)
- `RUNTIME_GUARDED` — staged hook draft + dry-run report (실 enforcement 준비)
- `HARNESS_ENFORCED` — live settings.json 적용 + harness deny 실 작동

입력:
- changed_files: list[str] — PR 안에서 수정된 file path
- evidence_files: list[str] — 박제 파일 / fixture 존재 path (memory/events, tests/fixtures/...)
- dry_run_report: dict | None — Track A dry-run report 내용 (PASS/FAIL 단언)
- rollback_plan_present: bool
- live_settings_applied: bool — ~/.claude/settings.json 에 hook 실 등록 여부
"""

from __future__ import annotations

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


class CloseoutGrade(str, Enum):
    DOCUMENTED_ONLY = "DOCUMENTED_ONLY"
    REGRESSION_GUARDED = "REGRESSION_GUARDED"
    RUNTIME_GUARDED = "RUNTIME_GUARDED"
    HARNESS_ENFORCED = "HARNESS_ENFORCED"


@dataclass(frozen=True)
class GradeDecision:
    grade: CloseoutGrade
    rationale: list[str]
    signals: dict[str, Any] = field(default_factory=dict)


# ─────────────────────────────────────────────────────────────────────────────
# helper: signal 추출
# ─────────────────────────────────────────────────────────────────────────────


def _has_regression_tests(changed_files: Iterable[str]) -> bool:
    return any(p.startswith("tests/regression/") and p.endswith(".py") for p in changed_files)


def _has_staged_hook_draft(changed_files: Iterable[str]) -> bool:
    """staged hook draft = hooks/*.py 생성 + memory/specs/staged_settings_template_*.json 동반."""
    has_hook = any(p.startswith("hooks/") and p.endswith(".py") for p in changed_files)
    has_template = any(
        p.startswith("memory/specs/staged_settings_template_") and p.endswith(".json")
        for p in changed_files
    )
    return has_hook and has_template


def _has_dry_run_report(
    changed_files: Iterable[str], dry_run_report: dict[str, Any] | None
) -> bool:
    """dry-run report = events JSON 존재 + report 본문이 deny/allow summary 포함."""
    has_event_file = any(
        p.startswith("memory/events/") and p.endswith(".json") and "dry_run" in p
        for p in changed_files
    )
    if not has_event_file:
        return False
    if not isinstance(dry_run_report, dict):
        return False
    summary = dry_run_report.get("case_summary") or {}
    return summary.get("deny_count", 0) >= 1 and summary.get("allow_count", 0) >= 1


def _doc_only_paths(changed_files: Iterable[str]) -> bool:
    """모든 변경 파일이 doc/spec/event/feedback 류만 인 경우."""
    docs_prefixes = (
        "memory/specs/",
        "memory/events/",
        "memory/feedback_",
        "docs/",
        "CLAUDE.md",
        "MEMORY.md",
    )
    doc_suffixes = (".md",)
    relevant = [p for p in changed_files if p.strip()]
    if not relevant:
        return False
    return all(
        any(p.startswith(pref) for pref in docs_prefixes) or p.endswith(doc_suffixes)
        for p in relevant
    )


# ─────────────────────────────────────────────────────────────────────────────
# 핵심 분류기
# ─────────────────────────────────────────────────────────────────────────────


def classify_closeout_grade(
    *,
    changed_files: list[str],
    evidence_files: list[str] | None = None,
    dry_run_report: dict[str, Any] | None = None,
    rollback_plan_present: bool = False,
    live_settings_applied: bool = False,
) -> GradeDecision:
    """closeout grade 자동 산정.

    우선순위 (highest → lowest):
    1. HARNESS_ENFORCED — live_settings_applied=True
    2. RUNTIME_GUARDED — staged hook draft + dry-run report PASS + rollback plan
    3. REGRESSION_GUARDED — regression tests 추가 (RUNTIME signal 없음)
    4. DOCUMENTED_ONLY — doc/spec/event/feedback only

    signal 누락 시 lower grade 로 fallback (예: hook 있는데 template 없으면 → REGRESSION_GUARDED).
    """
    cf = list(changed_files)
    ef = list(evidence_files or [])
    rationale: list[str] = []
    signals: dict[str, Any] = {
        "changed_file_count": len(cf),
        "has_regression": _has_regression_tests(cf),
        "has_staged_hook": _has_staged_hook_draft(cf),
        "has_dry_run_report": _has_dry_run_report(cf, dry_run_report),
        "rollback_plan_present": rollback_plan_present,
        "live_settings_applied": live_settings_applied,
        "doc_only_paths": _doc_only_paths(cf),
        "evidence_file_count": len(ef),
    }

    if live_settings_applied:
        rationale.append("live_settings_applied=True → live hook 실 작동")
        return GradeDecision(CloseoutGrade.HARNESS_ENFORCED, rationale, signals)

    if (
        signals["has_staged_hook"]
        and signals["has_dry_run_report"]
        and rollback_plan_present
    ):
        rationale.append("staged hook draft + dry-run report PASS + rollback plan 완비")
        if signals["has_regression"]:
            rationale.append("regression tests 추가 (정적 검사 + runtime 준비 합산)")
        return GradeDecision(CloseoutGrade.RUNTIME_GUARDED, rationale, signals)

    if signals["has_regression"]:
        rationale.append("regression tests 추가 (RUNTIME signal 미달 → 정적 검사 수준)")
        return GradeDecision(CloseoutGrade.REGRESSION_GUARDED, rationale, signals)

    if signals["doc_only_paths"] or (not cf and ef):
        rationale.append("doc/spec/event/feedback only 변경")
        return GradeDecision(CloseoutGrade.DOCUMENTED_ONLY, rationale, signals)

    # fallback: 코드 변경은 있는데 regression 도 없고 hook 도 없으면 documented_only 로 보수적으로
    rationale.append("RUNTIME/REGRESSION signal 없음 → 보수적으로 DOCUMENTED_ONLY 분류")
    return GradeDecision(CloseoutGrade.DOCUMENTED_ONLY, rationale, signals)
