"""tests/state_machine/test_state_repair.py — Group 2 (4건).

task-2472 regression: 수동 state 수정 차단 + checksum mismatch 검증.

5. test_state_json_direct_modification_checksum_mismatch
6. test_failed_to_human_approved_manual_transition_rejected
7. test_checksum_mismatch_blocks_done_or_merge
8. test_state_repair_without_evidence_rejected
"""
from __future__ import annotations

import hashlib
import importlib.util
import json
import sys
from pathlib import Path

WORKTREE = Path(__file__).resolve().parents[2]


def _load(mod_name: str, rel: str):
    path = WORKTREE / rel
    spec = importlib.util.spec_from_file_location(mod_name, str(path))
    assert spec is not None and spec.loader is not None, f"spec load 실패: {path}"
    mod = importlib.util.module_from_spec(spec)
    sys.modules[mod_name] = mod
    spec.loader.exec_module(mod)
    return mod


sr = _load("state_repair_mod", "utils/state_repair.py")
scg = _load("silent_corruption_guard_g2", "utils/silent_corruption_guard.py")


def _make_valid_state_file(tmp_path: Path, task_id: str, state: str = "RUNNING") -> Path:
    """올바른 checksum이 포함된 state 파일 생성."""
    state_dir = tmp_path / ".tasks" / "state"
    state_dir.mkdir(parents=True, exist_ok=True)
    state_data = {
        "task_id": task_id,
        "state": state,
        "actor": "taskctl",
    }
    checksum = hashlib.sha256(
        json.dumps(state_data, sort_keys=True, ensure_ascii=False).encode("utf-8")
    ).hexdigest()
    state_data["_checksum"] = checksum

    state_path = state_dir / f"{task_id}.json"
    state_path.write_text(json.dumps(state_data, ensure_ascii=False, indent=2), encoding="utf-8")
    return state_path


# ---------------------------------------------------------------------------
# Test 5: state JSON 직접 수정 후 inspect → checksum_match=False
# ---------------------------------------------------------------------------

def test_state_json_direct_modification_checksum_mismatch(tmp_path):
    """state JSON 직접 수정 후 inspect → checksum_match=False."""
    task_id = "task-chksum-test"
    state_path = _make_valid_state_file(tmp_path, task_id, state="RUNNING")

    # 정상 상태 확인
    result = sr.inspect_state(task_id, workspace=tmp_path)
    assert result["checksum_match"] is True, "초기 상태는 checksum 일치해야 함"

    # JSON 직접 수정 (state 변조)
    state_data = json.loads(state_path.read_text(encoding="utf-8"))
    state_data["state"] = "HUMAN_APPROVED"  # 수동 변조
    state_path.write_text(json.dumps(state_data, ensure_ascii=False, indent=2), encoding="utf-8")

    # inspect 다시 실행 → checksum mismatch 확인
    result = sr.inspect_state(task_id, workspace=tmp_path)
    assert result["exists"] is True
    assert result["json_valid"] is True
    assert result["checksum_present"] is True
    assert result["checksum_match"] is False, "수동 편집 후 checksum mismatch 감지해야 함"
    assert any("checksum mismatch" in issue for issue in result["issues"])


# ---------------------------------------------------------------------------
# Test 6: FAILED → HUMAN_APPROVED 수동 전이 시도 → reject (taskctl _transition)
# ---------------------------------------------------------------------------

def test_failed_to_human_approved_manual_transition_rejected(tmp_path):
    """taskctl에서 FAILED → HUMAN_APPROVED 직접 전이 시도 → 차단."""
    # silent_corruption_guard.check_state_file_present를 활용:
    # checksum mismatch 상태에서 done 차단 검증

    task_id = "task-transition-test"
    state_path = _make_valid_state_file(tmp_path, task_id, state="FAILED")

    # 수동으로 state를 HUMAN_APPROVED로 변조 (checksum 업데이트 안 함)
    state_data = json.loads(state_path.read_text(encoding="utf-8"))
    state_data["state"] = "HUMAN_APPROVED"
    # _checksum은 업데이트하지 않음 → mismatch 발생
    state_path.write_text(json.dumps(state_data, ensure_ascii=False, indent=2), encoding="utf-8")

    # check_state_file_present → checksum mismatch → ok=False
    result = scg.check_state_file_present(task_id, workspace=tmp_path)
    assert result["ok"] is False, "checksum mismatch 상태에서 state 검증 차단해야 함"
    assert "mismatch" in result["reason"] or "수동 편집" in result["reason"]
    assert result["detail"]["checksum_match"] is False


# ---------------------------------------------------------------------------
# Test 7: checksum mismatch 상태에서 cmd_done 호출 → 차단 (subprocess로 taskctl.py)
# ---------------------------------------------------------------------------

def test_checksum_mismatch_blocks_done_or_merge(tmp_path):
    """checksum mismatch 상태에서 check_state_file_present → ok=False → done 차단."""
    task_id = "task-mismatch-block"
    state_path = _make_valid_state_file(tmp_path, task_id, state="VERIFIED")

    # state 직접 변조 (checksum 갱신 없음)
    state_data = json.loads(state_path.read_text(encoding="utf-8"))
    state_data["extra_field"] = "injected_by_attacker"
    state_path.write_text(json.dumps(state_data, ensure_ascii=False, indent=2), encoding="utf-8")

    # silent_corruption_guard.check_state_file_present가 차단하는지 확인
    guard_result = scg.check_state_file_present(task_id, workspace=tmp_path)
    assert guard_result["ok"] is False, "checksum mismatch → done/merge 차단 필수"
    assert "mismatch" in guard_result["reason"] or "fail-closed" in guard_result["reason"]

    # 또한 inspect_state도 issues 리포트
    inspect = sr.inspect_state(task_id, workspace=tmp_path)
    assert inspect["checksum_match"] is False
    assert len(inspect["issues"]) > 0


# ---------------------------------------------------------------------------
# Test 8: state_repair() evidence_path 누락 → fail-closed
# ---------------------------------------------------------------------------

def test_state_repair_without_evidence_rejected(tmp_path):
    """repair_state() 호출 시 evidence_path 파일 없음 → fail-closed."""
    task_id = "task-repair-no-ev"
    _make_valid_state_file(tmp_path, task_id, state="FAILED")

    # evidence_path를 존재하지 않는 경로로 지정
    nonexistent_evidence = str(tmp_path / "no_such_evidence.json")

    result = sr.repair_state(
        task_id,
        approved_by_chairman="chairman",
        evidence_path=nonexistent_evidence,
        actor="test-actor",
        repair_action="recompute_checksum",
        workspace=tmp_path,
    )

    assert result["ok"] is False, "evidence 없이 repair → fail-closed 필수"
    assert "evidence_path" in result["reason"] or "없음" in result["reason"]
    assert result["backup_path"] == ""
