"""REC-72 Phase 0 meta-gate.

Proves END-TO-END, via the LIVE code paths (not reimplementations), that the
two Phase-0 fail-closed contracts hold:

  (A) A prose_verify BLOCK flows through the real generate.run_generation
      validate path and returns {"error": "validation_blocked"}.
  (B) The hardened name-set-independent scrub raises on an injected UNKNOWN
      proper noun (one that is not in the known-character set).

run_overnight.py has no --validate surface and does not reach the gate, so this
gate uses generate.run_generation(validate_only=True) against a synthetic passes
file (no project on disk, no generation, no spend).
"""

import json
import sys
from pathlib import Path
from types import SimpleNamespace

import pytest

_RECOIL_ROOT = Path(__file__).resolve().parent.parent
_PIPELINE_ROOT = _RECOIL_ROOT / "pipeline"
for _p in [str(_RECOIL_ROOT.parent), str(_RECOIL_ROOT), str(_PIPELINE_ROOT)]:
    if _p not in sys.path:
        sys.path.insert(0, _p)

from recoil.pipeline.cli import generate as gen_cli
from recoil.pipeline.tools.audit_assertions import assert_no_proper_nouns


def _block_pass_dict() -> dict:
    """A coverage pass dict whose prose_verify spec is missing a skeleton
    char_id → guaranteed prose_verify_coverage_char BLOCK."""
    return {
        "pass_id": "EP002_PASS_009_METAGATE",
        "episode_id": "EP002",
        "shot_range": ["SH001", "SH001"],
        "camera_side": "N",
        "label": "META (LOC)",
        "focus_character": "JADE",
        "pass_type": "character",
        "location_id": "LOC",
        "segments": [{
            "segment_index": 0,
            "source_shot_id": "SH001",
            "shot_type": "MS",
            "duration_s": 4,
            "prompt": "bound prompt",
        }],
        "generation_config": {
            "model": "seeddance-2.0",
            "mode": "t2v",
            "aspect_ratio": "9:16",
            "cfg_scale": 0.55,
            "start_frame_path": None,
            "prose_verify": {
                "authored_prose": "[0:00-0:04] The subject walks.",  # JADE missing
                "prompt_skeleton": {
                    "char_ids": ["JADE"],
                    "segments": [{"timecode": "[0:00-0:04]"}],
                    "duration_s": 4,
                },
            },
        },
    }


def _patch_paths(tmp_path, monkeypatch):
    passes_dir = tmp_path / "coverage_passes"
    passes_dir.mkdir(parents=True)
    (passes_dir / "ep_002_passes.json").write_text(
        json.dumps([_block_pass_dict()]), encoding="utf-8"
    )

    def _fake_for_episode(project, episode):
        return SimpleNamespace(coverage_passes_dir=passes_dir)

    monkeypatch.setattr(gen_cli.ProjectPaths, "for_episode", staticmethod(_fake_for_episode))


def test_prose_verify_block_validate_branch_fails_closed(tmp_path, monkeypatch):
    """(A1) The --validate branch (validate_only=True) surfaces the prose_verify
    BLOCK as success=False with the block in `blocks` — no spend, no generation."""
    _patch_paths(tmp_path, monkeypatch)
    result = gen_cli.run_generation(project="EP002", episode=2, validate_only=True)
    assert result["success"] is False
    assert any(
        b["check"].startswith("prose_verify") for b in result["blocks"]
    ), f"no prose_verify block in {result['blocks']}"


def test_prose_verify_block_live_branch_returns_validation_blocked(tmp_path, monkeypatch):
    """(A2) The LIVE-run branch (validate_only=False) short-circuits BEFORE any
    dispatch/spend and returns error=validation_blocked when prose_verify BLOCKs."""
    _patch_paths(tmp_path, monkeypatch)
    result = gen_cli.run_generation(
        project="EP002", episode=2, pass_ids=["EP002_PASS_009_METAGATE"], validate_only=False
    )
    assert result["success"] is False
    assert result["error"] == "validation_blocked"
    assert any(
        b["check"].startswith("prose_verify") for b in result["blocks"]
    ), f"no prose_verify block in {result['blocks']}"


def test_hardened_scrub_raises_on_injected_unknown_proper_noun():
    """(B) The hardened scrub hard-fails on an UNKNOWN interior proper noun even
    when the known-character set is empty."""
    with pytest.raises(AssertionError, match="unknown proper noun"):
        assert_no_proper_nouns(
            {"prompt": "The subject pauses as Zephyrine steps forward."},
            "video_i2v",
            "seeddance-2.0",
            {"asset_data": {"characters": []}},
        )
