"""Area #11 v1 W1 — LabeledRefPayload factory fail-fast tests.

11 unit tests:
- 9 fail-fast paths:
  - 4 non-list isinstance (labeled_refs / ref_roles / ref_role_metadata / attached_meta)
  - 3 length mismatch (ref_roles / ref_role_metadata / attached_meta)
  - 1 invalid enum value
  - 1 non-dict metadata entry
- 1 happy path
- 1 REF_ROLE_VALUES count + content check

Pattern donor: Area #5 W2 test_ref_contract_validator_sidecar.py.

spec: docs/superpowers/specs/2026-05-18-area-11-classify-label-substring-replacement-design.md §3.1
plan: docs/superpowers/plans/2026-05-18-area-11-classify-label-substring-replacement-implementation.md Task 1.1
"""
import pytest
from app.services.prompt_service import (
    LabeledRefPayload,
    make_labeled_ref_payload,
    RefRoleError,
    REF_ROLE_VALUES,
)


def _ok_inputs():
    """1-element valid payload — baseline for fail-fast tests."""
    return {
        "labeled_refs": [("character C01O01", b"\x89PNG")],
        "ref_roles": ["character_ref"],
        "ref_role_metadata": [{"sid": "C01"}],
        "attached_meta": [("character", "C01")],
    }


def test_factory_happy_path_returns_payload():
    """Happy path — valid 1-element payload returns LabeledRefPayload instance."""
    payload = make_labeled_ref_payload(**_ok_inputs())
    assert isinstance(payload, LabeledRefPayload)
    assert payload.ref_roles == ["character_ref"]
    assert payload.labeled_refs == [("character C01O01", b"\x89PNG")]
    assert payload.ref_role_metadata == [{"sid": "C01"}]
    assert payload.attached_meta == [("character", "C01")]


def test_factory_non_list_labeled_refs_raises():
    """isinstance gate (1/4) — labeled_refs non-list raises."""
    inputs = _ok_inputs()
    inputs["labeled_refs"] = "not-list"
    with pytest.raises(RefRoleError, match="labeled_refs is str"):
        make_labeled_ref_payload(**inputs)


def test_factory_non_list_ref_roles_raises():
    """isinstance gate (2/4) — ref_roles non-list raises."""
    inputs = _ok_inputs()
    inputs["ref_roles"] = {"character_ref": True}
    with pytest.raises(RefRoleError, match="ref_roles is dict"):
        make_labeled_ref_payload(**inputs)


def test_factory_non_list_ref_role_metadata_raises():
    """isinstance gate (3/4) — ref_role_metadata non-list raises."""
    inputs = _ok_inputs()
    inputs["ref_role_metadata"] = None
    with pytest.raises(RefRoleError, match="ref_role_metadata is NoneType"):
        make_labeled_ref_payload(**inputs)


def test_factory_non_list_attached_meta_raises():
    """isinstance gate (4/4) — attached_meta non-list raises."""
    inputs = _ok_inputs()
    inputs["attached_meta"] = 0
    with pytest.raises(RefRoleError, match="attached_meta is int"):
        make_labeled_ref_payload(**inputs)


def test_factory_ref_roles_length_mismatch_raises():
    """length mismatch (1/3) — ref_roles length != labeled_refs length raises."""
    inputs = _ok_inputs()
    inputs["ref_roles"] = ["character_ref", "prop_ref"]  # 2 vs 1
    with pytest.raises(RefRoleError, match="ref_roles length 2 != labeled_refs length 1"):
        make_labeled_ref_payload(**inputs)


def test_factory_ref_role_metadata_length_mismatch_raises():
    """length mismatch (2/3) — ref_role_metadata length != labeled_refs length raises."""
    inputs = _ok_inputs()
    inputs["ref_role_metadata"] = []  # 0 vs 1
    with pytest.raises(RefRoleError, match="ref_role_metadata length 0 != labeled_refs length 1"):
        make_labeled_ref_payload(**inputs)


def test_factory_attached_meta_length_mismatch_raises():
    """length mismatch (3/3) — attached_meta length != labeled_refs length raises."""
    inputs = _ok_inputs()
    inputs["attached_meta"] = [("character", "C01"), ("prop", "P01")]  # 2 vs 1
    with pytest.raises(RefRoleError, match="attached_meta length 2 != labeled_refs length 1"):
        make_labeled_ref_payload(**inputs)


def test_factory_invalid_enum_value_raises():
    """enum value (1/1) — invalid ref_roles entry raises."""
    inputs = _ok_inputs()
    inputs["ref_roles"] = ["INVALID_ROLE"]
    with pytest.raises(RefRoleError, match=r"ref_roles\[0\]='INVALID_ROLE' not in REF_ROLE_VALUES"):
        make_labeled_ref_payload(**inputs)


def test_factory_non_dict_metadata_entry_raises():
    """non-dict metadata (1/1) — ref_role_metadata entry not dict raises."""
    inputs = _ok_inputs()
    inputs["ref_role_metadata"] = [["sid", "C01"]]  # list not dict
    with pytest.raises(RefRoleError, match=r"ref_role_metadata\[0\] is list, not dict"):
        make_labeled_ref_payload(**inputs)


def test_ref_role_values_has_11_entries():
    """spec §3.1 — baseline 11 + composition_guide (W21B-W8, 2026-06-13)
    + immobilized_pose_guide (P8 I-1, 2026-06-28)
    + indoor_pose_guide (A2 실내 가이드 배선, 736027c8 2026-07-02)
    + outdoor_canon_photo_ref/outdoor_canon_map_ref (W22 야외 직행, 2026-07-10)
    + grounding_part_detail_ref/grounding_context_detail_ref
      (GROUNDING-V2 D, 2026-08-31 — `location_part` 고증 참조 묶음.
       ★기존 `background` **슬롯**을 그대로 쓰고 **역할만** 더했다.
       선언 자체는 비회귀다 — 기존 producer 가 이 값을 안 낸다)."""
    assert len(REF_ROLE_VALUES) == 18
    expected = {
        "outfit_ref_explicit",
        "previous_shot_same_frame_zoomed",
        "previous_shot_same_room",
        "previous_shot_continuity",
        "background_chain_ref",
        "character_ref",
        "character_state_ref",
        "outfit_ref_inline",
        "prop_ref",
        "background_general",
        "fallback",
        "composition_guide",
        "immobilized_pose_guide",
        "indoor_pose_guide",
        "grounding_part_detail_ref",
        "grounding_context_detail_ref",
        "outdoor_canon_photo_ref",
        "outdoor_canon_map_ref",
    }
    assert set(REF_ROLE_VALUES) == expected
