"""Tests for coverage_validator segment duration bounds BLOCK check — Phase 2."""

import sys
from pathlib import Path

# Path setup so these tests can import workspace + pipeline modules
_RECOIL_ROOT = Path(__file__).resolve().parent.parent
_PIPELINE_ROOT = _RECOIL_ROOT / "pipeline"
for _p in [str(_RECOIL_ROOT), str(_PIPELINE_ROOT)]:
    if _p not in sys.path:
        sys.path.insert(0, _p)

from orchestrator.coverage_planner import CoveragePass, CoverageSegment
from orchestrator.coverage_validator import Severity, validate_pass, validate_all_passes


def _make_pass(model: str, duration_s: int) -> CoveragePass:
    """Build a minimal CoveragePass with a single segment of the given duration."""
    seg = CoverageSegment(
        segment_index=0,
        source_shot_id="SH001",
        shot_type="MS",
        duration_s=duration_s,
        prompt="test prompt",
    )
    return CoveragePass(
        pass_id="EP001_PASS_001_SH1_N_CHAR",
        episode_id="EP001",
        shot_range=("SH001", "SH001"),
        camera_side="N",
        label="CHAR B (LOC_A)",
        focus_character="CHAR",
        pass_type="character",
        location_id="LOC_A",
        segments=[seg],
        generation_config={
            "model": model,
            "mode": "t2v",
            "aspect_ratio": "9:16",
            "cfg_scale": 0.55,
            "start_frame_path": None,
        },
    )


def test_seeddance_pass_with_3s_segment_is_blocked():
    """seeddance-2.0 has min=4s, so a 3s segment should BLOCK on segment_duration_model_bounds."""
    p = _make_pass("seeddance-2.0", 3)
    results = validate_pass(p)
    bounds_results = [r for r in results if r.check == "segment_duration_model_bounds"]
    assert len(bounds_results) >= 1
    assert all(r.severity == Severity.BLOCK for r in bounds_results)


def test_seeddance_pass_with_4s_segment_is_ok():
    """seeddance-2.0 has min=4s, so a 4s segment should NOT trigger segment_duration_model_bounds."""
    p = _make_pass("seeddance-2.0", 4)
    results = validate_pass(p)
    bounds_results = [r for r in results if r.check == "segment_duration_model_bounds"]
    assert len(bounds_results) == 0


def test_kling_pass_with_2s_segment_is_blocked():
    """kling-v3 has min=3s, so a 2s segment should BLOCK on segment_duration_model_bounds."""
    p = _make_pass("kling-v3", 2)
    results = validate_pass(p)
    bounds_results = [r for r in results if r.check == "segment_duration_model_bounds"]
    assert len(bounds_results) >= 1
    assert all(r.severity == Severity.BLOCK for r in bounds_results)


def test_unknown_model_emits_block_not_crash():
    """When generation_config.model is unknown, validator emits a BLOCK instead of raising."""
    p = _make_pass("nonexistent-model-xyz", 5)
    results = validate_pass(p)
    unknown_results = [r for r in results if r.check == "unknown_model"]
    assert len(unknown_results) >= 1
    assert all(r.severity == Severity.BLOCK for r in unknown_results)


# ── REC-72 D0b: prose_verify gate ─────────────────────────────────────────────


def _make_prose_pass(prose_verify_spec):
    """Build a t2v pass (4s single segment, model seeddance-2.0) carrying an
    optional prose_verify spec in generation_config."""
    seg = CoverageSegment(
        segment_index=0,
        source_shot_id="SH001",
        shot_type="MS",
        duration_s=4,
        prompt="bound prompt",
    )
    gen_config = {
        "model": "seeddance-2.0",
        "mode": "t2v",
        "aspect_ratio": "9:16",
        "cfg_scale": 0.55,
        "start_frame_path": None,
    }
    if prose_verify_spec is not None:
        gen_config["prose_verify"] = prose_verify_spec
    return CoveragePass(
        pass_id="EP002_PASS_001_SH1_N_CHAR",
        episode_id="EP002",
        shot_range=("SH001", "SH001"),
        camera_side="N",
        label="CHAR (LOC_A)",
        focus_character="CHAR",
        pass_type="character",
        location_id="LOC_A",
        segments=[seg],
        generation_config=gen_config,
    )


def _prose_checks(results):
    return [r for r in results if r.check.startswith("prose_verify")]


def test_prose_verify_noop_when_key_absent():
    """ALL of Phase 0: prose_verify key absent → strict NO-OP, no prose blocks.
    This is the only behavior exercised until D2 lands."""
    p = _make_prose_pass(None)
    results = validate_pass(p)
    assert _prose_checks(results) == []


def test_prose_verify_clean_prose_passes():
    p = _make_prose_pass({
        "authored_prose": "[0:00-0:04] The subject JADE walks down the corridor.",
        "prompt_skeleton": {
            "char_ids": ["JADE"],
            "segments": [{"timecode": "[0:00-0:04]"}],
            "duration_s": 4,
        },
    })
    results = validate_pass(p)
    blocks = [r for r in _prose_checks(results) if r.severity == Severity.BLOCK]
    assert blocks == [], f"unexpected prose blocks: {[b.message for b in blocks]}"


def test_prose_verify_missing_char_id_blocks():
    p = _make_prose_pass({
        "authored_prose": "[0:00-0:04] The subject walks down the corridor.",
        "prompt_skeleton": {
            "char_ids": ["JADE"],
            "segments": [{"timecode": "[0:00-0:04]"}],
            "duration_s": 4,
        },
    })
    results = validate_pass(p)
    char_blocks = [r for r in results if r.check == "prose_verify_coverage_char"]
    assert len(char_blocks) >= 1
    assert all(r.severity == Severity.BLOCK for r in char_blocks)


def test_prose_verify_segment_count_mismatch_blocks():
    p = _make_prose_pass({
        "authored_prose": "[0:00-0:02] JADE walks. [0:02-0:04] JADE turns.",  # 2 timecodes
        "prompt_skeleton": {
            "char_ids": ["JADE"],
            "segments": [{"timecode": "[0:00-0:04]"}],  # skeleton has 1
            "duration_s": 4,
        },
    })
    results = validate_pass(p)
    seg_blocks = [r for r in results if r.check == "prose_verify_segment_count"]
    assert len(seg_blocks) >= 1
    assert all(r.severity == Severity.BLOCK for r in seg_blocks)


def test_prose_verify_imagen_literal_blocks():
    p = _make_prose_pass({
        "authored_prose": "[0:00-0:04] @Image1 walks down the corridor.",  # author leaked @Image1
        "prompt_skeleton": {
            "char_ids": [],
            "segments": [{"timecode": "[0:00-0:04]"}],
            "duration_s": 4,
        },
    })
    results = validate_pass(p)
    leak_blocks = [r for r in results if r.check == "prose_verify_imagen_leak"]
    assert len(leak_blocks) >= 1
    assert all(r.severity == Severity.BLOCK for r in leak_blocks)


def test_prose_verify_block_flows_through_validate_all_passes():
    """A prose_verify BLOCK must aggregate into validate_all_passes so the CLI
    block path (generate.py) catches it."""
    p = _make_prose_pass({
        "authored_prose": "[0:00-0:04] The subject walks.",  # missing JADE
        "prompt_skeleton": {
            "char_ids": ["JADE"],
            "segments": [{"timecode": "[0:00-0:04]"}],
            "duration_s": 4,
        },
    })
    results = validate_all_passes([p])
    blocks = [r for r in results if r.severity == Severity.BLOCK]
    assert any(r.check.startswith("prose_verify") for r in blocks)
