"""W-G (2026-07-03) — same-building fp ref 배선 결정론 테스트.

LLM/image API call 0 (mock 클라이언트), DB write 0.

  - generate_location_aerial_base: building_fp_png 승격(T2I→I2I + 정합 지시)
    vs default(None)=기존 T2I byte-identical.
  - BUILDING_FP_PLATE_GUIDANCE / BUILDING_FP_AERIAL_GUIDANCE: generic 계약
    (시나리오 토큰 0은 grep 게이트가 별도 검증 — 여기선 구조 지시 포함만).
  - config_hash 스탬핑: flag=True 일 때만 payload 에 접힌다(OFF byte-identical).
"""
from __future__ import annotations

import base64
from types import SimpleNamespace

from app.modules.pipeline import outdoor_site_layout_provider as prov
from app.modules.pipeline.outdoor_site_layout_plan import (
    BUILDING_FP_AERIAL_GUIDANCE,
)

_PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 8192


class _RecordingClient:
    """images.generate/edit 호출 kwargs 를 기록하는 mock."""

    def __init__(self):
        b64 = base64.b64encode(_PNG).decode("ascii")
        calls = {"generate": [], "edit": []}

        class _Images:
            def generate(self, **kwargs):
                calls["generate"].append(kwargs)
                return SimpleNamespace(data=[SimpleNamespace(b64_json=b64)])

            def edit(self, **kwargs):
                calls["edit"].append(kwargs)
                return SimpleNamespace(data=[SimpleNamespace(b64_json=b64)])

        self.images = _Images()
        self.calls = calls


_LAYOUT = {
    "landmarks": [
        {"id": "L1", "label": "a long path", "kind": "area",
         "points": [[10, 10], [40, 15], [70, 12]]},
        {"id": "L2", "label": "a structure", "kind": "structure",
         "points": [[20, 30], [30, 30], [30, 40], [20, 40]]},
    ],
    "cameras": [{"shot_index": 1, "pos": [5, 5], "look_at": [40, 40]}],
}


class TestAerialBasePromotion:
    def test_default_none_stays_t2i_generate(self):
        client = _RecordingClient()
        png, meta = prov.generate_location_aerial_base(
            _LAYOUT, openai_client=client, model="gpt-image-2.5-sunburst")
        assert png == _PNG
        assert len(client.calls["generate"]) == 1
        assert len(client.calls["edit"]) == 0
        assert meta["building_fp_used"] is False
        assert BUILDING_FP_AERIAL_GUIDANCE not in meta["base_prompt"]

    def test_building_fp_png_promotes_to_i2i_edit_with_guidance(self):
        client = _RecordingClient()
        png, meta = prov.generate_location_aerial_base(
            _LAYOUT, openai_client=client, model="gpt-image-2.5-sunburst",
            building_fp_png=_PNG)
        assert png == _PNG
        assert len(client.calls["edit"]) == 1
        assert len(client.calls["generate"]) == 0
        assert meta["building_fp_used"] is True
        # 정합 지시가 프롬프트 끝에 붙고, 실제 edit 호출 prompt 와 일치.
        assert meta["base_prompt"].endswith(BUILDING_FP_AERIAL_GUIDANCE)
        sent = client.calls["edit"][0]["prompt"]
        assert sent == meta["base_prompt"]

    def test_empty_bytes_treated_as_no_fp(self):
        client = _RecordingClient()
        _, meta = prov.generate_location_aerial_base(
            _LAYOUT, openai_client=client, model="gpt-image-2.5-sunburst",
            building_fp_png=b"")
        assert meta["building_fp_used"] is False
        assert len(client.calls["generate"]) == 1


class TestGuidanceContracts:
    def test_plate_guidance_is_structural_reference_only(self):
        from app.modules.pipeline.background_render import (
            BUILDING_FP_PLATE_GUIDANCE,
        )
        text = BUILDING_FP_PLATE_GUIDANCE
        # 마지막 첨부를 지칭(render_one_background 의 첨부 순서 계약)하고,
        # fp 자체를 그리지 말 것 + 규모/개구부 정합을 요구해야 한다.
        assert "LAST attached image" in text
        assert "STRUCTURAL REFERENCE ONLY" in text
        assert "doors" in text and "windows" in text
        assert "Do NOT draw the floor plan" in text

    def test_aerial_guidance_forbids_plan_copy(self):
        text = BUILDING_FP_AERIAL_GUIDANCE
        assert "STRUCTURAL REFERENCE ONLY" in text
        assert "footprint" in text
        assert "Do NOT copy" in text


class TestConfigHashStamping:
    def test_background_render_hash_stamps_only_when_flag_on(self, monkeypatch):
        from app.core.config import settings
        from app.core.steps.background_render_step import BackgroundRenderStep

        step = BackgroundRenderStep.__new__(BackgroundRenderStep)
        monkeypatch.setattr(
            settings, "background_render_reference_mode", "shot_aware_plan")
        monkeypatch.setattr(
            settings, "outdoor_building_fp_ref_enabled", False)
        off = step._config_hash()
        monkeypatch.setattr(
            settings, "outdoor_building_fp_ref_enabled", True)
        on = step._config_hash()
        assert off != on
        # OFF 재확인 — True→False 도 invalidate(다시 off 해시로 복귀).
        monkeypatch.setattr(
            settings, "outdoor_building_fp_ref_enabled", False)
        assert step._config_hash() == off

    def test_outdoor_site_layout_hash_stamps_only_when_flag_on(
            self, monkeypatch):
        from app.core.config import settings
        from app.core.steps.outdoor_site_layout_step import (
            OutdoorSiteLayoutStep,
        )

        step = OutdoorSiteLayoutStep.__new__(OutdoorSiteLayoutStep)
        monkeypatch.setattr(
            settings, "outdoor_building_fp_ref_enabled", False)
        off = step._config_hash()
        monkeypatch.setattr(
            settings, "outdoor_building_fp_ref_enabled", True)
        on = step._config_hash()
        assert off != on
        monkeypatch.setattr(
            settings, "outdoor_building_fp_ref_enabled", False)
        assert step._config_hash() == off

    def test_flag_default_is_off(self):
        from app.core.config import Settings

        assert Settings.model_fields[
            "outdoor_building_fp_ref_enabled"].default is False
