"""task-2590 regression — fifth-truncate halt guard 8 scenarios.

회장 verbatim 결정 (memory/tasks/task-2590.md §7 + §6.4) 박제:
- 테스트는 fixture/tmp_path 기반으로만 수행. 실제 replacement_pr_runner.py 및
  baseline test file truncate 금지 (회장 G verbatim).
- halt_action_verified=true 검증 4 조건 (회장 #3 verbatim):
    1. guard halt trigger 발생 (tmp_path truncate 시뮬레이션)
    2. escalation_marker.py subprocess 호출 path 검증
    3. escalation JSON file 생성 확인
    4. non-zero exit code 87 + Telegram API mock call 인터셉트 확인 (실제 발송 금지)

대상: scripts/replacement_pr_dry_run_activation_guard.py
"""
from __future__ import annotations

import hashlib
import json
import os
import subprocess
import sys
from pathlib import Path

import pytest

WORKSPACE = Path(__file__).resolve().parent.parent.parent
GUARD_SCRIPT = WORKSPACE / "scripts" / "replacement_pr_dry_run_activation_guard.py"
ESCALATION_MARKER_SCRIPT = WORKSPACE / "scripts" / "escalation_marker.py"

HALT_EXIT_CODE = 87

# baseline content/size for fixture-only tests (실제 production 파일 미사용 — tmp_path 만 사용)
RUNNER_BASELINE_CONTENT = b"# fixture replacement_pr_runner.py baseline\n" + b"X" * 100
TEST_BASELINE_CONTENT = b"# fixture test_replacement_pr_runner_2510.py baseline\n" + b"Y" * 100


def _sha256_bytes(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


@pytest.fixture
def baseline_fixture(tmp_path: Path) -> dict:
    """tmp_path 안에 fixture runner / test 파일 + baseline metadata 생성.

    실제 production 파일은 절대 건드리지 않음 (회장 G verbatim).
    """
    fixture_dir = tmp_path / "fixture_repo"
    (fixture_dir / "utils").mkdir(parents=True)
    (fixture_dir / "tests" / "regression").mkdir(parents=True)

    runner_path = fixture_dir / "utils" / "replacement_pr_runner.py"
    test_path = fixture_dir / "tests" / "regression" / "test_replacement_pr_runner_2510.py"

    runner_path.write_bytes(RUNNER_BASELINE_CONTENT)
    test_path.write_bytes(TEST_BASELINE_CONTENT)

    events_dir = tmp_path / "memory" / "events"
    events_dir.mkdir(parents=True)
    (tmp_path / "memory" / "orchestration-audit").mkdir(parents=True)

    return {
        "tmp_root": tmp_path,
        "runner_path": runner_path,
        "test_path": test_path,
        "runner_sha256": _sha256_bytes(RUNNER_BASELINE_CONTENT),
        "test_sha256": _sha256_bytes(TEST_BASELINE_CONTENT),
        "runner_size": len(RUNNER_BASELINE_CONTENT),
        "test_size": len(TEST_BASELINE_CONTENT),
        "events_dir": events_dir,
        "telegram_mock_path": tmp_path / "telegram_mock.json",
    }


def _run_guard(
    bf: dict,
    *,
    runner_baseline_sha256: str | None = None,
    test_baseline_sha256: str | None = None,
    runner_baseline_size: int | None = None,
    test_baseline_size: int | None = None,
    extra_env: dict | None = None,
    escalation_task_id: str = "task-2586-test-fixture",
) -> subprocess.CompletedProcess:
    """guard subprocess 호출. WORKSPACE 환경변수로 escalation_marker.py 격리."""
    env = os.environ.copy()
    env["WORKSPACE"] = str(bf["tmp_root"])
    env["FIFTH_TRUNCATE_GUARD_TELEGRAM_MODE"] = "mock"
    env["FIFTH_TRUNCATE_GUARD_TELEGRAM_MOCK_PATH"] = str(bf["telegram_mock_path"])
    env["FIFTH_TRUNCATE_GUARD_ESCALATION_MARKER_PATH"] = str(ESCALATION_MARKER_SCRIPT)
    if extra_env:
        env.update(extra_env)

    cmd = [
        sys.executable,
        str(GUARD_SCRIPT),
        "preflight",
        "--task-id", "task-2590",
        "--escalation-task-id", escalation_task_id,
        "--runner-path", str(bf["runner_path"]),
        "--test-path", str(bf["test_path"]),
        "--runner-baseline-sha256", runner_baseline_sha256 or bf["runner_sha256"],
        "--test-baseline-sha256", test_baseline_sha256 or bf["test_sha256"],
        "--runner-baseline-size", str(runner_baseline_size if runner_baseline_size is not None else bf["runner_size"]),
        "--test-baseline-size", str(test_baseline_size if test_baseline_size is not None else bf["test_size"]),
        "--evidence-dir", str(bf["events_dir"]),
    ]
    return subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=60, check=False)


# ─── 8 scenarios ──────────────────────────────────────────────────────────


def test_01_positive_baseline_match(baseline_fixture):
    """T1 Positive: baseline match → PASS (exit 0, escalation 0)."""
    bf = baseline_fixture
    proc = _run_guard(bf)
    assert proc.returncode == 0, f"expected PASS, got {proc.returncode}\nstdout={proc.stdout}\nstderr={proc.stderr}"
    payload = json.loads(proc.stdout)
    assert payload["status"] == "PASS"
    assert payload["runner_check"]["ok"] is True
    assert payload["test_check"]["ok"] is True
    # PASS 경로에서는 side marker / telegram mock 파일 0건이어야 함
    side_marker = bf["events_dir"] / "task-2590.fifth-truncate-halt-trigger.json"
    assert not side_marker.exists(), "PASS 경로에서 side marker 박제 금지"
    assert not bf["telegram_mock_path"].exists(), "PASS 경로에서 telegram alert 금지"


def test_02_negative_utils_truncate(baseline_fixture):
    """T2 Negative — utils 0 bytes → escalation marker emit + non-zero exit."""
    bf = baseline_fixture
    bf["runner_path"].write_bytes(b"")
    proc = _run_guard(bf, escalation_task_id="task-2586-fx-02")

    assert proc.returncode == HALT_EXIT_CODE, f"expected 87, got {proc.returncode}\n{proc.stdout}\n{proc.stderr}"
    payload = json.loads(proc.stdout)
    assert payload["status"] == "HALT"
    assert payload["runner_check"]["reason"] == "truncated_zero"
    assert payload["test_check"]["ok"] is True

    # escalation marker 파일 생성 확인 (escalation_marker.py가 WORKSPACE/memory/events/<task>.done.escalated 생성)
    escalation_marker_file = bf["events_dir"] / "task-2586-fx-02.done.escalated"
    assert escalation_marker_file.exists(), f"escalation marker 미생성: {escalation_marker_file}"
    em_payload = json.loads(escalation_marker_file.read_text(encoding="utf-8"))
    assert em_payload["kind"] == "escalated"
    assert em_payload["source"] == "fifth_truncate_halt_guard"
    assert em_payload["blocking_condition"] == "fifth_truncate_recurrence"


def test_03_negative_test_truncate(baseline_fixture):
    """T3 Negative — test 0 bytes → escalation marker emit + non-zero exit."""
    bf = baseline_fixture
    bf["test_path"].write_bytes(b"")
    proc = _run_guard(bf, escalation_task_id="task-2586-fx-03")

    assert proc.returncode == HALT_EXIT_CODE
    payload = json.loads(proc.stdout)
    assert payload["status"] == "HALT"
    assert payload["test_check"]["reason"] == "truncated_zero"
    assert payload["runner_check"]["ok"] is True

    escalation_marker_file = bf["events_dir"] / "task-2586-fx-03.done.escalated"
    assert escalation_marker_file.exists()


def test_04_negative_both_truncate(baseline_fixture):
    """T4 Negative — 둘 다 0 bytes → escalation marker emit + non-zero exit."""
    bf = baseline_fixture
    bf["runner_path"].write_bytes(b"")
    bf["test_path"].write_bytes(b"")
    proc = _run_guard(bf, escalation_task_id="task-2586-fx-04")

    assert proc.returncode == HALT_EXIT_CODE
    payload = json.loads(proc.stdout)
    assert payload["status"] == "HALT"
    assert payload["runner_check"]["reason"] == "truncated_zero"
    assert payload["test_check"]["reason"] == "truncated_zero"

    escalation_marker_file = bf["events_dir"] / "task-2586-fx-04.done.escalated"
    assert escalation_marker_file.exists()


def test_05_negative_partial_size_drop(baseline_fixture):
    """T5 Negative — partial size drop (runner 100 bytes vs baseline N bytes) → mismatch escalation."""
    bf = baseline_fixture
    bf["runner_path"].write_bytes(b"Z" * 100)  # baseline_size != 100
    proc = _run_guard(bf, escalation_task_id="task-2586-fx-05")

    assert proc.returncode == HALT_EXIT_CODE
    payload = json.loads(proc.stdout)
    assert payload["status"] == "HALT"
    assert payload["runner_check"]["reason"] == "size_mismatch"


def test_06_edge_sha256_mismatch_same_size(baseline_fixture):
    """T6 Edge — runner size == baseline_size 이지만 sha256 mismatch."""
    bf = baseline_fixture
    same_size_different = bytearray(RUNNER_BASELINE_CONTENT)
    same_size_different[0] = (same_size_different[0] + 1) % 256
    bf["runner_path"].write_bytes(bytes(same_size_different))
    assert bf["runner_path"].stat().st_size == bf["runner_size"], "fixture invariant: size 동일해야 함"

    proc = _run_guard(bf, escalation_task_id="task-2586-fx-06")
    assert proc.returncode == HALT_EXIT_CODE
    payload = json.loads(proc.stdout)
    assert payload["status"] == "HALT"
    assert payload["runner_check"]["reason"] == "sha256_mismatch"


def test_07_audit_trail_emit_verification(baseline_fixture):
    """T7 Audit-trail emit — escalation_marker.py가 tmp_path WORKSPACE 안 state-recovery.jsonl 에 append.

    production audit-trail (memory/logs/audit-trail.jsonl) 조작 0건 — tmp_path 격리 검증.
    """
    bf = baseline_fixture
    bf["runner_path"].write_bytes(b"")
    proc = _run_guard(bf, escalation_task_id="task-2586-fx-07")
    assert proc.returncode == HALT_EXIT_CODE

    audit_path = bf["tmp_root"] / "memory" / "orchestration-audit" / "state-recovery.jsonl"
    assert audit_path.exists(), f"escalation audit log 미생성: {audit_path}"
    lines = [ln for ln in audit_path.read_text(encoding="utf-8").splitlines() if ln.strip()]
    assert lines, "audit log empty"
    last_record = json.loads(lines[-1])
    assert last_record["action"] == "emit_ok"
    assert last_record["source"] == "fifth_truncate_halt_guard"
    assert last_record["task_id"] == "task-2586-fx-07"

    # production audit-trail 미존재 검증 (tmp_root 격리)
    prod_audit = WORKSPACE / "memory" / "logs" / "audit-trail.jsonl"
    if prod_audit.exists():
        # 본 test가 production audit-trail 을 만지지 않았어야 함. 사이즈 기록은
        # 본 test scope 외 — 단순히 tmp_root != WORKSPACE 격리만 verify.
        assert str(prod_audit).startswith(str(WORKSPACE))
        assert str(audit_path).startswith(str(bf["tmp_root"]))
        assert not str(audit_path).startswith(str(WORKSPACE / "memory" / "logs"))


def test_08_halt_action_verification_4_conditions(baseline_fixture):
    """T8 Halt action verification (회장 #3 verbatim 4 조건).

    1. guard halt trigger 발생 (tmp_path truncate)
    2. escalation_marker.py subprocess 호출 path verify
    3. escalation JSON file 생성 확인
    4. non-zero exit code 87 + Telegram API mock call 인터셉트 (실제 발송 금지)
    """
    bf = baseline_fixture
    bf["runner_path"].write_bytes(b"")
    bf["test_path"].write_bytes(b"")

    proc = _run_guard(bf, escalation_task_id="task-2586-fx-08")

    # 조건 1: guard halt trigger
    assert proc.returncode != 0, "halt trigger 미발생"
    payload = json.loads(proc.stdout)
    assert payload["status"] == "HALT"

    # 조건 2: escalation_marker.py subprocess 호출 path verify
    em_result = payload["escalation_marker_result"]
    assert em_result["called"] is True, "escalation_marker.py subprocess 미호출"
    cmd_path = em_result["cmd"][1]
    assert cmd_path == str(ESCALATION_MARKER_SCRIPT), (
        f"escalation_marker.py path mismatch: expected {ESCALATION_MARKER_SCRIPT}, got {cmd_path}"
    )
    assert em_result["returncode"] == 0, f"escalation_marker.py subprocess 실패: {em_result}"

    # 조건 3: escalation JSON file 생성 확인
    em_file = bf["events_dir"] / "task-2586-fx-08.done.escalated"
    assert em_file.exists(), f"escalation JSON file 미생성: {em_file}"
    em_payload = json.loads(em_file.read_text(encoding="utf-8"))
    assert em_payload["kind"] == "escalated"
    assert em_payload["task_id"] == "task-2586-fx-08"
    assert em_payload["source"] == "fifth_truncate_halt_guard"
    assert em_payload["blocking_condition"] == "fifth_truncate_recurrence"
    # 필수 6 fields 모두 박제 (escalation_marker.py REQUIRED_PAYLOAD_FIELDS)
    for required in ("reason", "ts", "task_id", "source", "blocking_condition", "evidence_path"):
        assert required in em_payload and em_payload[required], f"escalation marker 누락 field: {required}"

    # 조건 4a: non-zero exit code 87 (회장 verbatim "halt_fifth_truncate" semantics)
    assert proc.returncode == HALT_EXIT_CODE, f"halt exit code 87 mismatch: got {proc.returncode}"

    # 조건 4b: Telegram API mock call 인터셉트 확인 (실제 발송 금지)
    assert bf["telegram_mock_path"].exists(), "telegram mock 미호출"
    mock_payload = json.loads(bf["telegram_mock_path"].read_text(encoding="utf-8"))
    assert "HOLD_FOR_CHAIR" in mock_payload["message"]
    assert "fifth_truncate_halt" in mock_payload["message"]

    # 보조: side marker (task-2590.fifth-truncate-halt-trigger.json) 박제 확인
    side_marker = bf["events_dir"] / "task-2590.fifth-truncate-halt-trigger.json"
    assert side_marker.exists()
    sm_payload = json.loads(side_marker.read_text(encoding="utf-8"))
    assert sm_payload["trigger"] == "fifth_truncate_halt_guard"
    assert sm_payload["halt_exit_code"] == HALT_EXIT_CODE
