"""S5_Shot3 chain bg — floor plan v2 (TV ↔ 소파 마주봄, variant E).

이전 도면(00_floorplan.png)의 결함:
  - "Left wall: TV ... a fabric sofa parallel to the wall facing the TV"
    → gpt-image-2가 'TV + 소파 둘 다 left wall에 붙임'으로 해석
  - 결과: 도면 자체가 이상 + chain bg에서 TV가 싱크대 옆으로 이동

v2 fix — 명확하게 분리:
  - 좌측 벽: TV만 (벽에 붙음, 화면이 오른쪽 향함)
  - 우측 벽: 소파만 (벽에 붙음, 좌측 향함)
  - 즉 TV ↔ 소파는 방 가운데를 사이에 두고 마주봄
  - 부엌(싱크대+창)은 뒤쪽 벽 (back wall)
  - 식탁은 뒤쪽 벽 가까이 또는 가운데

Step E1: v2 도면 생성
Step E2: v2 도면 기반 chain bg 재생성
Step E3: 새 chain bg + v2 도면 + 인물/소품 + 기존 prompt(A) → variant E
"""
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

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

from openai import OpenAI


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

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"
)

OUT_DIR = Path(__file__).parent / "experiment_results"

# Step E1: v2 floor plan prompt — TV/소파 명확 분리
FLOORPLAN_V2_PROMPT = (
    "Top-down architectural floor plan diagram, hand-drawn black ink lines on "
    "off-white paper, single small Korean rooftop apartment (옥탑방) interior "
    "viewed directly from above. Single rectangular room, roughly 5m wide x "
    "5m deep. NO interior walls dividing the room. "
    "Label and place each item EXACTLY where described — do not combine items "
    "on the same wall: "
    "(1) LEFT WALL: ONE television on a low stand, drawn as a small rectangle "
    "against the left wall, screen facing rightward into the room. Label: "
    "'TV'. Nothing else on the left wall. "
    "(2) RIGHT WALL: ONE fabric sofa drawn as a long rectangle against the "
    "right wall, with seat side facing leftward into the room. Label: 'SOFA'. "
    "The TV and the sofa are on OPPOSITE walls and face each other across the "
    "empty middle of the room. "
    "(3) BACK WALL (top edge of plan): kitchen counter running along the back "
    "wall. In the middle of this counter, draw a sink (square with faucet "
    "symbol). Label: 'SINK'. To the right of the sink along the same back "
    "wall, draw a small window with curtain symbol. Label: 'WINDOW'. At the "
    "far back-right corner of the room, draw a small interior doorway opening "
    "to a tiny adjacent space. Label: 'BEDROOM DOOR'. "
    "(4) FRONT WALL (bottom edge of plan, where the camera enters): in the "
    "middle of the front wall, draw the front entry door swinging outward "
    "(arc symbol showing swing). Label: 'FRONT DOOR'. "
    "(5) BETWEEN SOFA AND SINK (in the open floor area, NOT against any wall): "
    "draw a simple wooden dining table with two chairs. Label: 'TABLE'. "
    "Floor surface: wooden floorboards indicated by light parallel line "
    "hatching across the entire room. "
    "Style: clean schematic architectural diagram, plain black ink linework "
    "on warm off-white background, English labels in small caps, no "
    "perspective, no shading, no people, no furniture grouped together — "
    "each item must be clearly separated and clearly placed against the wall "
    "described."
)

# Step E2: v2 도면 기반 chain bg 재생성 prompt
CHAIN_BG_V2_PROMPT = (
    "Photorealistic cinematic interior background. Convert the architectural "
    "floor plan reference (top-down diagram) into a 35mm film-style photoreal "
    "view from inside the room, eye-level wide angle, single static frame, "
    "no people. Camera is positioned NEAR THE FRONT-LEFT CORNER looking "
    "diagonally toward the back-right corner so that BOTH the left wall (with "
    "the TV) and the back wall (with the sink and window) are clearly visible "
    "in the frame, and the right side shows the sofa across the room and the "
    "dining table. "
    "Match the floor plan layout EXACTLY: "
    "ONE television on a low wooden stand against the left wall, screen "
    "facing into the room (toward the sofa across the empty floor). "
    "ONE fabric sofa against the right wall, facing leftward toward the TV. "
    "Kitchen counter with stainless sink in the middle of the back wall, a "
    "small window with curtain to the right of the sink along the same back "
    "wall. A narrow plain interior doorway (closed) at the back-right corner "
    "leading to a tiny adjoining bedroom space (do NOT show its inside). "
    "A simple wooden dining table with two chairs in the middle of the floor "
    "between the sofa and the kitchen counter. "
    "Front entry door is offscreen behind the camera, ignore it. "
    "This is ONE single rooftop room (옥탑방), NOT a multi-room apartment. "
    "DRAW ONLY ONE TV (do NOT add a second TV anywhere). "
    "Material/lighting: dirty plaster walls in muted yellow-gray, worn wooden "
    "floorboards across the whole room, low ceiling with a single bare bulb "
    "fixture, pale daylight leaking through the small back window, soft warm "
    "tungsten ambient light filling the rest, dust in the air, damp domestic "
    "shadows, uneasy stillness, 35mm film grain, muted blue-gray and washed "
    "yellow palette."
)

# Step E3: 기존 prompt (A 그대로 — 안내 X)
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."
)

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


def gemini_generate(api_key: str, prompt: str, input_images: list) -> bytes:
    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=GEMINI_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 = 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 = 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 from exc
        except (urllib.error.URLError, socket.timeout) as exc:
            last = exc
            if attempt <= MAX_RETRIES:
                time.sleep(2 * attempt); continue
            raise RuntimeError(f"URL error: {exc}") from exc
    else:
        raise RuntimeError(f"Failed: {last}")
    pf = payload.get("promptFeedback", {})
    if pf.get("blockReason"):
        raise RuntimeError(f"Moderation: {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)[:300]}")


def main():
    openai_key = os.environ.get("OPENAI_API_KEY", "")
    gemini_key = os.environ.get("GEMINI_API_KEY", "")
    if not openai_key or not gemini_key:
        print("ERROR: OPENAI_API_KEY + GEMINI_API_KEY required", file=sys.stderr)
        sys.exit(1)

    OUT_DIR.mkdir(exist_ok=True)
    client = OpenAI(api_key=openai_key)

    # ── Step E1: v2 도면 생성 ──
    print("=" * 60)
    print("Step E1: floor plan v2 (gpt-image-2 text-to-image)")
    print("=" * 60)
    print(f"prompt length: {len(FLOORPLAN_V2_PROMPT)} chars")
    floorplan_v2_path = OUT_DIR / "20_floorplan_v2.png"
    if floorplan_v2_path.exists():
        print(f"이미 존재 — 재사용: {floorplan_v2_path}")
    else:
        resp = client.images.generate(
            model="gpt-image-2",
            prompt=FLOORPLAN_V2_PROMPT,
            size="1024x1024",
            quality="high",
            n=1,
        )
        b64 = resp.data[0].b64_json
        floorplan_v2_path.write_bytes(base64.b64decode(b64))
        print(f"OK: {floorplan_v2_path} ({floorplan_v2_path.stat().st_size:,} bytes)")
    print()

    # ── Step E2: v2 도면 기반 chain bg 재생성 ──
    print("=" * 60)
    print("Step E2: chain bg v2 (gpt-image-2 + v2 도면 ref)")
    print("=" * 60)
    print(f"prompt length: {len(CHAIN_BG_V2_PROMPT)} chars")
    new_chain_bg_v2_path = OUT_DIR / "21_new_chain_bg_v2.png"
    if new_chain_bg_v2_path.exists():
        print(f"이미 존재 — 재사용: {new_chain_bg_v2_path}")
    else:
        with open(floorplan_v2_path, "rb") as f:
            resp = client.images.edit(
                model="gpt-image-2",
                image=f,
                prompt=CHAIN_BG_V2_PROMPT,
                size="1536x1024",
                quality="high",
                n=1,
            )
        b64 = resp.data[0].b64_json
        new_chain_bg_v2_path.write_bytes(base64.b64decode(b64))
        print(f"OK: {new_chain_bg_v2_path} ({new_chain_bg_v2_path.stat().st_size:,} bytes)")
    print()

    # ── Step E3: variant E — 새 chain bg + v2 도면 + 인물/소품 + 기존 prompt(A) ──
    print("=" * 60)
    print("Step E3: variant E — chain bg v2 + 도면 v2 + 인물/소품 + prompt A")
    print("=" * 60)
    input_images = [
        (
            "spatial layout reference (floor plan, top-down) — "
            "TV on left wall, sofa on right wall, they FACE EACH OTHER across "
            "the empty middle of the room",
            floorplan_v2_path.read_bytes(),
        ),
        (
            "background reference (rebuilt photoreal interior matching the "
            "floor plan) — TV against left wall, sofa against right wall, "
            "kitchen and window on back wall; reuse all furniture positions "
            "as drawn here",
            new_chain_bg_v2_path.read_bytes(),
        ),
        ("character C04 identity", C04_FACE_REF.read_bytes()),
        ("object P06", P06_REF.read_bytes()),
    ]
    print(f"References (4):")
    for i, (label, _) in enumerate(input_images, 1):
        print(f"  [{i}] {label[:80]}...")
    print(f"prompt length: {len(PROMPT_A_ORIGINAL)} chars (A 그대로)")
    print()

    e_path = OUT_DIR / "s5shot3_E_floorplan_v2.png"
    try:
        e_bytes = gemini_generate(gemini_key, PROMPT_A_ORIGINAL, input_images)
        e_path.write_bytes(e_bytes)
        print(f"OK: {e_path} ({len(e_bytes):,} bytes)")
    except Exception as exc:
        print(f"FAIL: {exc}")
        e_path = None
    print()

    meta = {
        "test": "S5_Shot3 — floor plan v2 (TV/sofa opposite walls), variant E",
        "step1_floor_plan_v2": {
            "model": "gpt-image-2",
            "prompt": FLOORPLAN_V2_PROMPT,
            "result_path": str(floorplan_v2_path),
        },
        "step2_chain_bg_v2": {
            "model": "gpt-image-2",
            "input_ref": str(floorplan_v2_path),
            "prompt": CHAIN_BG_V2_PROMPT,
            "result_path": str(new_chain_bg_v2_path),
        },
        "step3_variant_E": {
            "model": GEMINI_MODEL,
            "references": [
                "20_floorplan_v2.png",
                "21_new_chain_bg_v2.png",
                "C04 face",
                "P06 prop",
            ],
            "prompt": PROMPT_A_ORIGINAL,
            "result_path": str(e_path) if e_path else None,
        },
    }
    (OUT_DIR / "s5shot3_floorplan_v2_metadata.json").write_text(
        json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    print(f"metadata: {OUT_DIR / 's5shot3_floorplan_v2_metadata.json'}")


if __name__ == "__main__":
    main()
