"""Task B5 — 비모델 PIL/결정론 아티팩트 capture_artifact 계측 테스트.

순수 PIL producer 내부에서 capture(lineage 풍부화는 Phase C). 전부 default-off —
scope 미배선이면 no-op. PIL save 인자/순서는 불변(capture 는 산출 bytes 읽기만).
"""

import io

from app.services.image_capture.context import generation_context
from app.services.image_capture.queue import CAPTURE_DIAG

_PNG_SIG = b"\x89PNG\r\n\x1a\n"


def _small_png(size=(16, 16), color=(10, 20, 30)):
    from PIL import Image

    buf = io.BytesIO()
    Image.new("RGB", size, color).save(buf, "PNG")
    return buf.getvalue()


# ─────────────────── registered pose underlay ───────────────────

def test_registered_underlay_capture_inside_scope():
    from app.services.registered_pose_guide_service import make_registration_underlay

    bg = _small_png()
    with generation_context("p-ul", "e-ul", "pose_guide") as q:
        out = make_registration_underlay(bg, variant="original")
        assert out[:8] == _PNG_SIG
        assert len(q._items) == 1
        _spool, meta = q._items[0]
        assert meta["pipeline_role"] == "registered_pose_underlay"
        assert meta["pipeline_metadata"]["variant"] == "original"


def test_registered_underlay_no_capture_outside_scope():
    from app.services.registered_pose_guide_service import make_registration_underlay

    before = CAPTURE_DIAG["skipped_no_context"]
    out = make_registration_underlay(_small_png(), variant="original")
    assert out[:8] == _PNG_SIG
    assert CAPTURE_DIAG["skipped_no_context"] >= before + 1


# ─────────────────── space_set marking (mark_fp) ───────────────────

def test_space_set_marked_fp_capture_inside_scope(tmp_path):
    from app.modules.pipeline.space_set_bg import mark_fp

    fp = tmp_path / "fp.png"
    fp.write_bytes(_small_png((64, 64)))
    out = tmp_path / "marked.png"
    with generation_context("p-mk", "e-mk", "space_set") as q:
        mark_fp(str(fp), str(out), 0.5, 0.5, 3)
        assert out.exists()
        assert len(q._items) == 1
        _spool, meta = q._items[0]
        assert meta["pipeline_role"] == "space_set_marked_fp"
        assert meta["pipeline_metadata"]["num"] == 3
        # Wave2: marked FP 는 diagnostic 중간물.
        assert meta["disposition"] == "diagnostic"


def test_space_set_marked_fp_no_capture_outside_scope(tmp_path):
    from app.modules.pipeline.space_set_bg import mark_fp

    fp = tmp_path / "fp.png"
    fp.write_bytes(_small_png((64, 64)))
    out = tmp_path / "marked.png"
    before = CAPTURE_DIAG["skipped_no_context"]
    mark_fp(str(fp), str(out), 0.3, 0.7, 1)
    assert out.exists()
    assert CAPTURE_DIAG["skipped_no_context"] >= before + 1


# ─────────────────── dwelling_zone annotation (verification-only) ───────────────────

def test_dwelling_zone_annotate_capture_inside_scope(tmp_path):
    from app.core.steps.dwelling_zone_map_step import DwellingZoneMapStep

    clean = tmp_path / "clean.png"
    clean.write_bytes(_small_png((100, 100)))
    out = tmp_path / "annot.png"
    # _annotate_fp 는 self._annotate_override 만 참조 → 생성자 우회.
    step = DwellingZoneMapStep.__new__(DwellingZoneMapStep)
    step._annotate_override = None
    zone_map = {
        "grid": {"size": 100},
        "bg_zone_assignments": {
            "bg_L01": {"grid_focus": {"x": 50, "y": 50}, "zone_id": "z1"}
        },
    }
    with generation_context("p-dz", "e-dz", "dwelling_zone") as q:
        res = step._annotate_fp(clean_png=clean, out_path=out, zone_map=zone_map)
        assert res == str(out)
        assert len(q._items) == 1
        _spool, meta = q._items[0]
        assert meta["pipeline_role"] == "dwelling_zone_annotated_fp"
        assert meta["pipeline_metadata"]["verification_only"] is True
        # Wave2: verification-only diagnostic 중간물.
        assert meta["disposition"] == "diagnostic"


# ─────────────────── camera diagram (B4, i2i 입력 diagnostic) ───────────────────

def test_camera_diagram_capture_inside_scope():
    from app.modules.gemini_i2i_editor import _create_camera_diagram

    with generation_context("p-cd", "e-cd", "angle_camera_diagram") as q:
        out = _create_camera_diagram(45, 10, 1.2)
        assert out[:8] == _PNG_SIG
        assert len(q._items) == 1
        _spool, meta = q._items[0]
        assert meta["pipeline_role"] == "angle_camera_diagram"
        assert meta["disposition"] == "diagnostic"
        assert meta["pipeline_metadata"]["horizontal_deg"] == 45


def test_camera_diagram_no_capture_outside_scope():
    from app.modules.gemini_i2i_editor import _create_camera_diagram

    before = CAPTURE_DIAG["skipped_no_context"]
    out = _create_camera_diagram(0, 0, 1.0)
    assert out[:8] == _PNG_SIG
    assert CAPTURE_DIAG["skipped_no_context"] >= before + 1


def test_b4_i2i_edit_output_not_captured_in_diagram_only_scope():
    """B4 불변식(Codex 가드): diagram-only scope 는 diagram 한 장만 잡고,
    i2i_edit 최종 출력(capture_generated_image)은 scope 밖이라 잡히지 않는다.

    실제 _generate_variation_i2i 는 _create_camera_diagram 을 scope 안에서,
    edit_angle(→_gemini_generate_content→i2i_edit capture)을 scope 밖에서 호출한다.
    여기선 그 구조를 직접 모사: scope 안=diagram, scope 종료 후=i2i_edit capture.
    """
    from app.modules.gemini_i2i_editor import _create_camera_diagram
    from app.services.image_capture.sink import capture_generated_image

    with generation_context("p-b4", "e-b4", "angle_camera_diagram") as q:
        _create_camera_diagram(30, 0, 1.0)
        # scope 안: diagram 한 장만 적재(i2i_edit 출력은 여기 없음).
        assert len(q._items) == 1
        assert q._items[0][1]["pipeline_role"] == "angle_camera_diagram"

    # scope 종료(flush) 후 i2i_edit 출력 capture → ctx 없음 → no-op(불변식 핵심).
    before = CAPTURE_DIAG["skipped_no_context"]
    capture_generated_image(b"I2IEDIT", role="i2i_edit")
    assert CAPTURE_DIAG["skipped_no_context"] >= before + 1
