# self_heal.py — 자가 복구 장치 [CEO 지시 2026-07-18]
#   원리 = CLAUDE.md 제2조(실패는 즉시 롤백)를 기계가 수행한다.
#   ① 검증 관문(run_all_gate.py) 실행
#   ② 통과 → 현재 커밋을 "마지막 정상 지점"으로 기록하고 끝
#   ③ 실패 → 자동 복구 시도 (데이터 손실 0 원칙):
#       a. 미커밋 변경이 있으면 → git stash(보관, 삭제 아님) 후 재검증
#          → 통과되면 원인 = 미커밋 변경. stash에 그대로 보존, 보고서 기록
#       b. 그래도 실패면 → 현재 상태를 백업 브랜치로 보존 후
#          마지막 정상 커밋으로 git reset --hard → 재검증 → 보고서 기록
#   ④ 모든 조치는 _self_heal_reports/ 에 사람이 읽는 보고서로 남김
#
#   사용:
#     python self_heal.py            # 1회 점검+복구
#     python self_heal.py --watch    # 30분마다 반복 (현장 상시 감시)
#     python self_heal.py --quick    # 핵심 4종만으로 판정
import subprocess, sys, os, time, datetime

HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(HERE)
GOOD = os.path.join(HERE, "_gate_last_good.txt")
REPORTS = os.path.join(HERE, "_self_heal_reports")
os.makedirs(REPORTS, exist_ok=True)

def git(*args, timeout=60):
    r = subprocess.run(["git", "-C", REPO] + list(args), capture_output=True, timeout=timeout)
    return r.returncode, r.stdout.decode("utf-8", "replace").strip(), r.stderr.decode("utf-8", "replace").strip()

def run_gate(quick):
    args = [sys.executable, os.path.join(HERE, "run_all_gate.py")] + (["--quick"] if quick else [])
    try:
        r = subprocess.run(args, capture_output=True, timeout=900, cwd=HERE)
        out = r.stdout.decode("utf-8", "replace")
        return ("관문 결과: 통과" in out), out
    except subprocess.TimeoutExpired:
        return False, "(관문 시간초과)"

def head():
    return git("rev-parse", "--short", "HEAD")[1]

def report(lines):
    os.makedirs(REPORTS, exist_ok=True)   # stash -u가 이 폴더까지 보관해 사라질 수 있음 — 매번 재생성
    ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    p = os.path.join(REPORTS, f"heal_{ts}.md")
    with open(p, "w", encoding="utf-8") as f:
        f.write("# 자가복구 보고서 " + ts + "\n\n" + "\n".join("- " + l for l in lines) + "\n")
    print("[보고서]", p)
    return p

def heal(quick):
    ok, out = run_gate(quick)
    if ok:
        with open(GOOD, "w") as f: f.write(head())
        print(f"[정상] 관문 통과 — 정상 지점 갱신: {head()}")
        return 0

    print("[감지] 관문 실패 — 자가 복구 시작")
    log = [f"감지 시각: {datetime.datetime.now()}", f"실패 시 커밋: {head()}"]
    fail_tail = "\n".join(out.strip().splitlines()[-8:])
    log.append("실패 출력(끝부분):\n```\n" + fail_tail + "\n```")

    # a. 미커밋 변경이 원인인지 — stash로 격리(보관) 후 재시험
    dirty = git("status", "--porcelain")[1]
    if dirty:
        git("stash", "push", "-m", "self_heal 자동보관 " + datetime.datetime.now().isoformat())
        log.append("미커밋 변경 발견 → git stash에 보관(삭제 아님). 복원: git stash pop")
        ok2, _ = run_gate(quick)
        if ok2:
            log.append("✅ 복구 성공 — 원인 = 미커밋 변경. 코드는 stash에 그대로 있음")
            with open(GOOD, "w") as f: f.write(head())
            report(log); return 0
        log.append("stash 후에도 실패 — 커밋 자체가 원인")

    # b. 마지막 정상 커밋으로 복귀 (현재 상태는 백업 브랜치로 보존)
    if not os.path.exists(GOOD):
        log.append("❌ 마지막 정상 지점 기록 없음 — 자동 복귀 불가. 사람 확인 필요")
        report(log); return 1
    good = open(GOOD).read().strip()
    if good == head():
        log.append("❌ 현재가 이미 마지막 정상 커밋인데 실패 — 환경 문제(서버 다운 등) 가능성. 사람 확인 필요")
        report(log); return 1
    bk = "self_heal_backup_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    git("branch", bk)
    log.append(f"현재 상태를 브랜치 '{bk}'로 보존 (삭제 아님)")
    git("reset", "--hard", good)
    log.append(f"마지막 정상 커밋 {good} 으로 복귀 (제2조)")
    ok3, _ = run_gate(quick)
    if ok3:
        log.append(f"✅ 복구 성공 — {good} 에서 관문 통과. 깨진 커밋들은 '{bk}' 브랜치에 보존")
        report(log); return 0
    log.append("❌ 복귀 후에도 실패 — 코드가 아니라 환경(서버·의존성) 문제. 사람 확인 필요")
    report(log); return 1

if __name__ == "__main__":
    quick = "--quick" in sys.argv
    if "--watch" in sys.argv:
        print("[감시 모드] 30분 간격 자가 점검 시작")
        while True:
            try: heal(quick)
            except Exception as e: print("[오류]", e)
            time.sleep(1800)
    else:
        sys.exit(heal(quick))
