#!/usr/bin/env python3
"""조사한 「장소 전체」 사진을 붙이면 그 지역다워지는가 — 3-arm A/B.

무엇을 찾았나 — era 조사는 **정답을 확보하고도 버렸다**.

    era_ref  ae2d4d155c10d3e3  「한국의 현대 골목길 사거리」   ← 어디에도 안 쓰임
    era_ref  de840c9ca2c8d05b  「사거리 도로 노면 표시」       ← S2sh1 에 붙음

버려진 쪽이 정확히 필요한 사진이다(붉은 벽돌 다세대·전봇대와 엉킨 전선·
원형 갓 가로등·세로 한글 간판·포터 트럭). **가장 좁은 것이 붙었다.**

★역할문 라벨이 권위 범위를 정한다 — `It decides ONLY what **this thing**
 looks like`. 그래서 사진만 갈아 끼우면 안 되고 **라벨의 subject 도 함께**
 바꿔야 장소 전체가 권위 안에 든다. 안 바꾸면 사진에 건물이 찍혀 있어도
 모델에게는 「도로 표시」만 베끼라는 뜻이다.

    arm A  도로 표시 사진 + 현행 tail            (현행 그대로)
    arm C  ★장소 전경 사진 + 현행 tail           (참조만 바꾼 효과)
    arm B  ★장소 전경 사진 + 관할 대응표 tail    (둘 다)

C 가 있어야 「참조 교체」와 「tail 교체」 중 무엇이 효과를 냈는지 갈린다.

★tail v22 는 첫 수리안(v21)과 다르다. v21 은 「이런 것들은 발명이 아니다」로
 경계를 다시 열어 **더 허용적인 arm** 이 됐다(Codex 지적). v22 는 금지 나열
 대신 **관할 대응표** 다 — WHAT/HOW MANY=목록, WHERE/SILHOUETTE=스케치,
 LOOK=참조 사진(라벨 범위 안), FALLBACK=세계 사실. 자기 footprint 가 필요한
 것은 LOOK 이 아니라 WHAT 이라 목록에 있어야 한다.

usage: ab_bg_place_ref.py [--dry] [--rounds=3]
"""
import json
import re
import sys
from pathlib import Path

sys.path.insert(0, "/Users/manta/Documents/Projects/TheRoad-I1/scratchpad")
import _opik_env  # noqa: E402,F401  ★cwd 를 backend 로 고정

ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
PROJ = "da049582-2c6d-492c-979d-f468d61bab6e"
EPI = "fb7a883f-baac-4145-9131-732ce628d474"
IMG = ROOT / "projects" / PROJ / "images" / EPI
RECIPE = IMG / "scene" / "recipe"
OUT = ROOT / "artifact" / "20260825_bg_place_ref_ab"
SCRATCH = Path("/private/tmp/claude-501/-Users-manta-Documents-Projects-TheRoad-I1"
               "/e9fa4bb4-eeb7-49ef-819c-f7e09fc1224f/scratchpad")
TAIL_V22 = SCRATCH / "bg_fill_tail_v22_draft.md"

SHOT = "S2sh1"
OLD_HEAD = "THINGS THAT LIVE AT THIS PLACE"
# 버려졌던 「장소 전체」 조사 — 이것을 붙인다
PLACE_HASH = "ae2d4d155c10d3e3"
RE_ERA_LABEL = re.compile(r"(PERIOD REFERENCE — )([^:]+)(:)")


def _dsn() -> str:
    """DB 접속 문자열은 `backend/.env` 에서 읽는다.

    ★코드에 적어 두면 저장소에 자격 증명이 남는다(GitGuardian).
     `_opik_env` 가 cwd 를 backend 로 옮겨 두므로 설정이 채워진다.
    """
    from app.core.config import settings

    url = getattr(settings, "database_url", "") or ""
    if not url:
        raise SystemExit("[설정 미로드] database_url 이 비었다 — "
                         "backend/.env 를 못 읽었다")
    return url


def swap_tail(prompt: str, new_tail: str) -> str:
    """`THINGS THAT LIVE AT THIS PLACE` 절만 갈아 끼운다 (era 역할문은 보존)."""
    i = prompt.find(OLD_HEAD)
    if i < 0:
        return prompt
    rest = prompt[i:]
    j = rest.find("PERIOD REFERENCE")
    era_part = rest[j:] if j >= 0 else ""
    return prompt[:i] + new_tail.strip() + (("\n\n" + era_part) if era_part else "\n")


def swap_era_subject(prompt: str, subject: str) -> str:
    """역할문의 subject 를 바꾼다 — 이것이 참조의 **권위 범위**다."""
    return RE_ERA_LABEL.sub(lambda m: f"{m.group(1)}{subject}{m.group(3)}",
                            prompt, count=1)


def main() -> int:
    dry = "--dry" in sys.argv
    rounds = 3
    for a in sys.argv[1:]:
        if a.startswith("--rounds="):
            rounds = int(a.split("=", 1)[1])

    recs = json.loads((RECIPE / "records.json").read_text(encoding="utf-8"))
    rec = recs[f"{SHOT}::bgfirst_bg"]
    base = rec.get("prompt") or ""
    tail22 = TAIL_V22.read_text(encoding="utf-8")

    # 참조 둘 — 콘티(고정)와 era 사진(arm 마다 갈림)
    conti = None
    for aid in (rec.get("input_asset_ids") or []):
        import psycopg2
        cn = psycopg2.connect(_dsn())
        cur = cn.cursor()
        cur.execute("SELECT file_path FROM image_asset WHERE id=%s", (aid,))
        row = cur.fetchone()
        cn.close()
        if row:
            p = IMG / row[0] if not Path(row[0]).is_absolute() else Path(row[0])
            if not p.exists():
                p = ROOT / row[0]
            if p.exists():
                conti = p
                break
    era_narrow = RECIPE / f"eraref_{(rec.get('era_research') or {}).get('h','')}.png"
    if not era_narrow.exists():
        # subject 로 되짚는다
        subj = (rec.get("era_research") or {}).get("subject", "")
        for k, v in recs.items():
            if k.startswith("era_ref::") and isinstance(v, dict) \
                    and v.get("subject") == subj:
                era_narrow = RECIPE / f"eraref_{k.split('::', 1)[1]}.png"
                break
    era_place = RECIPE / f"eraref_{PLACE_HASH}.png"
    place_subject = ""
    for k, v in recs.items():
        if k == f"era_ref::{PLACE_HASH}" and isinstance(v, dict):
            place_subject = v.get("subject", "")

    for name, p in (("콘티", conti), ("좁은 참조", era_narrow),
                    ("장소 전경", era_place)):
        print(f"  {name:<10} {'OK ' if p and p.exists() else '✘ 없음 '}{p}")
    if not (conti and conti.exists() and era_narrow.exists() and era_place.exists()):
        print("★재료가 빠졌다 — 멈춘다.")
        return 1
    print(f"  장소 전경 라벨: {place_subject}\n")

    # ── 세 arm 조립 ────────────────────────────────────────────────
    arms = {
        "A": (base, era_narrow, "도로 표시 + 현행 tail"),
        "C": (swap_era_subject(base, place_subject), era_place,
              "★장소 전경 + 현행 tail"),
        "B": (swap_era_subject(swap_tail(base, tail22), place_subject), era_place,
              "★장소 전경 + 대응표 tail"),
    }
    for k, (txt, ref, note) in arms.items():
        lbl = RE_ERA_LABEL.search(txt)
        print(f"  arm {k}  {len(txt):>6,}자  참조={ref.name}  {note}")
        print(f"          역할문 subject = {lbl.group(2) if lbl else '(없음)'}")

    OUT.mkdir(parents=True, exist_ok=True)
    if dry:
        (OUT / "_plan.json").write_text(json.dumps(
            {k: {"chars": len(t), "ref": r.name, "note": n}
             for k, (t, r, n) in arms.items()}, ensure_ascii=False, indent=2),
            encoding="utf-8")
        for k, (t, _, _) in arms.items():
            (OUT / f"_prompt_{k}.txt").write_text(t, encoding="utf-8")
        print(f"\n--dry — 프롬프트 세 벌을 {OUT} 에 적어 뒀다.")
        return 0

    from app.core.config import settings
    from app.core.openai_keys import openai_client
    from app.modules.llm.gpt_image_primitive import call_gpt_image_bytes
    from app.modules.pipeline.still_recipe import (
        BGFIRST_BG_IMAGE_MODEL, BGFIRST_BG_SIZE,
    )

    client = openai_client(timeout=float(settings.llm_timeout_image_gen))
    # ★회차마다 arm 순서를 돌린다 — 시간대 편향을 한 arm 이 몰아 받지 않게
    seq = []
    keys = ["A", "C", "B"]
    for r in range(1, rounds + 1):
        rot = keys[(r - 1) % 3:] + keys[:(r - 1) % 3]
        seq.extend((r, k) for k in rot)

    for rnd, k in seq:
        txt, ref, _ = arms[k]
        dst = OUT / f"{SHOT}_{k}{rnd}.png"
        if dst.exists():
            print(f"  {SHOT}_{k}{rnd} 이미 있음 — 건너뜀")
            continue
        try:
            png = call_gpt_image_bytes(
                client, mode="edit", prompt=txt,
                ref_paths=[conti, ref],
                call_kwargs={"model": BGFIRST_BG_IMAGE_MODEL,
                             "size": BGFIRST_BG_SIZE,
                             "quality": "high", "n": 1},
            )
            dst.write_bytes(png)
            print(f"  {SHOT}_{k}{rnd} ✔ {len(png):,}B")
        except Exception as e:  # noqa: BLE001
            print(f"  {SHOT}_{k}{rnd} ✘ {e}")
    print(f"\n→ {OUT}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
