#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
=============================================================================
KIEU STORY — EMPIRICAL ADVERSARIAL TEST SUITE: TIER 5 PIPELINE HARDENING
=============================================================================
Agent: teamwork_preview_challenger (Tier 5 Challenger 2 - Audio, Pipeline & Assembly)
Target Scope: Features F8 through F13
- F8: Audio Continuity & Crossfade Concat (Mode A/B, stream pre-conditioning, mute fallback)
- F9: 4-Stem Continuous Audio Architecture (stems, ducking, EQ notch)
- F10: Two-Pass Linear EBU R128 -14 LUFS Loudness Normalization & Silence Guard
- F11: Web Review Studio (0.0.0.0:1515, 188-shot matrix, concurrency 409 guard, validation)
- F12: Production Orchestrator & Automation (scene shot retrieval, versioning resolution)
- F13: Grand Feature Assembly (assemble_ep01_feature.py evaluation: 15-scene coverage,
       versioning _v*, missing scene handling, headless CI/CD execution)
=============================================================================
"""

import os
import sys
import re
import json
import time
import shutil
import tempfile
import subprocess
import unittest
from pathlib import Path
from typing import Dict, Any, List, Optional

# Ensure UTF-8 on Windows
if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8")
        sys.stderr.reconfigure(encoding="utf-8")
    except Exception:
        pass

BASE_DIR = Path(__file__).resolve().parent.parent
PIPELINE_DIR = BASE_DIR / "05_Production_Pipeline"
WEB_REVIEW_DIR = BASE_DIR / "web_review"
EXPORTS_DIR = BASE_DIR / "06_Exports"
VIDEOS_DIR = BASE_DIR / "04_Assets" / "videos"

for p in [str(PIPELINE_DIR), str(WEB_REVIEW_DIR), str(BASE_DIR)]:
    if p not in sys.path:
        sys.path.insert(0, p)

from starlette.testclient import TestClient
import server as srv
from audio_continuity_engine import AudioContinuityEngine, get_ffmpeg, get_ffprobe
import production_orchestrator as orch


class TestTier5PipelineHardening(unittest.TestCase):
    """
    Tier 5 Adversarial Coverage & Hardening Test Suite for Features F8-F13.
    """

    @classmethod
    def setUpClass(cls):
        cls.ffmpeg = get_ffmpeg()
        cls.ffprobe = get_ffprobe()
        cls.engine = AudioContinuityEngine()
        cls.client = TestClient(srv.app)
        cls.results_json_path = BASE_DIR / "tests" / "adversarial_tier5_pipeline_hardening_results.json"
        cls.telemetry: Dict[str, Any] = {
            "test_suite": "Tier 5 Pipeline Hardening & Adversarial Coverage",
            "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            "total_tests": 0,
            "passed_tests": 0,
            "failed_tests": 0,
            "findings": [],
            "test_details": {}
        }

    @classmethod
    def tearDownClass(cls):
        try:
            with open(cls.results_json_path, "w", encoding="utf-8") as f:
                json.dump(cls.telemetry, f, indent=2, ensure_ascii=False)
            print(f"\n[✓] Telemetry exported to: {cls.results_json_path}")
        except Exception as e:
            print(f"[!] Warning: failed to export telemetry: {e}")

    def _record_result(self, test_name: str, passed: bool, message: str, finding_type: Optional[str] = None):
        cls = self.__class__
        cls.telemetry["total_tests"] += 1
        if passed:
            cls.telemetry["passed_tests"] += 1
        else:
            cls.telemetry["failed_tests"] += 1
            if finding_type:
                cls.telemetry["findings"].append({
                    "test": test_name,
                    "type": finding_type,
                    "message": message
                })
        cls.telemetry["test_details"][test_name] = {
            "passed": passed,
            "message": message
        }

    # =========================================================================
    # SECTION 1: ASSEMBLE_EP01_FEATURE.PY RIGOROUS EVALUATION (FEATURE F13)
    # =========================================================================

    def test_f13_01_assemble_ep01_accounts_for_all_15_scenes(self):
        """
        [ADVERSARIAL CHALLENGE - F13]
        Verify assemble_ep01_feature.py accounts for ALL 15 scenes (C01 to C15).
        EP01 screenplay defines 15 scenes (188 shots = 29m40s). Scene 15 is the
        critical cliffhanger (Gian thờ tổ tiên - 6 shots).
        """
        script_path = PIPELINE_DIR / "assemble_ep01_feature.py"
        self.assertTrue(script_path.exists(), "assemble_ep01_feature.py must exist")
        content = script_path.read_text(encoding="utf-8")

        # Parse scene references in parts list
        missing_scenes = []
        for i in range(1, 16):
            expected_scene_tag = f"scene{i:02d}"
            if expected_scene_tag not in content:
                missing_scenes.append(expected_scene_tag)

        passed = len(missing_scenes) == 0
        msg = f"Missing scenes in assemble_ep01_feature.py: {missing_scenes}" if missing_scenes else "All 15 scenes accounted for"
        self._record_result("test_f13_01_assemble_ep01_accounts_for_all_15_scenes", passed, msg, "DEFECT: Missing Scene 15 in Assembly")
        self.assertEqual(len(missing_scenes), 0, f"assemble_ep01_feature.py misses scenes: {missing_scenes}. Scene 15 is the climax cliffhanger!")

    def test_f13_02_grand_feature_master_output_versioning(self):
        """
        [ADVERSARIAL CHALLENGE - F13]
        Verify assemble_ep01_feature.py adheres to user directive 2026-10-08T05:38:03Z:
        All video MP4 files must have _v<N> versioning suffix (e.g., _v1.mp4, _v2.mp4).
        Master feature output must be versioned (e.g. ep01_grand_feature_master_v1.mp4).
        """
        script_path = PIPELINE_DIR / "assemble_ep01_feature.py"
        content = script_path.read_text(encoding="utf-8")

        # Find output_master assignment
        m = re.search(r'output_master\s*=\s*.*?["\']([^"\']+\.mp4)["\']', content)
        self.assertIsNotNone(m, "output_master must be defined with an .mp4 file")
        output_filename = m.group(1)

        has_version_suffix = bool(re.search(r'_v\d+\.mp4$', output_filename, re.IGNORECASE))
        passed = has_version_suffix
        msg = f"output_master '{output_filename}' lacks _v<N> versioning suffix" if not passed else f"output_master '{output_filename}' correctly versioned"
        self._record_result("test_f13_02_grand_feature_master_output_versioning", passed, msg, "DEFECT: Non-versioned output_master filename")
        self.assertTrue(has_version_suffix, f"output_master '{output_filename}' lacks mandatory _v<N> suffix (violates Directive 2026-10-08T05:38:03Z)")

    def test_f13_03_scene_inputs_support_versioned_masters(self):
        """
        [ADVERSARIAL CHALLENGE - F13]
        Verify assemble_ep01_feature.py supports versioned scene masters (_v1.mp4, _v2.mp4)
        as output by production_orchestrator.py:master_scene_audio.
        Hardcoding unversioned 'ep01_sceneXX_cinematic_master.mp4' breaks when
        the orchestrator generates 'ep01_sceneXX_cinematic_master_v1.mp4'.
        """
        script_path = PIPELINE_DIR / "assemble_ep01_feature.py"
        content = script_path.read_text(encoding="utf-8")

        # Check if script dynamically resolves versioned files or has glob/version fallback
        has_dynamic_resolution = ("glob" in content or "_v" in content or "resolve_versioned" in content or "resolve_next_version" in content)
        passed = has_dynamic_resolution
        msg = "assemble_ep01_feature.py uses static unversioned filenames, incompatible with versioned orchestrator outputs" if not passed else "Dynamic version resolution supported"
        self._record_result("test_f13_03_scene_inputs_support_versioned_masters", passed, msg, "DEFECT: Rigid unversioned scene inputs")
        self.assertTrue(has_dynamic_resolution, "assemble_ep01_feature.py must support versioned scene masters (_v1.mp4) produced by production_orchestrator.py")

    def test_f13_04_no_top_level_side_effects_on_import(self):
        """
        [ADVERSARIAL CHALLENGE - F13]
        Verify assemble_ep01_feature.py does NOT execute top-level execution/sys.exit(1) on import.
        Currently, lines 40-44 check parts and execute sys.exit(1) at top-level import,
        making the module un-importable and untestable in CI/CD environments.
        """
        script_path = PIPELINE_DIR / "assemble_ep01_feature.py"
        content = script_path.read_text(encoding="utf-8")

        has_main_guard = '__name__ == "__main__"' in content or "__name__ == '__main__'" in content
        passed = has_main_guard
        msg = "assemble_ep01_feature.py lacks if __name__ == '__main__': guard (calls sys.exit on import)" if not passed else "Protected by main guard"
        self._record_result("test_f13_04_no_top_level_side_effects_on_import", passed, msg, "DEFECT: Module executes top-level sys.exit on import")
        self.assertTrue(has_main_guard, "assemble_ep01_feature.py must be wrapped in `if __name__ == '__main__':` to allow programmatic invocation and testing")

    def test_f13_05_headless_cicd_dry_run_or_missing_parts_gracefulness(self):
        """
        [ADVERSARIAL CHALLENGE - F13]
        Verify assemble_ep01_feature.py supports CLI flags (--dry-run, --check, --allow-missing, or argparse)
        so headless CI/CD can validate pipeline integrity without failing on missing multi-gigabyte render assets.
        """
        script_path = PIPELINE_DIR / "assemble_ep01_feature.py"
        content = script_path.read_text(encoding="utf-8")

        has_cli_args = "argparse" in content or "sys.argv" in content or "--dry-run" in content or "--check" in content
        passed = has_cli_args
        msg = "assemble_ep01_feature.py lacks CLI argument parsing or dry-run inspection mode for CI/CD" if not passed else "CLI arguments supported"
        self._record_result("test_f13_05_headless_cicd_dry_run_or_missing_parts_gracefulness", passed, msg, "GAP: Missing CLI argument handling / dry-run mode")
        self.assertTrue(has_cli_args, "assemble_ep01_feature.py should provide argparse CLI support with --dry-run / --status options for headless CI/CD")

    def test_f13_06_duration_specification_consistency(self):
        """
        [ADVERSARIAL CHALLENGE - F13]
        Verify assemble_ep01_feature.py title and logs do not contradict the 25-30 minute requirement.
        Currently line 75 prints: '7m30s' and line 37 names it '10min', while R1 requires 25-30 minutes.
        """
        script_path = PIPELINE_DIR / "assemble_ep01_feature.py"
        content = script_path.read_text(encoding="utf-8")

        has_stale_duration = "7m30s" in content or "10min" in content
        passed = not has_stale_duration
        msg = "assemble_ep01_feature.py contains stale duration labels ('7m30s', '10min') contradicting 25-30m EP01 specification" if not passed else "Duration labels consistent"
        self._record_result("test_f13_06_duration_specification_consistency", passed, msg, "GAP: Stale duration labels in script (7m30s/10min vs 25-30m)")
        self.assertFalse(has_stale_duration, "assemble_ep01_feature.py contains stale duration labels (7m30s / 10min) that contradict 25-30m EP01 specification")

    # =========================================================================
    # SECTION 2: AUDIO CONTINUITY ENGINE RIGOROUS EMPIRICAL STRESS (F8, F9, F10)
    # =========================================================================

    def _generate_test_video(self, output_path: str, duration: float = 2.0, has_audio: bool = True, freq: int = 440, vol: float = 0.5):
        """Helper to create minimal valid mp4 test fixtures."""
        cmd = [
            self.ffmpeg, "-y",
            "-f", "lavfi", "-i", f"color=c=black:s=160x120:d={duration}"
        ]
        if has_audio:
            cmd.extend([
                "-f", "lavfi", "-i", f"sine=frequency={freq}:duration={duration}",
                "-c:a", "aac", "-b:a", "128k", "-ar", "48000",
                "-af", f"volume={vol}"
            ])
        else:
            cmd.extend(["-an"])
        cmd.extend([
            "-c:v", "libx264", "-pix_fmt", "yuv420p", "-t", str(duration),
            output_path
        ])
        res = subprocess.run(cmd, capture_output=True, text=True)
        self.assertEqual(res.returncode, 0, f"FFmpeg failed creating test fixture: {res.stderr}")

    def _generate_test_audio(self, output_path: str, duration: float = 3.0, freq: int = 1000, vol: float = 0.3):
        """Helper to create minimal valid audio wav/m4a test fixtures."""
        cmd = [
            self.ffmpeg, "-y",
            "-f", "lavfi", "-i", f"sine=frequency={freq}:duration={duration}",
            "-af", f"volume={vol}",
            "-c:a", "aac", "-b:a", "128k", "-ar", "48000",
            output_path
        ]
        res = subprocess.run(cmd, capture_output=True, text=True)
        self.assertEqual(res.returncode, 0, f"FFmpeg failed creating audio fixture: {res.stderr}")

    def test_f08_mute_fallback_and_stream_preconditioning(self):
        """
        [ADVERSARIAL CHALLENGE - F8]
        Stress test stitch_with_audio_crossfade when input contains video WITHOUT audio stream (-an).
        Engine must synthesize aevalsrc=0 stereo silence and produce a valid 48kHz AAC master
        without FFmpeg crash.
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            td = Path(tmpdir)
            vid1 = str(td / "silent_shot.mp4")
            vid2 = str(td / "audio_shot.mp4")
            out_mode_a = str(td / "out_mode_a.mp4")
            out_mode_b = str(td / "out_mode_b.mp4")

            self._generate_test_video(vid1, duration=2.0, has_audio=False)
            self._generate_test_video(vid2, duration=2.0, has_audio=True, freq=440)

            # Test Mode A (boundary smoothing / micro crossfade)
            ok_a = self.engine.stitch_with_audio_crossfade(
                [vid1, vid2], out_mode_a, mode="boundary_smoothing", normalize_lufs=False
            )
            self.assertTrue(ok_a, "stitch_with_audio_crossfade Mode A must succeed with mute video")
            self.assertTrue(os.path.exists(out_mode_a))

            # Verify output has valid audio stream at 48000 Hz
            rep_a = self.engine.inspect_shot_audio(out_mode_a)
            self.assertTrue(rep_a["has_audio"], "Mode A output must have audio track despite silent input")
            self.assertEqual(rep_a["sample_rate"], 48000)

            # Test Mode B (acrossfade chaining with apad)
            ok_b = self.engine.stitch_with_audio_crossfade(
                [vid1, vid2], out_mode_b, crossfade_dur=0.5, mode="acrossfade", normalize_lufs=False
            )
            self.assertTrue(ok_b, "stitch_with_audio_crossfade Mode B must succeed with mute video")
            self.assertTrue(os.path.exists(out_mode_b))
            rep_b = self.engine.inspect_shot_audio(out_mode_b)
            self.assertTrue(rep_b["has_audio"])
            self.assertEqual(rep_b["sample_rate"], 48000)

            self._record_result("test_f08_mute_fallback_and_stream_preconditioning", True, "Both Mode A and Mode B synthesize stereo silence for mute videos")

    def test_f08_gain_staging_headroom_clipping_protection(self):
        """
        [ADVERSARIAL CHALLENGE - F8]
        Empirical test of active RMS gain staging:
        When audio has high peak (e.g. 0.0 dBTP), headroom protection must cap gain
        to ensure max_volume_db + gain_adj <= -1.0 dBTP.
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            td = Path(tmpdir)
            loud_vid = str(td / "loud_shot.mp4")
            out_vid = str(td / "staged_out.mp4")

            # Create video with 0 dBFS peak sine wave
            self._generate_test_video(loud_vid, duration=2.0, has_audio=True, freq=1000, vol=1.0)
            rep_before = self.engine.inspect_shot_audio(loud_vid)

            ok = self.engine.stitch_with_audio_crossfade(
                [loud_vid], out_vid, active_gain_staging=True, normalize_lufs=False
            )
            self.assertTrue(ok)
            rep_after = self.engine.inspect_shot_audio(out_vid)

            # Max volume must be clamped below -1.0 dBFS
            self.assertLessEqual(rep_after["max_volume_db"], -0.9, f"Headroom guard must prevent clipping (got {rep_after['max_volume_db']} dB)")
            self._record_result("test_f08_gain_staging_headroom_clipping_protection", True, "Headroom guard prevents audio clipping")

    def test_f09_four_stem_mixing_with_optional_stems(self):
        """
        [ADVERSARIAL CHALLENGE - F9]
        Verify mix_four_stems operates correctly when optional stems (Stem 3 Foley, Stem 4 Dialogue)
        are omitted, and only Stem 1 BGM and Stem 2 Ambience are provided.
        Must succeed, apply notch filter, and normalize to -14 LUFS.
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            td = Path(tmpdir)
            vid = str(td / "scene_shot.mp4")
            bgm = str(td / "bgm.m4a")
            amb = str(td / "ambience.m4a")
            out = str(td / "mixed_master.mp4")

            self._generate_test_video(vid, duration=3.0, has_audio=True, freq=300, vol=0.5)
            self._generate_test_audio(bgm, duration=4.0, freq=500, vol=0.3)
            self._generate_test_audio(amb, duration=4.0, freq=120, vol=0.2)

            ok = self.engine.mix_four_stems(
                video_path=vid,
                output_path=out,
                stem1_bgm=bgm,
                stem2_ambience=amb,
                stem3_foley=None,
                stem4_dialogue=None,
                target_lufs=-14.0,
                two_pass=False
            )
            self.assertTrue(ok, "mix_four_stems must succeed with only BGM and Ambience stems")
            self.assertTrue(os.path.exists(out))
            rep = self.engine.inspect_shot_audio(out)
            self.assertTrue(rep["has_audio"])
            self.assertEqual(rep["channels"], 2)
            self.assertEqual(rep["sample_rate"], 48000)

            self._record_result("test_f09_four_stem_mixing_with_optional_stems", True, "mix_four_stems gracefully mixes partial stems without error")

    def test_f10_two_pass_loudnorm_measurement_silence_guard(self):
        """
        [ADVERSARIAL CHALLENGE - F10]
        Verify measure_loudness detects pure silence (-inf) and sets is_silent=True.
        Verify normalize_loudness with two_pass=True safely falls back on silent audio
        without crashing FFmpeg or raising JSON decode errors.
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            td = Path(tmpdir)
            silent_vid = str(td / "pure_silence.mp4")
            out_vid = str(td / "norm_silent.mp4")

            # Generate video with zero-amplitude audio
            cmd = [
                self.ffmpeg, "-y",
                "-f", "lavfi", "-i", "color=c=black:s=160x120:d=2.0",
                "-f", "lavfi", "-i", "aevalsrc=0:d=2.0:s=48000:c=stereo",
                "-c:v", "libx264", "-c:a", "aac", "-b:a", "128k", "-shortest",
                silent_vid
            ]
            subprocess.run(cmd, capture_output=True, text=True)

            meas = self.engine.measure_loudness(silent_vid)
            self.assertIsNotNone(meas, "measure_loudness must return dict on silent file")
            self.assertTrue(meas.get("is_silent"), "is_silent must be True for pure silence")

            ok = self.engine.normalize_loudness(silent_vid, out_vid, target_lufs=-14.0, two_pass=True)
            passed = ok and os.path.exists(out_vid)
            msg = "normalize_loudness on pure silence triggers FFmpeg AAC Qavg:nan crash in single-pass fallback" if not passed else "Silence guard successfully intercepts -inf without crash"
            self._record_result("test_f10_two_pass_loudnorm_measurement_silence_guard", passed, msg, "DEFECT: Pure silence crashes FFmpeg loudnorm single-pass fallback")
            self.assertTrue(passed, msg)

    # =========================================================================
    # SECTION 3: WEB REVIEW STUDIO RIGOROUS TESTS (FEATURE F11)
    # =========================================================================

    def test_f11_01_default_host_and_port_constants(self):
        """
        [ADVERSARIAL CHALLENGE - F11]
        Verify web_review/server.py default host is 0.0.0.0 and port is 1515.
        """
        self.assertEqual(srv.DEFAULT_HOST, "0.0.0.0", "DEFAULT_HOST must be 0.0.0.0 for Tailscale remote access")
        self.assertEqual(srv.DEFAULT_PORT, 1515, "DEFAULT_PORT must be 1515 per PROJECT.md and AGENTS.md")
        self._record_result("test_f11_01_default_host_and_port_constants", True, "Host 0.0.0.0 and port 1515 correctly configured")

    def test_f11_02_matrix_ep01_full_188_shots_and_15_scenes(self):
        """
        [ADVERSARIAL CHALLENGE - F11]
        Verify /api/matrix/ep01 returns all 188 shots across all 15 scenes.
        Explicitly check Scene 15 (id='ep01_scene15') has exactly 6 shots.
        """
        resp = self.client.get("/api/matrix/ep01")
        self.assertEqual(resp.status_code, 200)
        data = resp.json()

        self.assertEqual(data.get("total_shots"), 188)
        scenes = data.get("scenes", [])
        self.assertEqual(len(scenes), 15)

        # Check scene 15 specifically
        sc15 = next((s for s in scenes if s.get("scene_id") == "ep01_scene15"), None)
        self.assertIsNotNone(sc15, "ep01_scene15 must be in /api/matrix/ep01")
        self.assertEqual(sc15.get("shots_total"), 6)
        self.assertEqual(len(sc15.get("shots", [])), 6)

        self._record_result("test_f11_02_matrix_ep01_full_188_shots_and_15_scenes", True, "Matrix EP01 correctly serves all 188 shots across 15 scenes")

    def test_f11_03_concurrency_409_state_guard(self):
        """
        [ADVERSARIAL CHALLENGE - F11]
        Verify HTTP 409 Conflict rejection when state machine is busy:
        1. When status == 'generating', both /api/generate and /api/concat return 409.
        2. When status == 'concatenating', both /api/generate and /api/concat return 409.
        """
        original_status = srv.gen_state["status"]
        try:
            # Test 'generating' lock
            srv.gen_state["status"] = "generating"
            r_gen = self.client.post("/api/generate", json={"prompt": "test prompt"})
            self.assertEqual(r_gen.status_code, 409)
            self.assertEqual(r_gen.json().get("status"), "busy")

            r_con = self.client.post("/api/concat", json={"scene_id": "ep01_scene01"})
            self.assertEqual(r_con.status_code, 409)
            self.assertEqual(r_con.json().get("status"), "busy")

            # Test 'concatenating' lock
            srv.gen_state["status"] = "concatenating"
            r_gen2 = self.client.post("/api/generate", json={"prompt": "test prompt 2"})
            self.assertEqual(r_gen2.status_code, 409)

            r_con2 = self.client.post("/api/concat", json={"scene_id": "ep01_scene02"})
            self.assertEqual(r_con2.status_code, 409)

            self._record_result("test_f11_03_concurrency_409_state_guard", True, "Concurrency 409 guard active on both endpoints")
        finally:
            srv.gen_state["status"] = original_status

    def test_f11_04_api_validation_rigor(self):
        """
        [ADVERSARIAL CHALLENGE - F11]
        Verify schema validation on endpoints:
        - POST /api/generate without prompt or shot_id -> HTTP 422
        - POST /api/concat with empty scene_id -> HTTP 422
        """
        r_gen = self.client.post("/api/generate", json={})
        self.assertEqual(r_gen.status_code, 422, "POST /api/generate without prompt or shot_id must return 422")

        r_con = self.client.post("/api/concat", json={"scene_id": "   "})
        self.assertEqual(r_con.status_code, 422, "POST /api/concat with whitespace scene_id must return 422")

        self._record_result("test_f11_04_api_validation_rigor", True, "Endpoint input validation enforces required parameters")

    def test_f11_05_logging_utf8_encoding_safety(self):
        """
        [ADVERSARIAL CHALLENGE - F11]
        Verify log_msg function does not crash on complex Vietnamese diacritics
        even if stdout has encoding limitations.
        """
        complex_vietnamese = "🎬 Thập Ngũ Niên: Đoạn Trường Ký — Thúy Kiều ngấn lệ bên nấm mồ sè sè ngọn cỏ dầu dầu Đạm Tiên"
        try:
            srv.log_msg(complex_vietnamese)
            self.assertIn(complex_vietnamese, srv.gen_state["logs"][-1])
            self._record_result("test_f11_05_logging_utf8_encoding_safety", True, "log_msg successfully buffers Vietnamese diacritics")
        except Exception as e:
            self.fail(f"log_msg crashed on Vietnamese diacritics: {e}")

    # =========================================================================
    # SECTION 4: PIPELINE AUTOMATION & ORCHESTRATOR RIGOROUS TESTS (FEATURE F12)
    # =========================================================================

    def test_f12_01_all_15_scenes_shots_retrievable(self):
        """
        [ADVERSARIAL CHALLENGE - F12]
        Verify production_orchestrator.get_shots_for_scene retrieves shots for all 15 scenes.
        Verify Scene 15 (ep01_scene15) returns exactly 6 shots.
        """
        for i in range(1, 16):
            sc_id = f"ep01_scene{i:02d}"
            shots = orch.get_shots_for_scene(sc_id)
            self.assertGreater(len(shots), 0, f"No shots retrieved for {sc_id}")
            if i == 15:
                self.assertEqual(len(shots), 6, f"{sc_id} must have exactly 6 shots")
        self._record_result("test_f12_01_all_15_scenes_shots_retrievable", True, "All 15 scenes have indexed shots in orchestrator")

    def test_f12_02_resolve_versioned_path_auto_increment(self):
        """
        [ADVERSARIAL CHALLENGE - F12]
        Verify production_orchestrator.resolve_versioned_path:
        - When no version exists -> returns stem_v1.mp4
        - When stem_v1.mp4 exists -> returns stem_v2.mp4
        - When stem_v2.mp4 exists -> returns stem_v3.mp4
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            td = Path(tmpdir)
            p1 = orch.resolve_versioned_path(td, "test_scene_master")
            self.assertEqual(p1.name, "test_scene_master_v1.mp4")
            p1.touch()

            p2 = orch.resolve_versioned_path(td, "test_scene_master")
            self.assertEqual(p2.name, "test_scene_master_v2.mp4")
            p2.touch()

            p3 = orch.resolve_versioned_path(td, "test_scene_master")
            self.assertEqual(p3.name, "test_scene_master_v3.mp4")

            self._record_result("test_f12_02_resolve_versioned_path_auto_increment", True, "Versioned path generator correctly increments _v1, _v2, _v3")

    def test_f12_03_find_rendered_video_version_preference(self):
        """
        [ADVERSARIAL CHALLENGE - F12]
        Verify find_rendered_video prioritizes the highest version number (_v2 > _v1 > unversioned).
        """
        with tempfile.TemporaryDirectory() as tmpdir:
            orig_videos_dir = orch.VIDEOS_DIR
            try:
                orch.VIDEOS_DIR = Path(tmpdir)
                shot_v1 = orch.VIDEOS_DIR / "ep01_test_shot01_10s_v1.mp4"
                shot_v2 = orch.VIDEOS_DIR / "ep01_test_shot01_10s_v2.mp4"

                # Create files with size > 1000 bytes
                shot_v1.write_bytes(b"0" * 2000)
                time.sleep(0.01)
                shot_v2.write_bytes(b"0" * 2000)

                found = orch.find_rendered_video("ep01_test_shot01")
                self.assertIsNotNone(found)
                self.assertEqual(found.name, "ep01_test_shot01_10s_v2.mp4", "find_rendered_video must choose _v2 over _v1")
                self._record_result("test_f12_03_find_rendered_video_version_preference", True, "find_rendered_video prioritizes highest version tag")
            finally:
                orch.VIDEOS_DIR = orig_videos_dir

    def test_f12_04_concat_scene_shots_missing_shot_safe_abort(self):
        """
        [ADVERSARIAL CHALLENGE - F12]
        Verify concat_scene_shots aborts safely and returns None when shots are missing,
        without throwing unhandled exceptions.
        """
        result = orch.concat_scene_shots("ep01_scene15")
        # Since scene 15 shots are not rendered yet in working tree, it must return None
        self.assertIsNone(result, "concat_scene_shots must return None when shots are missing")
        self._record_result("test_f12_04_concat_scene_shots_missing_shot_safe_abort", True, "concat_scene_shots returns None gracefully on missing shots")

    def test_f12_05_no_opencv_concat_in_any_pipeline_script(self):
        """
        [ADVERSARIAL CHALLENGE - F12]
        Scan all pipeline scripts in 05_Production_Pipeline to verify zero use
        of cv2.VideoWriter for concatenation (which destroys audio streams).
        """
        forbidden_pattern = "cv2.VideoWriter"
        violating_scripts = []
        for py_file in PIPELINE_DIR.glob("*.py"):
            text = py_file.read_text(encoding="utf-8")
            if forbidden_pattern in text and "concat" in py_file.name.lower():
                violating_scripts.append(py_file.name)
        self.assertEqual(len(violating_scripts), 0, f"Found cv2.VideoWriter in concat scripts: {violating_scripts}")
        self._record_result("test_f12_05_no_opencv_concat_in_any_pipeline_script", True, "Zero OpenCV VideoWriter violations in production pipeline")


if __name__ == "__main__":
    unittest.main()
