from __future__ import annotations

import json
from pathlib import Path

import pytest

from recoil.core.paths import ProjectPaths
from recoil.pipeline._lib import board_builder as bb
from recoil.pipeline._lib.prompt_engine import build_storyboard_strip_prompt


@pytest.fixture()
def project_paths(tmp_path: Path) -> ProjectPaths:
    paths = ProjectPaths(project_root=tmp_path / "fixture_project")
    paths.project_root.mkdir(parents=True)
    return paths


def _write_file(path: Path) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(b"fake-image")
    return path


def _write_prop_registry(paths: ProjectPaths, slugs: list[str]) -> None:
    registry = paths.assets_dir / "prop" / "prop.json"
    registry.parent.mkdir(parents=True, exist_ok=True)
    registry.write_text(
        json.dumps({slug: {"looks": ["base"]} for slug in slugs}),
        encoding="utf-8",
    )


def _write_prop_identity(paths: ProjectPaths, slug: str, filename: str = "a.png") -> Path:
    return _write_file(paths.pool_dir("prop", slug, "identity", look="base") / filename)


def _write_sublocation_registry(paths: ProjectPaths, location_id: str, slugs: list[str]) -> None:
    base = paths.asset_look_dir("loc", location_id, "base")
    sublocations = {}
    for slug in slugs:
        ref_rel = f"sublocations/sublocation_{slug}_v01.png"
        _write_file(base / ref_rel)
        sublocations[slug] = {"ref": ref_rel}
    base.mkdir(parents=True, exist_ok=True)
    (base / "location.json").write_text(
        json.dumps({"sublocations": sublocations}),
        encoding="utf-8",
    )


class _Primitive:
    location_id = "int_lab"


def test_prop_named_in_segment_attaches_identity_and_sidecar_entry(project_paths: ProjectPaths) -> None:
    _write_prop_registry(project_paths, ["cryo_pod"])
    first = _write_prop_identity(project_paths, "cryo_pod", "a.png")
    _write_prop_identity(project_paths, "cryo_pod", "b.png")

    refs: list[str] = []
    ref_layout: dict = {}
    sidecar_paths = bb._append_prop_refs(
        project_paths,
        [{"prompt": "The cryo-pod seal vents frost."}],
        refs,
        ref_layout,
    )

    assert refs == [str(first)]
    assert ref_layout["prop_refs"] == {"cryo_pod": 1}
    assert sidecar_paths == ["assets/prop/cryo_pod/base/pool/identity/a.png"]

    class _Beat:
        beat_metadata = {"batch_summary": {"shared_characters": []}}

    class _NoLocation:
        location_id = None

    _refs, _layout, _identity_refs, sidecar = bb._collect_board_refs(
        project_paths,
        _Beat(),
        _NoLocation(),
        [{"prompt": "The cryo-pod seal vents frost."}],
    )
    assert sidecar["prop_refs"] == [
        {
            "slug": "cryo_pod",
            "ref": "assets/prop/cryo_pod/base/pool/identity/a.png",
        }
    ]


def test_prop_without_identity_is_skipped_with_info_log(
    project_paths: ProjectPaths,
    caplog: pytest.LogCaptureFixture,
) -> None:
    _write_prop_registry(project_paths, ["cryo_pod"])

    refs: list[str] = []
    ref_layout: dict = {}
    with caplog.at_level("INFO"):
        sidecar_paths = bb._append_prop_refs(
            project_paths,
            [{"setting": "Jade circles the cryo pod."}],
            refs,
            ref_layout,
        )

    assert refs == []
    assert ref_layout == {}
    assert sidecar_paths == []
    assert "prop_ref_skipped slug=cryo_pod reason=no_identity_ref" in caplog.text


def test_prop_refs_honor_first_seen_order_and_cap_two(project_paths: ProjectPaths) -> None:
    _write_prop_registry(project_paths, ["salvage_hook", "warden_drone", "debt_counter"])
    debt = _write_prop_identity(project_paths, "debt_counter")
    drone = _write_prop_identity(project_paths, "warden_drone")
    _write_prop_identity(project_paths, "salvage_hook")

    refs: list[str] = []
    ref_layout: dict = {}
    sidecar_paths = bb._append_prop_refs(
        project_paths,
        [{"intent": "The debt counter flashes as a warden drone scans the salvage hook."}],
        refs,
        ref_layout,
    )

    assert refs == [str(debt), str(drone)]
    assert ref_layout["prop_refs"] == {"debt_counter": 1, "warden_drone": 2}
    assert sidecar_paths == [
        "assets/prop/debt_counter/base/pool/identity/a.png",
        "assets/prop/warden_drone/base/pool/identity/a.png",
    ]


def test_mixed_sublocations_attach_all_distinct_cap_three_and_record_sidecar(
    project_paths: ProjectPaths,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(bb, "validate_ref_file", lambda path: None)
    _write_sublocation_registry(
        project_paths,
        "int_lab",
        ["anchor_cables", "pod_platform", "debt_console", "outer_lock"],
    )

    refs: list[str] = []
    ref_layout: dict = {}
    sidecar = bb._append_sublocation_ref(
        project_paths,
        _Primitive(),
        [
            {"sublocation": "anchor_cables"},
            {"sublocation": "pod_platform"},
            {"sublocation": "anchor_cables"},
            {"sublocation": "debt_console"},
            {"sublocation": "outer_lock"},
        ],
        refs,
        ref_layout,
    )

    assert [Path(ref).name for ref in refs] == [
        "sublocation_anchor_cables_v01.png",
        "sublocation_pod_platform_v01.png",
        "sublocation_debt_console_v01.png",
    ]
    assert ref_layout["sublocation_locked"] is False
    assert [entry["slug"] for entry in ref_layout["sublocation_refs"]] == [
        "anchor_cables",
        "pod_platform",
        "debt_console",
    ]
    assert sidecar["shared_sublocation"] is None
    assert sidecar["shared_ref"] is None
    assert sidecar["sublocation_refs"] == [
        {
            "slug": "anchor_cables",
            "ref": "assets/loc/int_lab/base/sublocations/sublocation_anchor_cables_v01.png",
        },
        {
            "slug": "pod_platform",
            "ref": "assets/loc/int_lab/base/sublocations/sublocation_pod_platform_v01.png",
        },
        {
            "slug": "debt_console",
            "ref": "assets/loc/int_lab/base/sublocations/sublocation_debt_console_v01.png",
        },
    ]


def test_all_same_sublocation_locks_and_keeps_legacy_shared_ref(
    project_paths: ProjectPaths,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(bb, "validate_ref_file", lambda path: None)
    _write_sublocation_registry(project_paths, "int_lab", ["pod_platform"])

    refs: list[str] = []
    ref_layout: dict = {}
    sidecar = bb._append_sublocation_ref(
        project_paths,
        _Primitive(),
        [{"sublocation": "pod_platform"}, {"sublocation": "pod_platform"}],
        refs,
        ref_layout,
    )

    assert [Path(ref).name for ref in refs] == ["sublocation_pod_platform_v01.png"]
    assert ref_layout["sublocation_locked"] is True
    assert ref_layout["sublocation_ref"] == 1
    assert sidecar["shared_sublocation"] == "pod_platform"
    assert sidecar["shared_ref"] == "sublocations/sublocation_pod_platform_v01.png"
    assert sidecar["sublocation_refs"] == [
        {
            "slug": "pod_platform",
            "ref": "assets/loc/int_lab/base/sublocations/sublocation_pod_platform_v01.png",
        }
    ]


def test_empty_prop_refs_and_legacy_single_sublocation_prompt_is_byte_identical() -> None:
    expected = (
        "Create a storyboard as ONE single image: a grid of 2 columns x 2 rows, "
        "panels numbered 1-4 reading left to right, top to bottom. Each panel is "
        "a 9:16 vertical film frame. Draw only the first 1 panel(s); leave the "
        "trailing 3 cells completely blank — white paper.\n\n"
        f"STYLE:\n{bb.get_builder('gpt-image-2', 'storyboard').__globals__['STORYBOARD_STYLE_LOCK']}\n\n"
        "REFERENCE MAPPING:\n"
        "Attached sublocation image is the establishing geography reference. "
        "EVERY panel happens here. Keep this exact geography identical and "
        "recognizable in every panel. Never relocate them.\n\n"
        "AUTHORING:\n"
        f"{bb.get_builder('gpt-image-2', 'storyboard').__globals__['STORYBOARD_DIRECTOR_PREAMBLE']}\n"
        "Draw EXACTLY 1 panels, one per numbered beat below, in order. "
        "No invented panels, no filler.\n\n"
        "STORY BEATS:\n"
        "1.\n"
        "Jade checks the seal"
    )

    prompt = build_storyboard_strip_prompt(
        [{"setting": "Pod platform", "intent": "Jade checks the seal"}],
        4,
        {},
        {"sublocation_ref": 1, "sublocation_locked": True, "sublocation_panels": [1]},
        True,
        grid=(2, 2),
    )

    assert prompt == expected
