# -*- coding: utf-8 -*-
"""task-2776 — live_inbox evidence wiring 회귀 테스트.

목표(작업지시서):
  main 의 event_strategy/live_inbox 분기(driver:~2355)에서
  scan_live_inbox_once(..., write_evidence=True) 를 **명시 전달**해, actual main
  runtime 경로의 LIVE_* verdict(LIVE_PENDING_OWNER_PROOF 등)가
  driver_runs.jsonl(EVIDENCE_JSONL_REL)에 영속되게 한다(scan_once 경로와 대칭).

핵심 불변식:
  ① main-path LIVE_PENDING 영속 — main(limited_runtime=True) 격리 root 로 event_strategy
     ON + live_inbox fixture(head_sha mismatch) → driver_runs 에 LIVE_PENDING_OWNER_PROOF
     evidence persisted · callback 호출 0 · REAL_FIRED 0 · fixture move 0 · canonical write 0.
     ★ 함수 직접 호출이 아니라 main() runtime 경로 격리 검증.
  ② event_strategy OFF 회귀 0 — scan_once/process_one 경로 무변경(scan_live 미진입).
  ③ callback flag OFF safety — callback_launch_enabled 부재 → callback 호출 0 · real fire 0 · cron 0.
  ④ duplicate evidence 방지 — main live scan 1회에 LIVE_* evidence 정확히 1줄(중복 0).
     report_records(reporter_outbox/ledger)는 별도 파일이라 이중 기록 아님(명시 검증).
  ⑤ ACTIVE=false default / disabled NOOP — driver disabled → live wiring 임의 실행 0.

검증 대상 심볼: dispatch.anu_pickup_driver.main (live-inbox 분기 결선).
"""
from __future__ import annotations

import importlib.util as _ilu
import json
import sys
import time
from pathlib import Path

# ── import 부트스트랩 (test_limited_activation_bounds_2775.py 와 동일) ────────────
_ROOT = Path(__file__).resolve().parents[2]
if str(_ROOT) not in sys.path:
    sys.path.insert(0, str(_ROOT))

# tests/dispatch (테스트용 빈 패키지)가 실제 dispatch 패키지를 가리는 것을 방지:
# 실제 dispatch 패키지를 파일 위치로 직접 로드해 sys.modules 에 고정한다.
_real_init = _ROOT / "dispatch" / "__init__.py"
_cached = sys.modules.get("dispatch")
if _cached is None or (getattr(_cached, "__file__", "") or "") != str(_real_init):
    for _k in [k for k in list(sys.modules) if k == "dispatch" or k.startswith("dispatch.")]:
        del sys.modules[_k]
    _spec = _ilu.spec_from_file_location(
        "dispatch", _real_init, submodule_search_locations=[str(_ROOT / "dispatch")]
    )
    assert _spec is not None and _spec.loader is not None
    _pkg = _ilu.module_from_spec(_spec)
    sys.modules["dispatch"] = _pkg
    _spec.loader.exec_module(_pkg)

from dispatch import anu_pickup_driver as _drv  # noqa: E402
import dispatch.anu_result_pickup_runner as _runner  # noqa: E402
import dispatch.anu_callback_launch_audit as _cb  # noqa: E402

VERDICT_LIVE_PENDING_OWNER = _drv.VERDICT_LIVE_PENDING_OWNER       # "LIVE_PENDING_OWNER_PROOF"
VERDICT_LIVE_PROCESSED = _drv.VERDICT_LIVE_PROCESSED               # "LIVE_PROCESSED"
VERDICT_NOOP_DISABLED = _drv.VERDICT_NOOP_DISABLED
EVIDENCE_JSONL_REL = _drv.EVIDENCE_JSONL_REL                       # memory/p0b_state/driver_runs.jsonl
LIVE_INBOX_DIR_REL = _drv.LIVE_INBOX_DIR_REL
LIVE_PROCESSED_DIR_REL = _drv.LIVE_PROCESSED_DIR_REL
ACTIVATION_FLAG_REL = _drv.ACTIVATION_FLAG_REL
ACTIVATION_EPOCH_REL = _drv.ACTIVATION_EPOCH_REL
ACTIVATION_ENABLED = _drv.ACTIVATION_ENABLED
EVENT_STRATEGY_FLAG_REL = _drv.EVENT_STRATEGY_FLAG_REL
EVENT_STRATEGY_ENABLED = _drv.EVENT_STRATEGY_ENABLED
GATE_READY_STATUS = _drv.GATE_READY_STATUS


# ── 테스트 더블 ───────────────────────────────────────────────────────────────
class _StubGate:
    """owner_proof gate 결과 더블 — ready/status 만 노출(scan 이 보는 attr)."""

    def __init__(self, ready: bool):
        self.ready = bool(ready)
        self.status = GATE_READY_STATUS if ready else "OWNER_PROOF_NOT_READY"


def _owner_gate_builder(*, ready: bool, head_sha_expected: str | None = None):
    """build_owner_gate_fn 대체 빌더.

    head_sha_expected 지정 시: fixture result.json 의 head_sha 가 expected 와 **일치할
    때만** ready 로 판정(불일치 → not-ready). 이는 실제 owner_proof gate 의 head_sha
    mismatch 거부를 격리 모델로 재현한다(실 sealed-key/실발사 의존 0)."""

    def _factory(*args, **kwargs):
        def _gate(result_json_path, *, executor_key=None):
            if head_sha_expected is not None:
                try:
                    with open(result_json_path, "r", encoding="utf-8") as fh:
                        head = (json.load(fh) or {}).get("head_sha")
                except (OSError, ValueError, TypeError):
                    head = None
                return _StubGate(ready=(head == head_sha_expected))
            return _StubGate(ready=ready)
        return _gate

    return _factory


def _make_fixture(root: Path, *, task_id: str = "task-2776fx", head_sha: str = "stale-deadbeef") -> Path:
    """격리 root 의 live_inbox 에 final task-*.result.json fixture 생성(head_sha 포함)."""
    inbox = root / LIVE_INBOX_DIR_REL
    inbox.mkdir(parents=True, exist_ok=True)
    fx = inbox / f"{task_id}.result.json"
    fx.write_text(json.dumps({
        "task_id": task_id,
        "head_sha": head_sha,
        "terminal_intent": "callback",
    }), encoding="utf-8")
    return fx


def _enable_driver(root: Path) -> None:
    f = root / ACTIVATION_FLAG_REL
    f.parent.mkdir(parents=True, exist_ok=True)
    f.write_text(ACTIVATION_ENABLED + "\n", encoding="utf-8")


def _enable_event_strategy(root: Path) -> None:
    f = root / EVENT_STRATEGY_FLAG_REL
    f.parent.mkdir(parents=True, exist_ok=True)
    f.write_text(EVENT_STRATEGY_ENABLED + "\n", encoding="utf-8")


def _write_normal_epoch(root: Path) -> None:
    """bound gate 통과용 정상 epoch(now-60초, 활성창 이내) 기록."""
    f = root / ACTIVATION_EPOCH_REL
    f.parent.mkdir(parents=True, exist_ok=True)
    f.write_text(str(time.time() - 60.0), encoding="utf-8")


def _read_evidence_lines(root: Path) -> list:
    ev = root / EVIDENCE_JSONL_REL
    if not ev.exists():
        return []
    out = []
    for line in ev.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if line:
            out.append(json.loads(line))
    return out


# ── 테스트 1 (★핵심): main-path LIVE_PENDING_OWNER_PROOF 영속 ─────────────────
def test_main_live_inbox_pending_owner_evidence_persisted(tmp_path, monkeypatch):
    """main(limited_runtime=True) 격리 root, event_strategy ON + live fixture(head_sha mismatch).

    actual main runtime 경로(legacy 함수 직접 호출 아님)를 태워서:
      - driver_runs.jsonl 에 LIVE_PENDING_OWNER_PROOF evidence persisted
      - callback launch fn 호출 0 · REAL_FIRED 0(LIVE_PROCESSED 0)
      - fixture move 0 (live_inbox 잔존 · processed 디렉토리 비어있음)
      - canonical write 0 (격리 root 만 사용 — 실제 memory 미접촉, monkeypatch 로 구조 보장)
    """
    monkeypatch.setattr(_drv, "CANONICAL_ROOT", str(tmp_path))

    _enable_driver(tmp_path)
    _enable_event_strategy(tmp_path)
    _write_normal_epoch(tmp_path)
    fx = _make_fixture(tmp_path, head_sha="stale-deadbeef")

    # owner gate: head_sha 가 'canonical-head' 와 일치할 때만 ready. fixture 는 'stale-deadbeef'
    # → mismatch → not-ready → LIVE_PENDING_OWNER_PROOF.
    monkeypatch.setattr(
        _runner, "build_owner_gate_fn",
        _owner_gate_builder(ready=False, head_sha_expected="canonical-head"),
    )

    # callback launch fn 스파이: 호출 시 카운트 + 즉시 fire(BRIDGE_FIRED) 흉내내지 않음.
    cb_calls = {"n": 0}

    def _cb_builder(*args, **kwargs):
        def _fn(*a, **k):
            cb_calls["n"] += 1
            return None
        return _fn

    monkeypatch.setattr(_cb, "build_callback_launch_fn", _cb_builder)

    rc = _drv.main([], limited_runtime=True)
    assert rc == 0, f"main 반환 0 기대, 실제 {rc}"

    lines = _read_evidence_lines(tmp_path)
    verdicts = [ln.get("verdict") for ln in lines]

    # (1) LIVE_PENDING_OWNER_PROOF evidence 영속
    assert VERDICT_LIVE_PENDING_OWNER in verdicts, (
        f"driver_runs 에 LIVE_PENDING_OWNER_PROOF 영속 기대, 실제 verdicts={verdicts!r}"
    )
    # (2) callback 호출 0 · REAL_FIRED/LIVE_PROCESSED 0
    assert cb_calls["n"] == 0, f"callback launch 호출 0 기대, 실제 {cb_calls['n']}회"
    assert VERDICT_LIVE_PROCESSED not in verdicts, (
        f"LIVE_PROCESSED(=fire 후 처리)는 없어야 함, 실제 verdicts={verdicts!r}"
    )
    # (3) fixture move 0 — live_inbox 잔존, processed 디렉토리 비어있음/부재
    assert fx.exists(), "fixture 가 live_inbox 에 잔존(move 0)해야 함"
    pdir = tmp_path / LIVE_PROCESSED_DIR_REL
    moved = list(pdir.glob("*.result.json")) if pdir.exists() else []
    assert moved == [], f"processed 이동 0 기대, 실제 {moved!r}"
    # (4) canonical write 0 — completed ledger(LIVE_PROCESSED done-marker) 미생성
    ledger = tmp_path / "memory" / "events" / "p0b_processed_ledger.jsonl"
    assert not ledger.exists(), "fire/processed 0 이므로 완료 ledger 미생성 기대"


# ── 테스트 2: event_strategy OFF 회귀 0 (scan_once 경로 무변경) ───────────────
def test_event_strategy_off_uses_scan_once_not_live(tmp_path, monkeypatch):
    """driver ON + event_strategy 플래그 부재(OFF) → else 분기(scan_once) 도달,
    scan_live_inbox_once 미진입. write_evidence 결선이 OFF 경로에 영향 0."""
    monkeypatch.setattr(_drv, "CANONICAL_ROOT", str(tmp_path))

    _enable_driver(tmp_path)
    _write_normal_epoch(tmp_path)
    # event_strategy 플래그 미생성 (OFF)

    calls = {"scan_once": 0, "scan_live": 0}

    def _spy_scan_once(*a, **k):
        calls["scan_once"] += 1
        return []

    def _spy_scan_live(*a, **k):
        calls["scan_live"] += 1
        return []

    monkeypatch.setattr(_drv, "scan_once", _spy_scan_once)
    monkeypatch.setattr(_drv, "scan_live_inbox_once", _spy_scan_live)

    rc = _drv.main([], limited_runtime=True)
    assert rc == 0
    assert calls["scan_once"] == 1, f"event_strategy OFF → scan_once 1회 기대, 실제 {calls['scan_once']}"
    assert calls["scan_live"] == 0, f"event_strategy OFF → scan_live 미진입 기대, 실제 {calls['scan_live']}"


# ── 테스트 3: callback flag OFF safety (real fire 0 · cron 0) ─────────────────
def test_callback_flag_off_no_real_fire_no_cron(tmp_path, monkeypatch):
    """callback_launch_enabled 부재(OFF) + event_strategy ON + live fixture.

    owner gate 를 ready 로 강제해 callback 단계까지 도달시키되, **실 production
    build_callback_launch_fn**(real_launch_fn 미주입 + flag OFF)을 사용 →
    launch_callback_for_closeout 가 ACTIVE flag OFF 단락(NOOP_DISABLED) →
    real fire 0 · audit 미기록 · cron/schedule 파일 0. evidence 는 정상 영속.
    """
    monkeypatch.setattr(_drv, "CANONICAL_ROOT", str(tmp_path))

    _enable_driver(tmp_path)
    _enable_event_strategy(tmp_path)
    _write_normal_epoch(tmp_path)
    _make_fixture(tmp_path, head_sha="any-head")
    # callback flag(p0b_callback_launch_enabled) 미생성 (OFF)

    # owner gate ready → callback 단계 도달
    monkeypatch.setattr(
        _runner, "build_owner_gate_fn", _owner_gate_builder(ready=True),
    )
    # build_callback_launch_fn 은 실 production 빌더 그대로 사용(monkeypatch 안 함) —
    # real_launch_fn 미주입 + flag OFF → 전면 no-op. 실 호출 횟수는 audit 부재로 검증.

    rc = _drv.main([], limited_runtime=True)
    assert rc == 0

    # real fire 0 · cron 0: callback audit jsonl 미생성(flag OFF → write_audit=False)
    audit = tmp_path / _cb.DEFAULT_AUDIT_JSONL_REL
    assert not audit.exists(), (
        "callback flag OFF → audit 미기록(real fire 0·cron 0) 기대, 실제 audit 파일 존재"
    )

    # evidence 는 영속됨(LIVE_* verdict 한 줄 이상). 이 경로는 callback no-op 후 move 성공 시
    # LIVE_PROCESSED, move 실패 시 PENDING_MOVE — 어느 쪽이든 LIVE_* 가 1줄 이상 영속.
    lines = _read_evidence_lines(tmp_path)
    assert any(str(ln.get("verdict", "")).startswith("LIVE_") for ln in lines), (
        f"callback OFF safety 경로에서도 LIVE_* evidence 영속 기대, 실제 {[l.get('verdict') for l in lines]!r}"
    )


# ── 테스트 4: duplicate evidence 방지 (정확히 1줄) ───────────────────────────
def test_no_duplicate_evidence_single_line(tmp_path, monkeypatch):
    """main live scan 1회 → LIVE_PENDING_OWNER_PROOF evidence 가 driver_runs 에 정확히 1줄.

    _emit→_append_evidence(driver_runs)와 report_records(reporter_outbox/ledger)는 별도
    파일 — 이중 기록 아님을 명시 검증한다(driver_runs 에 LIVE_* 정확히 1줄)."""
    monkeypatch.setattr(_drv, "CANONICAL_ROOT", str(tmp_path))

    _enable_driver(tmp_path)
    _enable_event_strategy(tmp_path)
    _write_normal_epoch(tmp_path)
    _make_fixture(tmp_path, head_sha="stale-deadbeef")

    monkeypatch.setattr(
        _runner, "build_owner_gate_fn",
        _owner_gate_builder(ready=False, head_sha_expected="canonical-head"),
    )

    def _cb_builder(*args, **kwargs):
        def _fn(*a, **k):
            return None
        return _fn

    monkeypatch.setattr(_cb, "build_callback_launch_fn", _cb_builder)

    rc = _drv.main([], limited_runtime=True)
    assert rc == 0

    lines = _read_evidence_lines(tmp_path)
    pending = [ln for ln in lines if ln.get("verdict") == VERDICT_LIVE_PENDING_OWNER]
    assert len(pending) == 1, (
        f"driver_runs 에 LIVE_PENDING_OWNER_PROOF 정확히 1줄 기대(중복 0), 실제 {len(pending)}줄 "
        f"(전체 verdicts={[l.get('verdict') for l in lines]!r})"
    )

    # report_records 산출물(reporter_outbox/ledger)은 driver_runs 와 다른 파일 — driver_runs
    # 자체가 1줄임을 위에서 확인. reporter 산출물이 존재하더라도 driver_runs 에 합산되지 않음.
    reporter_outbox = tmp_path / getattr(
        _drv, "REPORTER_OUTBOX_REL", "memory/p0b_state/reporter_outbox.jsonl"
    )
    assert (tmp_path / EVIDENCE_JSONL_REL).resolve() != reporter_outbox.resolve(), (
        "driver_runs(evidence)와 reporter_outbox(보고)는 별도 경로여야 함"
    )


# ── 테스트 5: ACTIVE=false / disabled NOOP (live wiring 임의 실행 0) ──────────
def test_disabled_driver_noop_no_live_wiring(tmp_path, monkeypatch):
    """driver flag 부재(disabled) + event_strategy ON(설정돼 있어도) →
    is_activated False → live 분기 미진입(scan_live 미호출). 기존처럼 NOOP.
    write_evidence 결선이 disabled 상태에서 임의 실행되지 않음."""
    monkeypatch.setattr(_drv, "CANONICAL_ROOT", str(tmp_path))

    # driver flag 미생성 (disabled). event_strategy 는 ON 으로 둬도 2중 게이팅에서 차단.
    _enable_event_strategy(tmp_path)
    _make_fixture(tmp_path, head_sha="stale-deadbeef")

    calls = {"scan_live": 0, "scan_once": 0}

    def _spy_scan_live(*a, **k):
        calls["scan_live"] += 1
        return []

    def _spy_scan_once(*a, **k):
        calls["scan_once"] += 1
        return []

    monkeypatch.setattr(_drv, "scan_live_inbox_once", _spy_scan_live)
    monkeypatch.setattr(_drv, "scan_once", _spy_scan_once)

    rc = _drv.main([], limited_runtime=True)
    assert rc == 0

    # driver disabled → live 분기 미진입(is_activated False). else(scan_once) 경로로 가되
    # scan_once 내부 activation gate 가 NOOP_DISABLED 단락(여기선 spy 라 호출만 확인).
    assert calls["scan_live"] == 0, (
        f"disabled → scan_live_inbox_once 미호출 기대, 실제 {calls['scan_live']}회"
    )

    # live evidence(LIVE_*) 가 임의로 영속되지 않음
    lines = _read_evidence_lines(tmp_path)
    live_lines = [ln for ln in lines if str(ln.get("verdict", "")).startswith("LIVE_")]
    assert live_lines == [], (
        f"disabled 상태에서 LIVE_* evidence 영속 0 기대, 실제 {[l.get('verdict') for l in live_lines]!r}"
    )


# ── 테스트 6: 정적 결선 검증 — main 소스에 write_evidence=True 명시 전달 ──────
def test_main_source_passes_write_evidence_true():
    """main 의 scan_live_inbox_once 호출이 write_evidence=True 를 명시 전달하는지 정적 검증.

    scan_once 경로(write_evidence=True 기본)와 대칭임을 소스 텍스트로 보증한다."""
    import inspect
    src = inspect.getsource(_drv.main)
    assert "scan_live_inbox_once(" in src, "main 에 scan_live_inbox_once 호출이 있어야 함"
    # scan_live_inbox_once(...) 호출 블록 안에 write_evidence=True 가 포함되는지 확인
    call_idx = src.index("scan_live_inbox_once(")
    tail = src[call_idx:call_idx + 400]
    assert "write_evidence=True" in tail, (
        "main 의 scan_live_inbox_once 호출에 write_evidence=True 명시 전달이 없음 "
        "(driver_runs 영속 결선 누락)"
    )
