#!/usr/bin/env python3
"""task-3078 변이 하네스 — 결선 봉인이 실제로 이빨이 있는지 증명한다.

규칙(과거 사고에서 얻은 것):
  1. **대조군 먼저** — 무변이 상태에서 두 스위트가 모두 green 이어야 시작한다.
  2. **no-op 선증명** — 치환 원문이 소스에 실재하는지 assert 한다. 없으면 그 변이는
     아무것도 안 바꾼 채 통과하고 '거짓 SURVIVED' 를 만든다.
  3. **파이프 금지** — 종료코드를 파이프가 먹으면 '거짓 SURVIVED' 가 된다.
     subprocess 반환코드를 직접 읽는다.
  4. **제자리 실행** — git worktree 안에서 원본 파일을 고쳤다 되돌린다(/tmp 복사 금지).
"""
from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path

SERVER = Path(__file__).resolve().parent
MAIN = SERVER / "main.py"

# (id, 설명, 원문, 치환문, 대상파일)
MUTATIONS = [
    (
        "A4_drop_seo_mode_kwarg",
        "build_content_prompt 호출에서 seo_mode 인자 제거 (t3077 A4 재현)",
        "        seo_mode=seo_mode,\n",
        "",
        MAIN,
    ),
    (
        "A5_drop_benchmark_seo_mode",
        "build_benchmark_prompt 호출에서 seo_mode 인자 제거 (t3077 A5 재현)",
        "build_benchmark_prompt(req.benchmark, seo_mode)",
        "build_benchmark_prompt(req.benchmark)",
        MAIN,
    ),
    (
        "W1_hardcode_seo_mode",
        "요청값을 읽지 않고 상수로 하드코딩",
        "    seo_mode = normalize_seo_mode(req.seoMode)",
        "    seo_mode = SEO_MODE_SEO",
        MAIN,
    ),
    (
        "W1b_hardcode_default",
        "요청값 무시하고 기본값 고정 (모드1 요청이 조용히 무시된다)",
        "    seo_mode = normalize_seo_mode(req.seoMode)",
        "    seo_mode = SEO_MODE_DEFAULT",
        MAIN,
    ),
    (
        "W4_skip_channel_transform",
        "채널 프롬프트 SEO 블록 제거 결선을 무력화 (③ 변환만 죽인다)",
        "            channel_prompt = apply_seo_mode_to_channel_prompt(channel_prompt, seo_mode)",
        "            pass",
        MAIN,
    ),
    (
        "S1_skill_filter_bypass",
        "플랜 스킬 필터 우회 — 감사에서 3662건 중 0건이 잡음",
        "        skills=filtered_skills,",
        "        skills=requested_skills,",
        MAIN,
    ),
    (
        "S2_model_cap_bypass",
        "모델 상한 우회 — 감사에서 3662건 중 0건이 잡음",
        "    model_cap = plan_tier.model_cap",
        "    model_cap = plan_catalog.HIDDEN_TIER.model_cap",
        MAIN,
    ),
    (
        "S3_cli_model_hardcode",
        "과금·집계 모델 오기록 — 감사에서 3662건 중 0건이 잡음",
        "    cli_model = resolved_model",
        '    cli_model = "haiku"',
        MAIN,
    ),
    (
        "W5_skip_final_reminder",
        "최종 지시(⑦) 결선을 무력화 (끝에서 못박는 한 줄만 죽인다)",
        "        final_parts += build_organic_final_reminder_lines(seo_mode)",
        "        pass",
        MAIN,
    ),
]

# 스위트 A = t3077 이 남긴 기존 테스트(상수/순수함수 중심)
OLD_SUITE = ["tests/test_task3077_seo_mode.py"]
# 스위트 B = t3078 이 추가한 엔드포인트 결선 봉인
NEW_SUITE = ["tests/test_task3078_endpoint_wiring_seal.py"]


def run(paths: list[str]) -> tuple[int, str]:
    """pytest 를 돌리고 **반환코드를 직접** 읽는다(파이프에 먹히지 않게)."""
    proc = subprocess.run(
        [sys.executable, "-m", "pytest", "-q", "-p", "no:randomly", *paths],
        cwd=SERVER,
        capture_output=True,
        text=True,
    )
    tail = (proc.stdout or "").strip().splitlines()
    summary = tail[-1] if tail else "(no output)"
    return proc.returncode, summary


def main() -> int:
    print("=" * 78)
    print("대조군 (무변이)")
    print("=" * 78)
    for label, suite in (("OLD(t3077)", OLD_SUITE), ("NEW(t3078)", NEW_SUITE)):
        rc, s = run(suite)
        print(f"  {label:12s} rc={rc}  {s}")
        if rc != 0:
            print("  ✖ 대조군이 이미 빨갛다 — 변이 결과를 신뢰할 수 없다. 중단.")
            return 1

    backup = MAIN.with_suffix(".py.mutbak")
    shutil.copy2(MAIN, backup)
    rows = []
    try:
        for mid, desc, old, new, target in MUTATIONS:
            src = target.read_text(encoding="utf-8")
            # ★ no-op 선증명
            assert old in src, f"[{mid}] 치환 원문이 소스에 없다 — no-op 변이(결과 무효)"
            occurrences = src.count(old)
            mutated = src.replace(old, new, 1)
            assert mutated != src, f"[{mid}] 치환 후 소스가 동일 — no-op"
            target.write_text(mutated, encoding="utf-8")
            try:
                rc_old, s_old = run(OLD_SUITE)
                rc_new, s_new = run(NEW_SUITE)
            finally:
                shutil.copy2(backup, target)
            verdict_old = "KILL" if rc_old != 0 else "SURVIVED"
            verdict_new = "KILL" if rc_new != 0 else "SURVIVED"
            rows.append((mid, occurrences, verdict_old, verdict_new, desc))
            print(f"\n[{mid}] {desc}")
            print(f"   원문 출현 {occurrences}회 (선증명 통과)")
            print(f"   OLD(t3077) rc={rc_old:<3} → {verdict_old:9s} {s_old}")
            print(f"   NEW(t3078) rc={rc_new:<3} → {verdict_new:9s} {s_new}")
    finally:
        shutil.copy2(backup, MAIN)
        backup.unlink(missing_ok=True)

    print("\n" + "=" * 78)
    print(f"{'변이':28s} {'OLD(t3077)':12s} {'NEW(t3078)':12s}")
    print("-" * 78)
    for mid, _occ, vo, vn, _d in rows:
        print(f"{mid:28s} {vo:12s} {vn:12s}")
    killed = sum(1 for r in rows if r[3] == "KILL")
    print("-" * 78)
    print(f"신규 봉인 KILL {killed}/{len(rows)}")

    # 무변이 복원 확인
    rc, s = run(NEW_SUITE)
    print(f"복원 확인: rc={rc} {s}")
    return 0 if killed == len(rows) and rc == 0 else 1


if __name__ == "__main__":
    raise SystemExit(main())
