"""3a fix — background_render depth-1 star anchor 주입 결정론 테스트 (2026-07-01).

_inject_star_anchors 순수 함수만 검증(생성/DB/이미지 무관). flag 게이팅은 caller
(_execute)에서 하고, 이 함수는 flag ON 일 때만 불린다 — OFF byte-identical 은
caller 가 미호출로 보장하므로 여기선 주입 규칙(그룹/anchor/preserve/self)만 잠근다.
"""
from app.core.steps.background_render_step import (
    _compose_bg_input_image_ids,
    _inject_star_anchors,
)


def _specs(*items):
    """(bg_id, loc_id, depends_on_bg) → bg_specs/items/order/renderable 빌드."""
    bg_specs = {bid: {"loc_id": loc, "depends_on_bg": list(dob)}
                for bid, loc, dob in items}
    order = [bid for bid, _, _ in items]
    items_map = {bid: {"parent_id": (dob[0] if dob else "")}
                 for bid, _, dob in items}
    return bg_specs, items_map, order, set(order)


def test_star_anchor_groups_same_loc_to_first():
    bg_specs, items, order, rnd = _specs(
        ("L04B01", "L04", []), ("L04B02", "L04", []), ("L04B03", "L04", []))
    inj = _inject_star_anchors(bg_specs, items, order, rnd)
    assert inj == {"L04B02": "L04B01", "L04B03": "L04B01"}
    assert bg_specs["L04B01"]["depends_on_bg"] == []          # anchor 자신 불변
    assert bg_specs["L04B02"]["depends_on_bg"] == ["L04B01"]
    assert items["L04B02"]["parent_id"] == "L04B01"           # DAG parent 갱신
    assert bg_specs["L04B02"]["_star_anchor_meta"]["anchor_bg_id"] == "L04B01"


def test_star_anchor_single_bg_group_skipped():
    bg_specs, items, order, rnd = _specs(("L03B01", "L03", []))
    assert _inject_star_anchors(bg_specs, items, order, rnd) == {}
    assert bg_specs["L03B01"]["depends_on_bg"] == []


def test_star_anchor_preserves_existing_chain():
    # 기존 depends_on_bg 가 있으면 덮어쓰지 않음(순환/기존 체인 파괴 방지).
    bg_specs, items, order, rnd = _specs(
        ("L05B01", "L05", []), ("L05B02", "L05", ["L05B01"]))
    inj = _inject_star_anchors(bg_specs, items, order, rnd)
    assert "L05B02" not in inj
    assert bg_specs["L05B02"]["depends_on_bg"] == ["L05B01"]


def test_star_anchor_anchor_priority_b01_natural_sort():
    # 순서가 뒤섞여도 자연정렬 첫(B01)이 anchor.
    bg_specs, items, order, rnd = _specs(
        ("L07B03", "L07", []), ("L07B01", "L07", []), ("L07B02", "L07", []))
    inj = _inject_star_anchors(bg_specs, items, order, rnd)
    assert set(inj.values()) == {"L07B01"}
    assert "L07B01" not in inj


def test_star_anchor_no_loc_id_excluded():
    # loc_id 없는 bg 는 그룹 대상 아님(default-deny).
    bg_specs, items, order, rnd = _specs(
        ("XB01", "", []), ("XB02", "", []))
    assert _inject_star_anchors(bg_specs, items, order, rnd) == {}


def test_star_anchor_only_renderable_grouped():
    # renderable 아닌 bg 는 그룹 카운트/anchor 후보에서 제외.
    bg_specs, items, order, _ = _specs(
        ("L08B01", "L08", []), ("L08B02", "L08", []))
    rnd = {"L08B01"}   # B02 는 prompt 실패 등으로 non-renderable
    assert _inject_star_anchors(bg_specs, items, order, rnd) == {}  # 그룹 <2


# ── 3a recording: _compose_bg_input_image_ids (실제 첨부 prior_bg → input_image_ids) ──

def test_compose_i2i_reference_fp_then_prior_order():
    # 실제 i2i 첨부(prior_bg_ids)=[B04] → [fp, B04], kind=i2i_reference.
    iids, meta = _compose_bg_input_image_ids(
        ["L04B04"], "fp-uuid", {"L04B04": "b04-uuid"}, reuse=False)
    assert iids == ["fp-uuid", "b04-uuid"]           # fp 먼저, 그다음 prior
    assert meta["lineage_kind"] == "i2i_reference"
    assert meta["prior_bg_ids"] == ["L04B04"]
    assert meta["bg_lineage_policy"] == "attached_prior_bg"
    assert "unresolved_inputs" not in meta


def test_compose_reuse_alias_tagged():
    # reuse_existing_plate(copy-less alias) → 엣지 유지하되 kind=reuse_alias.
    iids, meta = _compose_bg_input_image_ids(
        ["L04B02"], "fp-uuid", {"L04B02": "b02-uuid"}, reuse=True)
    assert iids == ["fp-uuid", "b02-uuid"]
    assert meta["lineage_kind"] == "reuse_alias"


def test_compose_multiple_prior_bgs():
    iids, meta = _compose_bg_input_image_ids(
        ["A", "B"], "fp", {"A": "ua", "B": "ub"}, reuse=False)
    assert iids == ["fp", "ua", "ub"]
    assert meta["prior_bg_ids"] == ["A", "B"]


def test_compose_unresolved_prior_kept_as_structural_key():
    # 부분재렌더 등으로 resolver 에 없으면 unresolved_inputs 구조키(bg:<id>), fp 유지.
    iids, meta = _compose_bg_input_image_ids(
        ["L04B99"], "fp-uuid", {}, reuse=False)
    assert iids == ["fp-uuid"]                        # 미해결 prior 는 iids 에서 제외
    assert meta["unresolved_inputs"] == ["bg:L04B99"]


def test_compose_partial_resolve_mixes():
    iids, meta = _compose_bg_input_image_ids(
        ["A", "MISS"], "fp", {"A": "ua"}, reuse=False)
    assert iids == ["fp", "ua"]
    assert meta["unresolved_inputs"] == ["bg:MISS"]


def test_compose_no_fp_uuid_prior_only():
    # fp UUID 미해결(None) 이면 prior 만.
    iids, _ = _compose_bg_input_image_ids(
        ["L04B04"], None, {"L04B04": "b04-uuid"}, reuse=False)
    assert iids == ["b04-uuid"]


def test_compose_empty_prior_returns_fp_only():
    # prior 없음(첨부 없는 bg) → phase-2 는 애초에 안 부르지만 방어적으로 [fp].
    iids, meta = _compose_bg_input_image_ids([], "fp", {}, reuse=False)
    assert iids == ["fp"]
    assert meta["prior_bg_ids"] == []


def test_compose_dedup_prior_stable_order():
    # 중복 prior_bg_id → dedup + stable order(첫 등장 순서) 보존 (Codex NARROW).
    iids, meta = _compose_bg_input_image_ids(
        ["B", "A", "B", "A"], "fp", {"A": "ua", "B": "ub"}, reuse=False)
    assert iids == ["fp", "ub", "ua"]                # B 먼저(첫 등장), dedup
    assert meta["prior_bg_ids"] == ["B", "A", "B", "A"]  # 원본 lineage 는 그대로 메타에


def test_compose_fp_not_duplicated_when_in_prior():
    # fp_uuid 가 prior 에도 해석되면(비정상) input_image_ids 에 중복 안 됨.
    iids, _ = _compose_bg_input_image_ids(
        ["X"], "same-uuid", {"X": "same-uuid"}, reuse=False)
    assert iids == ["same-uuid"]                     # 중복 제거


def test_compose_prior_excludes_fp_by_construction():
    # prior_bg_ids 는 FP 를 담지 않는다(lineage 계약). compose 는 fp 를 prior 로 취급 안 함.
    iids, meta = _compose_bg_input_image_ids(
        ["L04B04"], "fp-uuid", {"L04B04": "b04"}, reuse=False)
    assert iids[0] == "fp-uuid"                       # fp 는 항상 별도 맨 앞
    assert "fp-uuid" not in meta["prior_bg_ids"]
