"""REC-31: the dispatch audit must self-heal its synthetic fixture PNGs.

The fixture source PNGs under audit_fixtures/{refs,start_frames}/ are gitignored
(`*.png` evict rule), so a fresh clone has none. The audit generates them at
startup via `_ensure_synthetic_fixture_pngs`, keeping it portable. Without that,
`_install_v2_fixture_refs` installs nothing and ~75 paths fail
"reference_images empty" (97/172). These tests guard the self-heal.
"""
import json
import os
import re
from pathlib import Path

from recoil.pipeline.tools import audit_dispatch
from recoil.pipeline.tools.audit_dispatch import (
    _ensure_synthetic_fixture_pngs,
    _synthetic_png_1x1,
)

TOOLS_DIR = Path(__file__).resolve().parents[1]  # recoil/pipeline/tools
PLAN = TOOLS_DIR / "audit_fixtures" / "audit_plan.json"


def _referenced_pngs() -> list[str]:
    plan_text = PLAN.read_text()
    return sorted(set(re.findall(r"audit_fixtures/[A-Za-z0-9_]+/[A-Za-z0-9_]+\.png", plan_text)))


def test_synthetic_png_is_a_valid_png():
    png = _synthetic_png_1x1()
    assert png[:8] == b"\x89PNG\r\n\x1a\n"        # signature
    assert png[-8:] == b"IEND\xaeB`\x82"          # IEND chunk + CRC
    assert len(png) < 200                          # genuinely minimal


def test_ensure_recreates_missing_fixture_and_is_idempotent():
    plan = json.loads(PLAN.read_text())
    rels = _referenced_pngs()
    assert rels, "plan must reference fixture PNGs"

    # Make sure all exist, then remove ONE to simulate the fresh-clone gap.
    _ensure_synthetic_fixture_pngs(plan)
    target = TOOLS_DIR / rels[0]
    target.unlink(missing_ok=True)
    assert not target.exists()

    created = _ensure_synthetic_fixture_pngs(plan)
    assert created == 1, f"expected to recreate exactly the removed fixture, got {created}"
    assert target.exists()
    assert target.read_bytes()[:8] == b"\x89PNG\r\n\x1a\n"

    # Idempotent: a second call creates nothing.
    assert _ensure_synthetic_fixture_pngs(plan) == 0


def test_install_never_overwrites_a_real_asset(tmp_path, monkeypatch):
    """The audit must NEVER destroy a real project asset. If a real
    (non-fixture) file occupies a canonical ref name, _install_v2_fixture_refs
    leaves it untouched; it only fills empty slots with fixture symlinks.
    Guards the 2026-06-02 incident where `--project tartarus` unlinked real
    promoted refs and replaced them with 1x1 fixtures."""
    from recoil.core.paths import ProjectPaths

    _ensure_synthetic_fixture_pngs(json.loads(PLAN.read_text()))

    # Fake project rooted in tmp; point ProjectPaths.for_project at it.
    monkeypatch.setattr(
        ProjectPaths, "for_project",
        classmethod(lambda cls, project: ProjectPaths.from_root(tmp_path)),
    )

    jade_dir = ProjectPaths.from_root(tmp_path).asset_subject_dir("char", "jade")
    jade_dir.mkdir(parents=True, exist_ok=True)
    # Plant a REAL promoted ref where the installer would write jade hero.
    real = jade_dir / "jade_identity_hero_v01.png"
    real.write_bytes(b"\x89PNG\r\n\x1a\n" + b"REAL" * 64)  # not a 68-byte fixture
    real_bytes = real.read_bytes()

    audit_dispatch._install_v2_fixture_refs("fakeproj")

    # Real asset untouched: still a real file, same bytes, NOT a symlink.
    assert real.exists() and not real.is_symlink()
    assert real.read_bytes() == real_bytes
    # An empty slot (jade front) still got a fixture symlink installed.
    front = jade_dir / "jade_identity_front_v01.png"
    assert front.is_symlink() and "audit_fixtures" in os.path.realpath(front)


def test_build_payload_dry_run_forwards_grouping_and_configs(monkeypatch):
    captured = {}

    def _fake_build_dispatch_payload(**kwargs):
        captured.update(kwargs)
        grouping = kwargs["grouping"]
        return {
            "output_filename": f"EP001_COV_{grouping['ordinal']:03d}_SH01_02_take1.mp4",
            "grouping": dict(grouping),
            "generation_config": dict(kwargs["generation_config"]),
            "element_config": dict(kwargs["element_config"]),
        }

    import recoil.pipeline._lib.dispatch_payload as dispatch_payload

    monkeypatch.setattr(
        dispatch_payload,
        "build_dispatch_payload",
        _fake_build_dispatch_payload,
    )
    grouping = {
        "strategy": "coverage",
        "ordinal": 11,
        "shot_ids": ["EP001_SH01", "EP001_SH02"],
        "source_pass_id": "EP001_PASS_011_SH01_02_A_ENV",
    }
    generation_config = {"tier": "pro", "seed": 1234}
    element_config = {"identity_ref_mode": "full_turnaround"}

    payload = audit_dispatch.build_payload_dry_run(
        "r2v_multi",
        "seeddance-2.0",
        {
            "shots": [object(), object()],
            "grouping": grouping,
            "generation_config": generation_config,
            "element_config": element_config,
        },
        project="p",
        episode="ep_001",
    )

    assert captured["grouping"] == grouping
    assert captured["generation_config"] == generation_config
    assert captured["element_config"] == element_config
    assert payload["output_filename"] == "EP001_COV_011_SH01_02_take1.mp4"
