"""Tests for FloorPlanPromptStep — Phase 7 Step 3 step entry / Phase 8 v2."""
from __future__ import annotations

from unittest.mock import MagicMock, patch

from app.core.steps.floor_plan_prompt_step import (
    PROMPT_VERSION,
    FloorPlanPromptStep,
)


def test_step_aggregates_fps_across_groups():
    """모든 chain_bg group의 floor_plans를 모두 모아 LLM 호출."""
    plans_data = {
        "data": {
            "plans": {
                "bg_x": {
                    "status": "ok",
                    "plan": {
                        "group_id": "bg_x",
                        "rationale_summary": "",
                        "floor_plans": [
                            {
                                "fp_id": "fp_living",
                                "sub_location": "living_room",
                                "scope": "x",
                                "depends_on_fp": [],
                            }
                        ],
                        "backgrounds": [
                            {
                                "bg_id": "cb_living_day",
                                "loc_id": "L01",
                                "sub_location": "living_room",
                                "state_label": "day",
                                "depends_on_fp": ["fp_living"],
                                "depends_on_bg": [],
                                "applies_to_shots": ["S01_Shot1"],
                            }
                        ],
                        "gen_order": ["fp_living", "cb_living_day"],
                    },
                },
                "bg_y": {"status": "prev_shot_only", "plan": None},
            }
        }
    }
    cp_map = {
        "background_master_plan": plans_data,
        "scene_save": {
            "data": {"segments": [{"scene_index": 1, "heading": "h", "text": "t"}]}
        },
        "shot_validator": {
            "data": {
                "scenes": [
                    {
                        "scene_index": 1,
                        "shots": [
                            {
                                "shot_index": 1,
                                "description": "d",
                                "location_id": "L01",
                            }
                        ],
                    }
                ]
            }
        },
        "shot_selection": {
            "data": {"scenes": [{"scene_index": 1, "selected_shot_indices": [1]}]}
        },
        "visual_world_rules": {"data": {"rules_text": ""}},
    }
    with patch("app.core.config.settings.background_mode", "on"):
        step = FloorPlanPromptStep.__new__(FloorPlanPromptStep)
        step.project_id = "p"
        step.episode_id = "e"
        step.project_config = {}
        step.build_opik_metadata = MagicMock(return_value={})
        step._load_prev_checkpoint = MagicMock(side_effect=lambda sid: cp_map.get(sid))
        with patch(
            "app.core.steps.floor_plan_prompt_step.run_floor_plan_prompt"
        ) as mock_run:
            mock_run.return_value = {
                "fp_id": "fp_living",
                "t2i_prompt": "x" * 60,
                "key_elements": [],
            }
            result = step._execute()
        assert result["completed_count"] == 1
        assert "fp_living" in (result["data"].get("floor_plans") or {})


# ----- Phase 8 v2 -----


def test_prompt_version_bumped_to_v2():
    """PROMPT_VERSION 갱신 history.

    Phase 8.1: 2.202604300936 (world_guide cp inject)
    Phase 8.3: 3.202604301041 (flat 2D + color-coded area fills, anti-3D)
    D6 v4: 4.202605091200 (BG_ID_RE pattern + Valid bg_ids block + state_label_raw)
    C8 W3a: 5.202605201406 (콘티 example·구체 location ID example·region-specific 가구 example 추상화)
    """
    assert PROMPT_VERSION == "5.202605201406"


def _make_v2_cp_map():
    plans_data = {
        "data": {
            "plans": {
                "g_a": {
                    "status": "ok",
                    "plan": {
                        "group_id": "g_a",
                        "rationale_summary": "",
                        "floor_plans": [
                            {
                                "fp_id": "fp_a",
                                "sub_location": "room_a",
                                "scope": "x",
                                "depends_on_fp": [],
                            }
                        ],
                        "backgrounds": [
                            {
                                "bg_id": "bg_a1",
                                "loc_id": "L01",
                                "sub_location": "room_a",
                                "state_label": "day",
                                "depends_on_fp": ["fp_a"],
                                "depends_on_bg": [],
                                "applies_to_shots": ["S01_Shot1"],
                            },
                            {
                                "bg_id": "bg_a2",
                                "loc_id": "L01",
                                "sub_location": "room_a",
                                "state_label": "night",
                                "depends_on_fp": ["fp_a"],
                                "depends_on_bg": [],
                                "applies_to_shots": ["S02_Shot1"],
                            },
                        ],
                        "gen_order": ["fp_a", "bg_a1", "bg_a2"],
                    },
                }
            }
        }
    }
    return {
        "background_master_plan": plans_data,
        "scene_save": {
            "data": {"segments": [{"scene_index": 1, "heading": "h", "text": "t"}]}
        },
        "shot_validator": {
            "data": {
                "scenes": [
                    {
                        "scene_index": 1,
                        "shots": [
                            {
                                "shot_index": 1,
                                "description": "d",
                                "location_id": "L01",
                            }
                        ],
                    }
                ]
            }
        },
        "shot_selection": {
            "data": {"scenes": [{"scene_index": 1, "selected_shot_indices": [1]}]}
        },
        "visual_world_rules": {"data": {"rules_text": ""}},
    }


def test_step_persists_v2_numbered_and_camera_fields():
    """v2: data.floor_plans[fp_id]에 numbered_elements + camera_recommendations 보존."""
    cp_map = _make_v2_cp_map()
    llm_out = {
        "fp_id": "fp_a",
        "t2i_prompt": "x" * 60,
        "key_elements": ["wall"],
        "numbered_elements": [
            {
                "number": 1,
                "label": "door",
                "category": "opening",
                "position_hint": "north wall",
            },
            {
                "number": 2,
                "label": "wardrobe",
                "category": "furniture",
                "position_hint": "south wall",
            },
        ],
        "camera_recommendations": [
            {
                "bg_id": "bg_a1",
                "sub_location": "room_a",
                "camera_position": "near number 1",
                "camera_height": "1.6m std",
                "lens_hint": "35mm",
            },
            {
                "bg_id": "bg_a2",
                "sub_location": "room_a",
                "camera_position": "near number 2",
                "camera_height": "1.6m std",
                "lens_hint": "35mm",
            },
        ],
    }
    with patch("app.core.config.settings.background_mode", "on"):
        step = FloorPlanPromptStep.__new__(FloorPlanPromptStep)
        step.project_id = "p"
        step.episode_id = "e"
        step.project_config = {}
        step.build_opik_metadata = MagicMock(return_value={})
        step._load_prev_checkpoint = MagicMock(side_effect=lambda sid: cp_map.get(sid))
        with patch(
            "app.core.steps.floor_plan_prompt_step.run_floor_plan_prompt"
        ) as mock_run:
            mock_run.return_value = llm_out
            result = step._execute()
        # caller forwarded expected_bg_ids
        kwargs = mock_run.call_args.kwargs
        assert kwargs.get("expected_bg_ids") == {"bg_a1", "bg_a2"}
        # checkpoint persists v2 fields
        fp = result["data"]["floor_plans"]["fp_a"]
        assert "numbered_elements" in fp
        assert "camera_recommendations" in fp
        assert fp["numbered_elements"][0]["category"] == "opening"
        assert {c["bg_id"] for c in fp["camera_recommendations"]} == {
            "bg_a1",
            "bg_a2",
        }


def test_step_persists_empty_lists_when_llm_returns_empty():
    """v2: LLM이 빈 list 반환 시에도 키가 dict에 보존(downstream consumer 안전)."""
    cp_map = _make_v2_cp_map()
    llm_out = {
        "fp_id": "fp_a",
        "t2i_prompt": "x" * 60,
        "key_elements": [],
        "numbered_elements": [],
        "camera_recommendations": [],
    }
    with patch("app.core.config.settings.background_mode", "on"):
        step = FloorPlanPromptStep.__new__(FloorPlanPromptStep)
        step.project_id = "p"
        step.episode_id = "e"
        step.project_config = {}
        step.build_opik_metadata = MagicMock(return_value={})
        step._load_prev_checkpoint = MagicMock(side_effect=lambda sid: cp_map.get(sid))
        with patch(
            "app.core.steps.floor_plan_prompt_step.run_floor_plan_prompt"
        ) as mock_run:
            mock_run.return_value = llm_out
            result = step._execute()
        fp = result["data"]["floor_plans"]["fp_a"]
        assert fp["numbered_elements"] == []
        assert fp["camera_recommendations"] == []
