"""REC-213 C1+C2 — core tests: sheet existence/validity resolver, per-kind layout,
bundle sheet projection, and the fail-closed gate's orthogonality to sheets.

CORE-only: imports recoil.core.* exclusively (the live-path video parity test that
exercises recoil.pipeline._lib.dispatch_payload lives in
recoil/pipeline/_lib/tests/test_ref_c1c2_video_parity.py)."""
import os
import pathlib

import pytest
from PIL import Image

from recoil.core.paths import ProjectPaths
from recoil.core.ref_resolver import resolve_sheet_asset
from recoil.core.ref_errors import SheetIntegrityError, MissingRequiredRefError
from recoil.core.ref_gate import assert_refs_complete
from recoil.core.ref_stem import subject_id_norm
from recoil.core.ref_types import RefAsset, ReferenceBundle


def _real_png(p: pathlib.Path, size=(512, 512)) -> None:
    """A valid sheet: PNG magic, >=512x512, and (via noise) >= the 100KB floor."""
    p.parent.mkdir(parents=True, exist_ok=True)
    Image.frombytes("RGB", size, os.urandom(size[0] * size[1] * 3)).save(p, "PNG")


def test_resolve_sheet_asset_all_states(tmp_path):
    pp = ProjectPaths(project_root=tmp_path / "proj")
    # ABSENT -> None
    assert resolve_sheet_asset(pp, "loc", "corridor") is None

    sp = pp.sheet_path("loc", "corridor")
    # VALID -> sheet RefAsset
    _real_png(sp)
    assert sp.stat().st_size >= 100_000
    a = resolve_sheet_asset(pp, "loc", "corridor")
    assert a is not None and a.role == "sheet" and a.kind == "sheet" and a.source == "sheet"
    assert a.path == sp

    # CORRUPT reason 1 — too small
    sp.write_bytes(b"\x89PNG\r\n\x1a\n" + b"\x00" * 100)
    with pytest.raises(SheetIntegrityError):
        resolve_sheet_asset(pp, "loc", "corridor")
    # CORRUPT reason 2 — bad PNG magic, big enough
    sp.write_bytes(b"NOTPNGMAGIC" + os.urandom(200_000))
    with pytest.raises(SheetIntegrityError):
        resolve_sheet_asset(pp, "loc", "corridor")
    # CORRUPT reason 3 — valid PNG but < 512x512 (padded past the size floor)
    _real_png(sp, size=(64, 64))
    sp.write_bytes(sp.read_bytes() + os.urandom(120_000))
    with pytest.raises(SheetIntegrityError):
        resolve_sheet_asset(pp, "loc", "corridor")


def test_sheet_path_layout(tmp_path):
    pp = ProjectPaths(project_root=tmp_path / "proj")
    ch = str(pp.sheet_path("char", "JADE"))
    lo = str(pp.sheet_path("loc", "int_lower_decks_corridor"))
    pr = str(pp.sheet_path("prop", "Debt_Counter"))
    assert ch.endswith("/base/sheets/sheet_v1.png") and "/char/jade/" in ch
    assert lo.endswith("/sheets/sheet_v1.png") and "/loc/int_lower_decks_corridor/" in lo
    assert "/base/sheets/" not in lo  # loc is sheets/, NOT base/sheets/
    assert pr.endswith("/sheet_v1.png") and "/prop/debt_counter/" in pr


def test_bundle_canonical_ref_images():
    sheet = RefAsset(path=pathlib.Path("/x/sheet_v1.png"), role="sheet", subject="c", kind="sheet", source="sheet")
    ident = RefAsset(path=pathlib.Path("/x/hero.png"), role="identity", subject="c", kind="identity", is_hero=True)
    b = ReferenceBundle((ident, sheet))
    assert b.sheet() is sheet
    assert b.canonical_ref_images() == (sheet,)            # sheet REPLACES (cap-proof)
    assert b.canonical_ref_images(prefer_sheet=False) == (ident,)
    assert b.fell_back() is False

    nb = ReferenceBundle((ident,))
    assert nb.sheet() is None and nb.fell_back() is True
    assert nb.canonical_ref_images() == (ident,)


def test_gate_orthogonal_to_sheet():
    """CRITICAL 2 lock: a sheet (role='sheet') never satisfies the identity gate."""
    jade = subject_id_norm("JADE")
    sheet = RefAsset(path=pathlib.Path("/x/sheet_v1.png"), role="sheet", subject=jade, kind="sheet", source="sheet")
    hero = RefAsset(path=pathlib.Path("/x/hero.png"), role="identity", subject=jade, kind="identity", is_hero=True)

    # Sheet present but NO hero identity -> gate STILL raises (sheet is orthogonal).
    with pytest.raises(MissingRequiredRefError):
        assert_refs_complete(
            shot_id="S1", required_subjects=["JADE"],
            bundle=ReferenceBundle((sheet,)),
            board_gated=False, board_ref_path=None,
        )

    # Hero present (with or without a sheet) -> gate passes.
    for assets in ((hero,), (hero, sheet)):
        assert_refs_complete(
            shot_id="S1", required_subjects=["JADE"],
            bundle=ReferenceBundle(assets),
            board_gated=False, board_ref_path=None,
        )  # no raise
