# -*- coding: utf-8 -*-
"""tests/regression/test_collector_spawn_dry_run.py

task-2635 — REGISTERED→spawn expected · NOT_REGISTERED→no-spawn ·
subprocess 실호출 0.

Spec §5: REGISTERED status + cron_schedule_id 입력 → collector spawn
expected=true 단언 · NOT_REGISTERED 입력 → collector spawn expected=false 단언 ·
subprocess 실호출 0 (frozen fixture 기반 dry-run).
"""
from __future__ import annotations

import json
from pathlib import Path

import pytest

from utils.anu_callback_fallback import (
    decide_fallback_cancel,
    expected_collector_spawn,
    explain_fail_closed,
    has_durable_success_marker,
)
from utils.callback_envelope_schema import (
    NormalCallbackRegistrationStatus,
    is_callback_complete,
)

FIXTURE_ROOT = (
    Path(__file__).resolve().parents[1]
    / "fixtures"
    / "normal_callback_registration"
)


def _load_evidence(scenario: str):
    return json.loads(
        (FIXTURE_ROOT / scenario / "evidence.json").read_text(encoding="utf-8")
    )


def _load_expected(scenario: str):
    return json.loads(
        (FIXTURE_ROOT / scenario / "expected.json").read_text(encoding="utf-8")
    )


# ── REGISTERED → spawn expected = true ───────────────────────────────────────


def test_registered_normal_spawns_collector():
    ev = _load_evidence("registered_normal")
    assert ev["registration_status"] == NormalCallbackRegistrationStatus.REGISTERED.value
    assert expected_collector_spawn(ev) is True
    assert is_callback_complete(ev) is True


def test_registered_normal_with_durable_marker_cancels_fallback():
    ev = _load_evidence("registered_normal")
    assert has_durable_success_marker(ev) is True
    decision = decide_fallback_cancel(ev)
    assert decision.cancel_fallback is True
    assert decision.schedule_id == ev["cron_schedule_id"]


# ── NOT_REGISTERED → spawn expected = false ──────────────────────────────────


@pytest.mark.parametrize(
    "scenario",
    [
        "not_registered_envelope_only",
        "sendfile_only_no_cron",
        "register_failed_cli_error",
    ],
)
def test_fail_closed_statuses_do_not_spawn_collector(scenario):
    ev = _load_evidence(scenario)
    assert expected_collector_spawn(ev) is False
    assert is_callback_complete(ev) is False
    decision = decide_fallback_cancel(ev)
    assert decision.cancel_fallback is False, (
        f"{scenario}: fail-closed must not cancel fallback (reason={decision.reason})"
    )


def test_skipped_with_explicit_reason_does_not_spawn_collector():
    """SKIPPED is a success-status for fail-closed gating, but the collector
    DOES NOT spawn (no actual cron registration occurred)."""
    ev = _load_evidence("skipped_explicit_reason_dryrun")
    assert (
        ev["registration_status"]
        == NormalCallbackRegistrationStatus.SKIPPED_WITH_EXPLICIT_REASON.value
    )
    assert expected_collector_spawn(ev) is False
    assert is_callback_complete(ev) is False
    # Double-lock present → fallback cancel allowed despite no-spawn.
    decision = decide_fallback_cancel(ev)
    assert decision.cancel_fallback is True


# ── subprocess actual-call ZERO budget ───────────────────────────────────────


def test_no_subprocess_call_during_fixture_decisions():
    """Reading fixtures and calling decision pure-functions must not invoke
    any subprocess. We patch subprocess.run to a tripwire and re-run the
    decision suite — any call would fail the test.
    """
    import subprocess

    tripwire_calls = []
    real_run = subprocess.run

    def tripwire(*args, **kwargs):
        tripwire_calls.append((args, kwargs))
        raise AssertionError(
            "subprocess.run invoked during fixture-based decision suite — "
            "spec §7 (live cokacdir CLI 실호출 0) violated"
        )

    subprocess.run = tripwire  # type: ignore[assignment]
    try:
        for scenario in (
            "registered_normal",
            "not_registered_envelope_only",
            "sendfile_only_no_cron",
            "register_failed_cli_error",
            "skipped_explicit_reason_dryrun",
        ):
            ev = _load_evidence(scenario)
            _ = expected_collector_spawn(ev)
            _ = is_callback_complete(ev)
            _ = decide_fallback_cancel(ev)
            _ = explain_fail_closed(ev)
    finally:
        subprocess.run = real_run  # type: ignore[assignment]

    assert tripwire_calls == []


# ── fixture matrix sanity (cross-check expected.json) ────────────────────────


@pytest.mark.parametrize(
    "scenario",
    [
        "registered_normal",
        "not_registered_envelope_only",
        "sendfile_only_no_cron",
        "register_failed_cli_error",
        "skipped_explicit_reason_dryrun",
    ],
)
def test_runtime_spawn_decision_matches_expected_field(scenario):
    ev = _load_evidence(scenario)
    expected = _load_expected(scenario)
    assert expected_collector_spawn(ev) is bool(expected["collector_spawn_expected"])
    assert is_callback_complete(ev) is bool(expected["is_callback_complete"])
