"""시대 오류 groupbg 표적 재생성 — 검색 그라운딩 참조 + 상대 비교 검증.

배경(A급: 실존 고유명사 장소)이 현대 모습으로 그려진 결함(실측: 서울역
=2004년 이후 유리 신역사)의 수정 경로. 원인은 4겹 — 통칭화·주 지시
일반명사·실물 참조 0장·시대 앵커의 한계(텍스트로는 실존 건물의 당시
모습을 특정 못 함). 해법 = 당시 실물 사진을 검색으로 확보해 참조로
붙이고, 기존 groupbg 프롬프트(콘티 카메라 계약)를 유지한 채 재생성.

흐름:
  ① records 의 groupbg::<key> 에서 원판 프롬프트·콘티 asset·size 로드
  ② search_grounded_ref.search_reference_images — 원어 지시문으로 당시
     사진 회수(질의·결과 전건 기록) → 후보 다운로드
  ③ VLM(gemini-pro)이 후보 중 시대·장소 부합 1장 선택
  ④ gpt-image-2 edit — [콘티 스케치, 시대 참조] + 원판 프롬프트에
     PERIOD REFERENCE 절만 추가해 재생성 (구도 계약 불변)
  ⑤ VLM 상대 비교(신 vs 구, 좌우 2회) — 신본이 우세할 때만 통과
  ⑥ 원판 .bak 보존 후 같은 파일명으로 교체 (하류 참조 경로 불변)

사용:
  .venv/bin/python regen_period_bg.py --group 대형역_앞거리 \
      --directive "1980년대 서울역 앞 광장과 역사 건물의 실제 모습 사진" \
      --terms "1980년대 서울역" "서울역 1980" \
      [--dry]   # 교체 없이 산출·판정만
"""
from __future__ import annotations

import argparse
import json
import shutil
import sys
import time
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

PROJ = "c7e3b2e7-c545-4516-93b2-62a51a74d794"
EPI = "7c902020-4451-4967-9eb6-1e53c2b9b717"
ROOT = Path(__file__).resolve().parent.parent
RECIPE = ROOT / f"projects/{PROJ}/images/{EPI}/scene/recipe"
OUT_DIR = ROOT / "artifact" / "20260812_A급배경_시대재생성"

LANG_LOCK = "모든 검색어는 반드시 한국어로만 작성하라. 영어 단어를 덧붙이지 마라."

PERIOD_CLAUSE = """
PERIOD REFERENCE (attached last): a real photograph of THIS exact place as it
appeared in the story's era. This reference is the sole authority for the era-
correct architecture, façade materials, signage style and street furniture of
the landmark building(s) — reproduce that building's period appearance, not the
modern-day version you may associate with this place. The sketch still owns the
camera and composition; the period reference owns what the place looked like
back then. Do not copy the reference's camera, weather or people.
"""


def q_asset_path(asset_id: str) -> str:
    import subprocess

    out = subprocess.run(
        ["psql", "-h", "localhost", "-U", "theroad", "-d", "theroad",
         "-t", "-A", "-c",
         f"SELECT file_path FROM image_asset WHERE id='{asset_id}';"],
        env={"PGPASSWORD": "theroad_dev_2026",
             "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin"},
        capture_output=True, text=True, check=True)
    p = out.stdout.strip()
    if not p:
        raise RuntimeError(f"asset 경로 없음: {asset_id}")
    return p if p.startswith("/") else str(ROOT / p)


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--group", required=True)
    ap.add_argument("--directive", required=True)
    ap.add_argument("--terms", nargs="*", default=None)
    ap.add_argument("--dry", action="store_true")
    args = ap.parse_args()

    from app.core.steps.shot_conti_light_step import _resolve_openai_client
    from app.modules.llm.gpt_image_primitive import call_gpt_image_bytes
    from app.modules.llm.llm_client import call_structured
    from app.modules.pipeline.multiroll_gemini import png_part
    from app.modules.pipeline.search_grounded_ref import (
        _fetch_safe, search_reference_images)

    records = json.loads((RECIPE / "records.json").read_text("utf-8"))
    rec = records.get(f"groupbg::{args.group}")
    if not rec:
        raise SystemExit(f"groupbg::{args.group} 레코드 없음")
    old_path = Path(rec["bg_path"])
    prompt = rec["prompt"]
    size = (rec.get("meta") or {}).get("size") or "1536x864"
    conti_path = Path(q_asset_path(rec["origin_inputs"]["conti_asset_id"]))
    print(f"원판: {old_path.name} · size {size} · 콘티 {conti_path.name}")

    OUT_DIR.mkdir(parents=True, exist_ok=True)
    work = OUT_DIR / args.group
    work.mkdir(exist_ok=True)

    # ② 검색 — 원어 지시문·질의 기록
    client = _resolve_openai_client()
    sr = search_reference_images(
        client, directive_native=args.directive,
        terms_native=args.terms, language_lock_native=LANG_LOCK)
    (work / "search_result.json").write_text(
        json.dumps({"queries": sr.get("queries"),
                    "images": sr.get("images")}, ensure_ascii=False, indent=1),
        "utf-8")
    print(f"검색 질의: {sr.get('queries')}")
    cands = []
    for i, im in enumerate((sr.get("images") or [])[:8]):
        url = im.get("image_url") or im.get("thumbnail_url")
        if not url:
            continue
        try:
            data = _fetch_safe(url)   # HTTP 오류(403 등)는 전파되는 계약
        except Exception:
            continue
        if not data or len(data) < 20_000:
            continue
        # openai SDK 는 파일 확장자로 mimetype 을 정한다 — 매직 바이트로
        # 실제 형식을 감지해 맞는 확장자로 저장(그 외 형식은 버림).
        if data[:3] == b"\xff\xd8\xff":
            ext = "jpg"
        elif data[:8] == b"\x89PNG\r\n\x1a\n":
            ext = "png"
        elif data[:4] == b"RIFF" and data[8:12] == b"WEBP":
            ext = "webp"
        else:
            continue
        p = work / f"cand_{i}.{ext}"
        p.write_bytes(data)
        cands.append(p)
    if not cands:
        raise SystemExit("검색 참조 후보 0장 — 지시문을 바꿔 다시 시도 필요")
    print(f"후보 {len(cands)}장 확보")

    # ③ VLM 이 시대·장소 부합 1장 선택
    parts = [{"type": "text", "text": (
        "다음 사진들 중, 아래 장소 설명의 '그 시대 실물'로 가장 부합하는 "
        "한 장을 골라라. 관광 홍보물·현대 모습·다른 장소는 제외한다.\n"
        f"장소·시대: {args.directive}")}]
    for i, p in enumerate(cands):
        parts.append({"type": "text", "text": f"후보 {i}:"})
        parts.append(png_part(p))
    pick = call_structured(
        "period_ref_pick", "너는 시대 고증 사진 감별사다.", parts,
        {"type": "object",
         "properties": {"best_index": {"type": "integer"},
                        "reason_ko": {"type": "string"}},
         "required": ["best_index", "reason_ko"]},
        project_config={"period_ref_pick": {"model": "gemini-pro"}},
        schema_name="period_ref_pick",
        opik_metadata={"operation_type": "period_ref_pick",
                       "project_id": PROJ, "episode_id": EPI},
        enable_fallback=False)
    ref = cands[int(pick["best_index"]) % len(cands)]
    print(f"참조 선택: {ref.name} — {pick['reason_ko'][:120]}")

    # ④ 재생성 — 원판 프롬프트 + PERIOD REFERENCE 절, 구도 계약 불변
    new_prompt = prompt + "\n" + PERIOD_CLAUSE
    t0 = time.time()
    png = call_gpt_image_bytes(
        client, mode="edit", prompt=new_prompt,
        ref_paths=[conti_path, ref],
        call_kwargs={"model": "gpt-image-2", "size": size, "quality": "high"},
        capture_metadata={"operation": "period_bg_regen",
                          "group_id": args.group})
    new_path = work / f"groupbg_{args.group}_period.png"
    new_path.write_bytes(png)
    print(f"재생성 완료 {len(png)//1024}KB · {time.time()-t0:.0f}s")

    # ⑤ 상대 비교 — 신 vs 구, 좌우 2회 (둘 다 신본 승일 때만 통과)
    wins = 0
    for order in ((new_path, old_path), (old_path, new_path)):
        cmp_parts = [{"type": "text", "text": (
            "두 배경 사진 A/B 중, 아래 장소가 '그 시대'의 실제 모습으로 "
            "더 부합하는 쪽을 골라라. 건물 양식·간판·거리 시설의 시대 "
            "정합이 판단 기준이다.\n"
            f"장소·시대: {args.directive}")},
            {"type": "text", "text": "A:"}, png_part(order[0]),
            {"type": "text", "text": "B:"}, png_part(order[1])]
        v = call_structured(
            "period_bg_compare", "너는 시대 고증 감독이다.", cmp_parts,
            {"type": "object",
             "properties": {"winner": {"type": "string", "enum": ["A", "B"]},
                            "reason_ko": {"type": "string"}},
             "required": ["winner", "reason_ko"]},
            project_config={"period_bg_compare": {"model": "gemini-pro"}},
            schema_name="period_bg_compare",
            opik_metadata={"operation_type": "period_bg_compare",
                           "project_id": PROJ, "episode_id": EPI},
            enable_fallback=False)
        won = (v["winner"] == "A") == (order[0] == new_path)
        wins += int(won)
        print(f"비교({'신좌' if order[0]==new_path else '구좌'}): "
              f"{'신본 승' if won else '구본 승'} — {v['reason_ko'][:100]}")

    (work / "verdict.json").write_text(json.dumps(
        {"pick": pick, "wins": wins, "size": size,
         "new": str(new_path), "old": str(old_path)},
        ensure_ascii=False, indent=1), "utf-8")

    if wins < 2:
        print(f"교체 보류 — 신본 {wins}/2 승 (2/2 필요). 산출은 {work} 에.")
        return
    if args.dry:
        print(f"dry — 교체 생략. 신본: {new_path}")
        return

    # ⑥ 원판 보존 후 교체 (같은 파일명 — 하류 참조 경로 불변)
    bak = old_path.with_suffix(".png.bak")
    if not bak.exists():
        shutil.copy2(old_path, bak)
    shutil.copy2(new_path, old_path)
    print(f"교체 완료: {old_path.name} (원판={bak.name})")


if __name__ == "__main__":
    main()
