"""Spike Test 2 (Variant B) — SVG XML in prompt (no schematic PNG ref).

가설: SVG XML 텍스트(좌표 명시)를 prompt에 넣으면 모델이 layout을 텍스트로도 이해하는가?

비교:
  variant E/F (검증됨): floor plan PNG + chain bg prompt → 정확한 chain bg
  variant B (이 실험):  SVG XML in prompt (no PNG) → ?

대상 chain bg 노드: interior_living_kitchen_day_normal (variant F의 chain bg #1과 동일)
모델: gpt-image-2 (text-to-image) + Gemini nano-banana-2 (text-to-image)
"""
from __future__ import annotations

import base64
import concurrent.futures
import json
import os
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any, Dict, List, Tuple

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

from openai import OpenAI


HERE = Path(__file__).parent
RESULTS = HERE / "results"
RESULTS.mkdir(parents=True, exist_ok=True)


# ───────── chain bg prompt (v3 v3 거실 day normal) ─────────

CHAIN_BG_PROMPT = (
    "Photorealistic cinematic interior background of a Korean two-room "
    "rooftop apartment (투룸 옥탑방), pale daylight, no people, "
    "single static frame, 35mm film grain, eye-level wide angle. "
    "Camera is positioned in the LIVING/KITCHEN room near the front entry "
    "side, looking diagonally toward the back where the kitchen counter "
    "and the two interior bedroom doors are visible. "
    "Visible in this view: ONE television on a low wooden stand against "
    "the left wall (single TV — do NOT add a second TV); kitchen counter "
    "with stainless sink in the middle of the back wall; small window with "
    "thin curtain to the right of the sink; wooden dining table with two or "
    "three chairs in the center floor; the closed interior door to the main "
    "bedroom (안방) at the upper-right; the closed interior door to the "
    "daughter's bedroom (수리영 방) on the right wall further forward. "
    "DO NOT show interior of bedrooms or bathroom — only the living/kitchen "
    "room is in this frame. The apartment is one rooftop apartment, NOT a "
    "multi-floor or multi-unit building. "
    "Material/lighting: dirty plaster walls in muted yellow-gray, worn "
    "wooden floorboards, low ceiling with a single bare bulb fixture, pale "
    "daylight from the small back window, soft warm tungsten ambient, dust "
    "in the air, damp domestic shadows, uneasy stillness, muted blue-gray "
    "and washed yellow palette."
)


# ───────── spec.json → SVG XML serializer ─────────

def spec_to_svg_xml(spec: Dict[str, Any]) -> str:
    """spec.json → SVG XML string (top-down floor plan layout).

    좌표는 spec의 logical units (meters). SVG viewBox로 그대로 사용.
    label_en + id 모두 명시 (모델이 의미 인식하도록).
    """
    canvas = spec.get("canvas", {"width": 10, "height": 10})
    cw = float(canvas["width"])
    ch = float(canvas["height"])

    # extend viewBox to include exterior zones
    xmin, ymin, xmax, ymax = 0.0, 0.0, cw, ch
    for ez in spec.get("exterior_adjacency_zones", []):
        for x, y in ez.get("polygon", []):
            xmin = min(xmin, x)
            ymin = min(ymin, y)
            xmax = max(xmax, x)
            ymax = max(ymax, y)
    pad = 0.5
    vb = f"{xmin - pad} {ymin - pad} {xmax - xmin + 2*pad} {ymax - ymin + 2*pad}"

    parts: List[str] = []
    parts.append(
        f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{vb}" '
        f'data-unit="meters" data-view="top_down">'
    )

    # exterior zones
    for ez in spec.get("exterior_adjacency_zones", []):
        poly = ez.get("polygon", [])
        if len(poly) >= 3:
            pts = " ".join(f"{x},{y}" for x, y in poly)
            parts.append(
                f'  <polygon id="{ez["id"]}" data-type="exterior_{ez["type"]}" '
                f'points="{pts}" fill="#d6d3d1" stroke="#78716c" stroke-width="0.05"/>'
            )
            cx = sum(p[0] for p in poly) / len(poly)
            cy = sum(p[1] for p in poly) / len(poly)
            parts.append(
                f'  <text x="{cx}" y="{cy}" text-anchor="middle" '
                f'font-size="0.4">{ez.get("label_en", ez["type"])}</text>'
            )

    # rooms
    for room in spec.get("rooms", []):
        poly = room.get("polygon", [])
        if len(poly) < 3:
            continue
        pts = " ".join(f"{x},{y}" for x, y in poly)
        parts.append(
            f'  <polygon id="room_{room["id"]}" data-room-id="{room["id"]}" '
            f'data-label-en="{room.get("label_en", "")}" '
            f'data-label-ko="{room.get("label_ko", "")}" '
            f'points="{pts}" fill="none" stroke="#1f2937" stroke-width="0.08"/>'
        )

    # doors
    for door in spec.get("doors", []):
        x, y = door.get("position", [0, 0])
        kind = door.get("kind", "interior")
        parts.append(
            f'  <circle id="door_{door["id"]}" data-kind="{kind}" '
            f'data-from="{door.get("from_room","")}" '
            f'data-to="{door.get("to_room","")}" '
            f'cx="{x}" cy="{y}" r="0.15" '
            f'fill="{"#dc2626" if kind == "front_entry" else "#16a34a"}"/>'
        )

    # furniture
    for f in spec.get("furniture", []):
        x, y = f.get("position", [0, 0])
        sw, sh = f.get("size", [0.5, 0.5])
        parts.append(
            f'  <rect id="furn_{f["id"]}" data-type="{f.get("type","")}" '
            f'data-room="{f.get("room","")}" '
            f'data-wall="{f.get("wall","")}" '
            f'data-label-en="{f.get("label_en","")}" '
            f'x="{x - sw/2}" y="{y - sh/2}" width="{sw}" height="{sh}" '
            f'fill="#9ca3af" stroke="#000" stroke-width="0.03"/>'
        )

    parts.append("</svg>")
    return "\n".join(parts)


# ───────── Build variant B prompt ─────────

def build_variant_b_prompt(svg_xml: str) -> str:
    """SVG XML + chain bg prompt 결합."""
    return (
        "[ARCHITECTURAL FLOOR PLAN — top-down SVG, used as authoritative spatial layout]\n"
        "Below is the top-down floor plan of the apartment encoded as SVG. "
        "Use the coordinates and labels to determine the exact positions of "
        "rooms, doors, windows, and furniture. The SVG uses meters as units, "
        "with origin at bottom-left, +x rightward, +y upward (north up).\n\n"
        f"{svg_xml}\n\n"
        "[RENDER INSTRUCTIONS]\n"
        + CHAIN_BG_PROMPT
        + "\n\nMatch the spatial layout described in the SVG above EXACTLY "
        "for furniture positions, door positions, and window positions. "
        "Do not invent additional furniture or doors. Render as a single "
        "photorealistic eye-level cinematic frame from the camera position "
        "described in the prompt above (NOT a top-down view)."
    )


# ───────── gpt-image-2 ─────────

def call_gpt_image_2(prompt: str) -> bytes:
    """gpt-image-2 text-to-image (no ref)."""
    client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
    resp = client.images.generate(
        model="gpt-image-2",
        prompt=prompt,
        size="1536x1024",
        quality="high",
        n=1,
    )
    return base64.b64decode(resp.data[0].b64_json)


# ───────── Gemini nano-banana-2 ─────────

GEMINI_API_URL = (
    "https://generativelanguage.googleapis.com/v1beta/models/"
    "gemini-3.1-flash-image-preview:generateContent?key={key}"
)


def call_gemini_nano_banana(prompt: str) -> bytes:
    """Gemini nano-banana-2 text-to-image."""
    api_key = os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY")
    body = {
        "contents": [{"parts": [{"text": prompt}]}],
        "generationConfig": {
            "responseModalities": ["TEXT", "IMAGE"],
            "imageConfig": {"aspectRatio": "16:9", "imageSize": "2K"},
        },
    }
    url = GEMINI_API_URL.format(key=api_key)

    last_err: Exception | None = None
    for attempt in range(1, 4):
        try:
            req = urllib.request.Request(
                url,
                data=json.dumps(body).encode("utf-8"),
                headers={"Content-Type": "application/json"},
                method="POST",
            )
            with urllib.request.urlopen(req, timeout=600) as resp:
                payload = json.loads(resp.read().decode("utf-8"))
            for part in payload["candidates"][0]["content"]["parts"]:
                if "inline_data" in part:
                    return base64.b64decode(part["inline_data"]["data"])
                if "inlineData" in part:
                    return base64.b64decode(part["inlineData"]["data"])
            raise RuntimeError("no image part in response")
        except urllib.error.HTTPError as exc:
            last_err = RuntimeError(f"{exc.code} {exc.read().decode('utf-8', errors='replace')[:300]}")
            if exc.code in {429, 500, 502, 503, 504} and attempt < 3:
                time.sleep(2 * attempt)
                continue
            raise last_err from exc
        except Exception as exc:
            last_err = exc
            if attempt < 3:
                time.sleep(2 * attempt)
                continue
            raise
    raise RuntimeError(f"Gemini failed after retries: {last_err}")


# ───────── Main ─────────

def main() -> int:
    # 1) spec.json (GPT 결과 사용 — furniture 더 풍부함)
    spec_path = RESULTS / "spec_gpt.json"
    if not spec_path.exists():
        print(f"[ERROR] {spec_path} not found. Run test_01 first.")
        return 1
    spec = json.loads(spec_path.read_text())
    svg_xml = spec_to_svg_xml(spec)
    (RESULTS / "spec_gpt.svg").write_text(svg_xml, encoding="utf-8")
    print(f"[INFO] SVG XML 생성: {len(svg_xml)}자 → spec_gpt.svg")

    # 2) variant B prompt 빌드
    prompt = build_variant_b_prompt(svg_xml)
    (RESULTS / "variant_b_prompt.txt").write_text(prompt, encoding="utf-8")
    print(f"[INFO] variant B prompt: {len(prompt)}자 → variant_b_prompt.txt")

    # 3) 두 모델 병렬 호출
    print("\n[INFO] gpt-image-2 + Gemini nano-banana-2 병렬 호출...")
    with concurrent.futures.ThreadPoolExecutor(max_workers=2) as ex:
        f_gpt = ex.submit(call_gpt_image_2, prompt)
        f_gem = ex.submit(call_gemini_nano_banana, prompt)
        results: Dict[str, Tuple[str, bytes | str]] = {}
        for name, fut in (("gpt-image-2", f_gpt), ("nano-banana-2", f_gem)):
            try:
                img_bytes = fut.result(timeout=600)
                results[name] = ("ok", img_bytes)
            except Exception as exc:
                results[name] = ("error", f"{type(exc).__name__}: {exc}")

    # 4) 저장
    for name, (status, payload) in results.items():
        slug = name.replace("-", "_").replace(".", "")
        if status == "ok":
            out = RESULTS / f"variant_b_{slug}.png"
            out.write_bytes(payload)
            print(f"[OK] {name} → {out} ({len(payload):,} bytes)")
        else:
            err_path = RESULTS / f"variant_b_{slug}_error.txt"
            err_path.write_text(str(payload), encoding="utf-8")
            print(f"[ERROR] {name}: {payload}")

    return 0


if __name__ == "__main__":
    sys.exit(main())
