"""_build_single_scene_prompt_and_refs uses build_scene_attached_refs — Task 2.

spec: docs/superpowers/specs/2026-05-08-single-batch-reference-contract-design.md §4.1
plan: docs/superpowers/plans/2026-05-08-single-batch-reference-contract-implementation.md Task 2

5 가드 (사용자):
1. _build_single_scene_prompt_and_refs() 가 새 helper 호출
2. 단건 chain_bg / prev_shot_ref / state_variant / close skip 정책 적용 (helper 자동 처리)
3. helper 호출 시 background_chain_bg_map / state_variant / cached_* 등 9 fields 전달
4. S13 같은 face close-up 의 No reference images 결함은 Task 4 영역 — 본 task 미해결 OK
5. batch path 건드리지 말 것
"""
from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from app.services.scene_generation_coordinator import SceneGenerationCoordinator


@pytest.fixture
def coord() -> SceneGenerationCoordinator:
    """SceneGenerationCoordinator instance — minimal mock dependencies."""
    instance = SceneGenerationCoordinator.__new__(SceneGenerationCoordinator)
    instance._db = MagicMock()
    instance._project_id = "p_test"
    instance._actor_id = "u_test"
    instance._logger = MagicMock()
    instance._persistence_svc = MagicMock()
    instance._reference_svc = MagicMock()
    instance._validation_svc = MagicMock()
    instance._variation_svc = MagicMock()
    instance._provenance_svc = MagicMock()
    return instance


def _make_still():
    still = MagicMock()
    still.camera_json = "{}"
    still.scene_index = 8
    still.still_index = 0
    still.shot_index = 4
    return still


def test_single_path_calls_build_scene_attached_refs(coord, tmp_path):
    """가드 1: _build_single_scene_prompt_and_refs 가 helper 1회 호출."""
    still = _make_still()
    still_data = {
        "scene_index": 8,
        "shot_index": 4,
        "still_frame_prompt": "test",
        "beat_title": "",
        # D1 patch: caller-injected RPC (lookup skip 경로).
        "render_prompt_card": {"asset_requirements": {"required_refs": []}},
    }

    # context 가 build 되도록 helpers mock
    coord.build_single_still_context = MagicMock(return_value={
        "entity_lookup": {"c01": {"id": "c01", "short_id": "C01", "entity_type": "character"}},
        "ref_image_map": {"c01": b"face"},
        "scene_paths_by_index_by_id": {},
        "location_scene_history": {},
        "background_chain_bg_map": {"8_4": {"label": "BG", "image_bytes": b"bg"}},
        "dep_detail_map": {},
        "staging": {"camera_direction": "medium shot", "framing_scale": "medium"},
        "cached_style_context": "STYLE",
        "cached_entity_text_map": {"C01": "young woman"},
        "stills": [still],
    })

    with patch(
        "app.services.scene_generation_coordinator.build_scene_attached_refs",
        # P0: 5-tuple (full_prompt, labeled_refs, attached_meta, reference_phrase_kinds, payload)
        return_value=(
            "MOCK_FULL_PROMPT",
            [("character C01O02 in outfit", b"x")],
            [("character_outlook", "C01O02")],
            [],
            None,
        ),
    ) as mock_helper, patch(
        "app.services.scene_generation_coordinator.load_project_llm_config",
        return_value={},
    ):
        full_prompt, labeled_refs, _attached_meta, _payload = coord._build_single_scene_prompt_and_refs(
            still=still,
            episode_id="ep_test",
            still_data=still_data,
            visible_entities=[{"id": "c01", "short_id": "C01", "entity_type": "character"}],
            ref_image_map={"c01": b"face"},
        )

    assert mock_helper.call_count == 1, "build_scene_attached_refs must be called exactly once"
    assert full_prompt == "MOCK_FULL_PROMPT"
    assert labeled_refs == [("character C01O02 in outfit", b"x")]


def test_single_path_helper_call_includes_chain_bg_map(coord, tmp_path):
    """가드 3: helper 호출 시 background_chain_bg_map 가 빈 dict 가 아니라 context 의 값 전달."""
    still = _make_still()
    still_data = {
        "scene_index": 8, "shot_index": 4,
        "still_frame_prompt": "test", "beat_title": "",
        # D1 patch: caller-injected RPC (lookup skip 경로). Task 2 helper 호출
        # 검증이라 RPC 내용은 무관 — 빈 required_refs 로 validator pass.
        "render_prompt_card": {"asset_requirements": {"required_refs": []}},
    }
    expected_bg_map = {"8_4": {"label": "BACKGROUND chain reference", "image_bytes": b"bg"}}

    coord.build_single_still_context = MagicMock(return_value={
        "entity_lookup": {"c01": {"id": "c01", "short_id": "C01"}},
        "ref_image_map": {},
        "scene_paths_by_index_by_id": {},
        "location_scene_history": {},
        "background_chain_bg_map": expected_bg_map,
        "dep_detail_map": {},
        "staging": {"camera_direction": "medium shot", "framing_scale": "medium"},
        "cached_style_context": "",
        "cached_entity_text_map": {},
        "stills": [still],
    })

    with patch(
        "app.services.scene_generation_coordinator.build_scene_attached_refs",
        return_value=("MOCK", [], [], [], None),  # P0: 5-tuple
    ) as mock_helper, patch(
        "app.services.scene_generation_coordinator.load_project_llm_config",
        return_value={},
    ):
        coord._build_single_scene_prompt_and_refs(
            still=still, episode_id="ep_test", still_data=still_data,
            visible_entities=[], ref_image_map={},
        )

    call_kwargs = mock_helper.call_args.kwargs
    assert call_kwargs["background_chain_bg_map"] == expected_bg_map, \
        "helper must receive background_chain_bg_map from context"


def test_single_path_helper_call_includes_cached_style_and_text_map(coord, tmp_path):
    """가드 3 + v2 audit: helper 호출 시 cached_style_context + cached_entity_text_map 전달."""
    still = _make_still()
    still_data = {
        "scene_index": 8, "shot_index": 4,
        "still_frame_prompt": "test", "beat_title": "",
        # D1 patch: caller-injected RPC (lookup skip 경로). Task 2 helper 호출
        # 검증이라 RPC 내용은 무관 — 빈 required_refs 로 validator pass.
        "render_prompt_card": {"asset_requirements": {"required_refs": []}},
    }

    coord.build_single_still_context = MagicMock(return_value={
        "entity_lookup": {},
        "ref_image_map": {},
        "scene_paths_by_index_by_id": {},
        "location_scene_history": {},
        "background_chain_bg_map": {},
        "dep_detail_map": {},
        "staging": {"camera_direction": "medium shot", "framing_scale": "medium"},
        "cached_style_context": "PROVIDED_STYLE_CTX",
        "cached_entity_text_map": {"C01": "PROVIDED_TEXT"},
        "stills": [],
    })

    with patch(
        "app.services.scene_generation_coordinator.build_scene_attached_refs",
        return_value=("MOCK", [], [], [], None),  # P0: 5-tuple
    ) as mock_helper, patch(
        "app.services.scene_generation_coordinator.load_project_llm_config",
        return_value={},
    ):
        coord._build_single_scene_prompt_and_refs(
            still=still, episode_id="ep_test", still_data=still_data,
            visible_entities=[], ref_image_map={},
        )

    call_kwargs = mock_helper.call_args.kwargs
    assert call_kwargs["cached_style_context"] == "PROVIDED_STYLE_CTX"
    assert call_kwargs["cached_entity_text_map"] == {"C01": "PROVIDED_TEXT"}


def test_single_path_helper_call_includes_project_id(coord, tmp_path):
    """가드 3: helper 호출 시 project_id (별 인자) 전달."""
    still = _make_still()
    still_data = {
        "scene_index": 8, "shot_index": 4,
        "still_frame_prompt": "test", "beat_title": "",
        # D1 patch: caller-injected RPC (lookup skip 경로). Task 2 helper 호출
        # 검증이라 RPC 내용은 무관 — 빈 required_refs 로 validator pass.
        "render_prompt_card": {"asset_requirements": {"required_refs": []}},
    }

    coord.build_single_still_context = MagicMock(return_value={
        "entity_lookup": {}, "ref_image_map": {},
        "scene_paths_by_index_by_id": {}, "location_scene_history": {},
        "background_chain_bg_map": {}, "dep_detail_map": {},
            "staging": {"camera_direction": "medium shot", "framing_scale": "medium"},
            "cached_style_context": "",
        "cached_entity_text_map": {}, "stills": [],
    })

    with patch(
        "app.services.scene_generation_coordinator.build_scene_attached_refs",
        return_value=("MOCK", [], [], [], None),  # P0: 5-tuple
    ) as mock_helper, patch(
        "app.services.scene_generation_coordinator.load_project_llm_config",
        return_value={"llm_config": "x"},
    ):
        coord._build_single_scene_prompt_and_refs(
            still=still, episode_id="ep_test", still_data=still_data,
            visible_entities=[], ref_image_map={},
        )

    call_kwargs = mock_helper.call_args.kwargs
    assert call_kwargs["project_id"] == "p_test"
    assert call_kwargs["project_config"] == {"llm_config": "x"}
