"""Tests for background_render — Phase 7 Step 6 (gpt-image-2)."""
from __future__ import annotations

from unittest.mock import MagicMock

from app.modules.pipeline.background_render import render_one_background


def test_render_with_fp_and_prior_bg(tmp_path):
    """fp_path + prior_bg_paths 둘 다 있으면 multi-image edit (image=list)."""
    client = MagicMock()
    resp = MagicMock()
    resp.data = [MagicMock(b64_json="aGVsbG8=")]  # base64('hello')
    client.images.edit.return_value = resp

    fp = tmp_path / "fp.png"
    fp.write_bytes(b"FP")
    prior = tmp_path / "prior.png"
    prior.write_bytes(b"PR")
    out = tmp_path / "cb_x.png"

    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="bg prompt",
        out_path=out,
        fp_path=fp,
        prior_bg_paths=[prior],
        bg_id="cb_x",
    )

    assert res["status"] == "ok"
    assert res["png_path"] == str(out)
    assert out.exists() and out.read_bytes() == b"hello"
    assert client.images.edit.called
    assert not client.images.generate.called
    call_kwargs = client.images.edit.call_args.kwargs
    assert isinstance(call_kwargs["image"], list)
    assert len(call_kwargs["image"]) == 2
    assert res["ref_used"].startswith("refs_") or res["ref_used"] == "fp_only"


def test_render_text_only_when_no_refs(tmp_path):
    """fp_path=None + prior_bg_paths=[] → text-only generate."""
    client = MagicMock()
    resp = MagicMock()
    resp.data = [MagicMock(b64_json="aGVsbG8=")]
    client.images.generate.return_value = resp
    out = tmp_path / "cb_x.png"

    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb_x",
    )

    assert res["status"] == "ok"
    assert res["ref_used"] == "text_only"
    assert client.images.generate.called
    assert not client.images.edit.called
    assert out.exists() and out.read_bytes() == b"hello"


def test_render_one_background_default_size_16_9(tmp_path):
    """Phase 8: default size = 1536x864 (16:9 cinematic) — no explicit kwarg.

    Caller가 size 인자를 명시 전달하지 않을 때, multi-image edit / single
    edit / text-only generate 모두 cinematic 16:9 (1536x864)로 호출되어야
    한다.
    """
    # ── multi-image edit path (fp + prior) ──
    client_multi = MagicMock()
    resp_multi = MagicMock()
    resp_multi.data = [MagicMock(b64_json="aGVsbG8=")]
    client_multi.images.edit.return_value = resp_multi

    fp = tmp_path / "fp.png"
    fp.write_bytes(b"FP")
    prior = tmp_path / "prior.png"
    prior.write_bytes(b"PR")
    out_multi = tmp_path / "cb_multi.png"

    render_one_background(
        openai_client=client_multi,
        image_model="gpt-image-2.5-sunburst",
        prompt="bg prompt",
        out_path=out_multi,
        fp_path=fp,
        prior_bg_paths=[prior],
        bg_id="cb_multi",
    )
    assert client_multi.images.edit.call_args.kwargs["size"] == "1536x864"

    # ── single edit path (fp only) ──
    client_single = MagicMock()
    resp_single = MagicMock()
    resp_single.data = [MagicMock(b64_json="aGVsbG8=")]
    client_single.images.edit.return_value = resp_single
    out_single = tmp_path / "cb_single.png"

    render_one_background(
        openai_client=client_single,
        image_model="gpt-image-2.5-sunburst",
        prompt="bg prompt",
        out_path=out_single,
        fp_path=fp,
        prior_bg_paths=[],
        bg_id="cb_single",
    )
    assert client_single.images.edit.call_args.kwargs["size"] == "1536x864"

    # ── text-only generate path (no refs) ──
    client_gen = MagicMock()
    resp_gen = MagicMock()
    resp_gen.data = [MagicMock(b64_json="aGVsbG8=")]
    client_gen.images.generate.return_value = resp_gen
    out_gen = tmp_path / "cb_gen.png"

    render_one_background(
        openai_client=client_gen,
        image_model="gpt-image-2.5-sunburst",
        prompt="bg prompt",
        out_path=out_gen,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb_gen",
    )
    assert client_gen.images.generate.call_args.kwargs["size"] == "1536x864"


# ── W-G (2026-07-03): building_fp_path 추가 구조 참조 ──


def _ok_client_edit():
    client = MagicMock()
    resp = MagicMock()
    resp.data = [MagicMock(b64_json="aGVsbG8=")]
    client.images.edit.return_value = resp
    client.images.generate.return_value = resp
    return client


def test_building_fp_default_none_is_byte_identical(tmp_path):
    """building_fp_path 미전달 = 기존 계약 그대로 (text_only + 마킹 False)."""
    client = _ok_client_edit()
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb",
    )
    assert res["ref_used"] == "text_only"
    assert res["building_fp_attached"] is False
    assert client.images.generate.called
    assert not client.images.edit.called


def test_building_fp_alone_promotes_text_only_to_single_edit(tmp_path):
    """direct-plate lane: 자기 fp 없음 + building fp → single edit (refs_1)."""
    client = _ok_client_edit()
    bfp = tmp_path / "fp_building.png"
    bfp.write_bytes(b"BF")
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb",
        building_fp_path=bfp,
    )
    assert res["status"] == "ok"
    assert res["ref_used"] == "refs_1"  # fp_only 아님 — 자기 fp 채널과 구분
    assert res["building_fp_attached"] is True
    assert client.images.edit.called
    assert not client.images.generate.called


def test_building_fp_appended_last_after_fp_and_prior(tmp_path):
    """shot_aware lane: ref 순서 = 자기 fp → prior → building fp (마지막)."""
    client = _ok_client_edit()
    fp = tmp_path / "fp_self.png"
    fp.write_bytes(b"FP")
    prior = tmp_path / "prior.png"
    prior.write_bytes(b"PR")
    bfp = tmp_path / "fp_building.png"
    bfp.write_bytes(b"BF")
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=fp,
        prior_bg_paths=[prior],
        bg_id="cb",
        building_fp_path=bfp,
    )
    assert res["ref_used"] == "refs_3"
    assert res["building_fp_attached"] is True
    handles = client.images.edit.call_args.kwargs["image"]
    assert isinstance(handles, list) and len(handles) == 3
    # 첨부 순서 계약 — 프롬프트 정합 지시가 'LAST attached image' 를 지칭한다.
    assert [h.name for h in handles] == [str(fp), str(prior), str(bfp)]


def test_building_fp_missing_file_ignored(tmp_path):
    """building fp 파일이 사라졌으면 조용히 미첨부(기존 경로) — 비차단."""
    client = _ok_client_edit()
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb",
        building_fp_path=tmp_path / "nope.png",
    )
    assert res["ref_used"] == "text_only"
    assert res["building_fp_attached"] is False
    assert client.images.generate.called


# ── W-I (2026-07-03): building group anchor 렌더 ref ──


def test_building_anchor_default_none_is_byte_identical(tmp_path):
    """building_anchor_path 미전달 = 기존 계약 그대로 (마킹 False)."""
    client = _ok_client_edit()
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb",
    )
    assert res["ref_used"] == "text_only"
    assert res["building_anchor_attached"] is False
    assert client.images.generate.called
    assert not client.images.edit.called


def test_building_anchor_order_prior_then_anchor_then_building_fp(tmp_path):
    """ref 순서 계약 = 자기 fp → prior → building anchor → building fp(마지막)."""
    client = _ok_client_edit()
    fp = tmp_path / "fp_self.png"
    fp.write_bytes(b"FP")
    prior = tmp_path / "prior.png"
    prior.write_bytes(b"PR")
    anchor = tmp_path / "anchor_plate.png"
    anchor.write_bytes(b"AN")
    bfp = tmp_path / "fp_building.png"
    bfp.write_bytes(b"BF")
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=fp,
        prior_bg_paths=[prior],
        bg_id="cb",
        building_fp_path=bfp,
        building_anchor_path=anchor,
    )
    assert res["ref_used"] == "refs_4"
    assert res["building_anchor_attached"] is True
    assert res["building_fp_attached"] is True
    handles = client.images.edit.call_args.kwargs["image"]
    assert [h.name for h in handles] == [
        str(fp), str(prior), str(anchor), str(bfp)]


def test_building_anchor_alone_promotes_text_only_to_single_edit(tmp_path):
    """direct-plate lane: ref 0 + anchor → single edit (refs_1)."""
    client = _ok_client_edit()
    anchor = tmp_path / "anchor_plate.png"
    anchor.write_bytes(b"AN")
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb",
        building_anchor_path=anchor,
    )
    assert res["status"] == "ok"
    assert res["ref_used"] == "refs_1"
    assert res["building_anchor_attached"] is True
    assert client.images.edit.called
    assert not client.images.generate.called


def test_building_anchor_missing_file_ignored(tmp_path):
    """anchor 파일이 사라졌으면 조용히 미첨부(기존 경로) — 비차단."""
    client = _ok_client_edit()
    out = tmp_path / "cb.png"
    res = render_one_background(
        openai_client=client,
        image_model="gpt-image-2.5-sunburst",
        prompt="x",
        out_path=out,
        fp_path=None,
        prior_bg_paths=[],
        bg_id="cb",
        building_anchor_path=tmp_path / "nope.png",
    )
    assert res["ref_used"] == "text_only"
    assert res["building_anchor_attached"] is False
    assert client.images.generate.called
