"""S5_Shot3 chain bg 이중 묘사 가설 검증 테스트.

가설: chain bg PNG가 reference로 주입되면, 그 이미지에 이미 그려진 배경 요소
(TV, 창문, 싱크대, 식탁, 문)을 t2i_prompt가 또 묘사할 때 모델이 그 자리에
이중 합성을 하거나 배경을 변형시킨다.

검증 방법: 같은 reference 이미지 + 같은 모델로 prompt만 두 버전 호출:
  A. 기존 prompt (재묘사 포함, production 그대로)
  B. 기존 prompt + [배경 참조 안내] 섹션 추가 ("TV/창문/싱크대 등은 chain bg에
     이미 있음. 그대로 유지하고 새로 그리지 말 것")

production 코드 (`gemini_i2i_editor._gemini_generate_content`)와 동일한 방식
으로 호출. 모델은 nano-banana-2 (gemini-3.1-flash-image-preview).

reference 순서는 production resolve_refs_for_prompt와 동일:
  1. chain bg ref (background_chain_node primary path)
  2. character C04 primary ref (composite 없으므로 face only)
  3. P06 prop ref (t2i_prompt에 P06 명시됨)

미포함:
  - O06 outfit (composite 없어 production이 차단)
  - P03 prop (t2i_prompt에 P03 미명시)
  - prev_shot_ref (chain bg 있어 fallback 안 됨)
  - shot_dependency (S5_Shot3 dep_id=None)

사용법:
  cd backend
  .venv/bin/python scripts/experiment_chain_bg_double_render_test.py

결과:
  scripts/experiment_results/s5shot3_A_original.png
  scripts/experiment_results/s5shot3_B_with_bg_note.png
  scripts/experiment_results/s5shot3_metadata.json
"""
from __future__ import annotations

import base64
import json
import os
import socket
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path

# .env 로드
try:
    from dotenv import load_dotenv
    load_dotenv(Path(__file__).parent.parent / ".env")
except ImportError:
    pass


PID = "c00bbe19-a9b5-463f-acfc-806f2e820258"
EID = "fe165e3a-19c2-4a0f-9acb-e0c9bab0ee5a"
PROJECT_ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")

# Production reference 경로 (S5_Shot3 분석 결과)
CHAIN_BG = (
    PROJECT_ROOT / "projects" / PID / "images" / EID
    / "background_chain" / "L05" / "interior_living_kitchen_day_normal.png"
)
C04_FACE_REF = (
    PROJECT_ROOT / "projects" / PID / "images" / EID
    / "reference" / "008b5d4c-5a02-4233-853e-ffc99225cfe1.png"
)
P06_REF = (
    PROJECT_ROOT / "projects" / PID / "images" / EID
    / "reference" / "48735792-33c0-415b-afdf-46146fe19ed4.png"
)

# 기존 production prompt (DB에서 가져온 그대로)
PROMPT_A_ORIGINAL = (
    "Photorealistic cinematic still. "
    "[L05: A cramped modern Korean 옥탑방 (rooftop room) interior, dusty small "
    "window, worn sink, low table, pale daylight, damp muted domestic shadows.] "
    "C04O06 in a plain apron occupies the right-center of the frame, at "
    "three-quarter angle from the left, one hand gripping the apron hem, eyes "
    "fixed on the television at the far left. The lens emphasizes her serious "
    "expression, tightened jaw, and the tense hand at the apron hem through a "
    "thin veil of white kitchen steam in the left foreground. A blurred "
    "television edge, screen angled away, sits at the far left, its cold "
    "blue-gray glow brushing her cheek; pale daylight from the closed dusty "
    "window adds washed yellow highlights. A steaming pot with a slightly "
    "lifted lid sits in the foreground, P06 rests rinsed beside the sink, and "
    "a thin stream from the faucet falls into the basin in the background. "
    "Muted blue-gray and washed yellow palette, damp domestic surfaces, uneasy "
    "stillness."
)

# 같은 prompt + 배경 참조 안내 섹션 추가
BG_NOTE = (
    "\n\n[Background reference notice — read FIRST]\n"
    "The first reference image (background chain ref) already contains the full "
    "interior layout: the television (left side), the small window, the worn "
    "sink, the kitchen counter, the low dining table, the front door, and the "
    "wall finish. These background elements are FIXED and must be reused as-is "
    "from the reference image. DO NOT redraw them, do not change their "
    "position, size, color, or style. The prompt mentions some of these "
    "elements only to indicate where the character looks or what light hits "
    "her face — they are NOT instructions to redraw the elements themselves. "
    "Add only: the character (C04O06), her pose, her gaze direction, the "
    "steam, the steaming pot in the foreground, and P06 (a herb) beside the "
    "sink. Keep everything else identical to the background reference."
)

PROMPT_B_WITH_NOTE = PROMPT_A_ORIGINAL + BG_NOTE

# Production reference 라벨 (resolve_refs_for_prompt 순서 그대로)
INPUT_IMAGES = [
    (
        "background chain ref (interior_living_kitchen_day_normal for L05) — "
        "match wall/floor/ceiling/lighting",
        CHAIN_BG.read_bytes(),
    ),
    ("character C04 identity", C04_FACE_REF.read_bytes()),
    ("object P06", P06_REF.read_bytes()),
]

GEMINI_API_URL_TEMPLATE = (
    "https://generativelanguage.googleapis.com/v1beta/models/{model}:"
    "generateContent?key={api_key}"
)
MODEL = "gemini-3.1-flash-image-preview"
ASPECT_RATIO = "16:9"
TIMEOUT = 180
MAX_RETRIES = 2


def gemini_generate(api_key: str, prompt: str, input_images: list) -> bytes:
    """production gemini_i2i_editor._gemini_generate_content와 동일한 호출."""
    parts: list = [{"text": prompt}]
    for label, img_bytes in input_images:
        if label:
            parts.append({"text": label})
        parts.append({
            "inline_data": {
                "mime_type": "image/png",
                "data": base64.b64encode(img_bytes).decode("ascii"),
            }
        })

    body = {
        "contents": [{"parts": parts}],
        "generationConfig": {
            "responseModalities": ["TEXT", "IMAGE"],
            "imageConfig": {
                "aspectRatio": ASPECT_RATIO,
                "imageSize": "2K",
            },
        },
    }
    url = GEMINI_API_URL_TEMPLATE.format(model=MODEL, api_key=api_key)
    req = urllib.request.Request(
        url,
        data=json.dumps(body).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )

    last_error = None
    for attempt in range(1, MAX_RETRIES + 2):
        try:
            with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
                payload = json.loads(resp.read().decode("utf-8"))
            break
        except urllib.error.HTTPError as exc:
            text = exc.read().decode("utf-8", errors="replace")
            last_error = RuntimeError(f"HTTP {exc.code}: {text}")
            if exc.code in {429, 500, 502, 503, 504} and attempt <= MAX_RETRIES:
                time.sleep(2 * attempt)
                continue
            raise last_error from exc
        except (urllib.error.URLError, socket.timeout) as exc:
            last_error = exc
            if attempt <= MAX_RETRIES:
                time.sleep(2 * attempt)
                continue
            raise RuntimeError(f"URL error after retries: {exc}") from exc
    else:
        raise RuntimeError(f"Failed: {last_error}")

    pf = payload.get("promptFeedback", {})
    if pf.get("blockReason"):
        raise RuntimeError(f"Moderation blocked: {pf['blockReason']}")
    cands = payload.get("candidates", [])
    if cands and cands[0].get("finishReason") == "SAFETY":
        raise RuntimeError("Safety filter")
    for cand in cands:
        for part in cand.get("content", {}).get("parts", []):
            inline = part.get("inlineData") or part.get("inline_data")
            if isinstance(inline, dict) and inline.get("data"):
                return base64.b64decode(inline["data"])
    raise RuntimeError(f"No image: {json.dumps(payload)[:500]}")


def main():
    api_key = os.environ.get("GEMINI_API_KEY", "")
    if not api_key:
        print("ERROR: GEMINI_API_KEY env not set", file=sys.stderr)
        sys.exit(1)

    out_dir = Path(__file__).parent / "experiment_results"
    out_dir.mkdir(exist_ok=True)

    print("=" * 60)
    print("S5_Shot3 chain bg 이중 묘사 가설 검증")
    print("=" * 60)
    print(f"Model: {MODEL}")
    print(f"References ({len(INPUT_IMAGES)}):")
    for i, (label, img) in enumerate(INPUT_IMAGES, 1):
        print(f"  [{i}] {label[:60]}... ({len(img):,} bytes)")
    print()

    # A: 기존 prompt
    print("--- A: 기존 prompt (재묘사 포함) ---")
    print(f"prompt length: {len(PROMPT_A_ORIGINAL)} chars")
    a_path = out_dir / "s5shot3_A_original.png"
    try:
        a_bytes = gemini_generate(api_key, PROMPT_A_ORIGINAL, INPUT_IMAGES)
        a_path.write_bytes(a_bytes)
        print(f"OK: {a_path} ({len(a_bytes):,} bytes)")
    except Exception as exc:
        print(f"FAIL: {exc}")
        a_path = None
    print()

    # B: 배경 안내 추가
    print("--- B: 기존 + [배경 참조 안내] 섹션 ---")
    print(f"prompt length: {len(PROMPT_B_WITH_NOTE)} chars")
    b_path = out_dir / "s5shot3_B_with_bg_note.png"
    try:
        b_bytes = gemini_generate(api_key, PROMPT_B_WITH_NOTE, INPUT_IMAGES)
        b_path.write_bytes(b_bytes)
        print(f"OK: {b_path} ({len(b_bytes):,} bytes)")
    except Exception as exc:
        print(f"FAIL: {exc}")
        b_path = None
    print()

    # metadata
    meta = {
        "test": "S5_Shot3 chain bg double-render verification",
        "project_id": PID,
        "episode_id": EID,
        "model": MODEL,
        "aspect_ratio": ASPECT_RATIO,
        "references": [
            {"label": "chain bg", "path": str(CHAIN_BG)},
            {"label": "C04 face", "path": str(C04_FACE_REF)},
            {"label": "P06 prop", "path": str(P06_REF)},
        ],
        "variants": {
            "A_original": {
                "prompt": PROMPT_A_ORIGINAL,
                "result_path": str(a_path) if a_path else None,
            },
            "B_with_bg_note": {
                "prompt": PROMPT_B_WITH_NOTE,
                "result_path": str(b_path) if b_path else None,
                "added_section": BG_NOTE,
            },
        },
    }
    meta_path = out_dir / "s5shot3_metadata.json"
    meta_path.write_text(
        json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    print(f"metadata: {meta_path}")
    print()
    print("비교 항목:")
    print("  1. TV 이중 묘사: A vs B에서 TV 영역 변형/이중 가구 발생 여부")
    print("  2. 창문/싱크대 위치: chain bg 그대로 유지 여부")
    print("  3. 인물 자세 + 시선 + 김 + 냄비: 두 버전 모두 정상 합성")
    print()
    print("원본 chain bg + production scene 이미지:")
    print(f"  chain bg: {CHAIN_BG}")
    print(
        f"  production: {PROJECT_ROOT}/projects/{PID}/images/{EID}/scene/"
        "cb2a0a73-96a7-43d2-b5f4-796f3b66ce6e.png"
    )


if __name__ == "__main__":
    main()
