"""실험 — interior 도면 reference로 (1) 파노라마 1장 + (2) 4분할(N/E/S/W) 1장 생성.

GPT-image-2 단일. 도면 PNG만 reference.

사용:
  python scripts/experiment_panorama_and_quad.py --plan-png <path> --out-dir <dir> [--canon "..."]
"""
from __future__ import annotations

import argparse
import base64
import json
import logging
import os
import sys
from pathlib import Path

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

from dotenv import load_dotenv  # noqa: E402
load_dotenv(BACKEND / ".env")

from openai import OpenAI  # noqa: E402

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("pano_quad")


def compact_canon_text(canon: dict) -> str:
    parts = []
    b = canon.get("building") or {}
    if b:
        bp = []
        for k in ("stories", "primary_material", "exterior_stairs", "rooftop_features",
                  "window_pattern", "weathering"):
            v = b.get(k)
            if v:
                if isinstance(v, list): v = ", ".join(map(str, v))
                bp.append(f"{k}={v}")
        if b.get("color_palette"):
            bp.append("colors=" + ", ".join(map(str, b["color_palette"])))
        if bp: parts.append("Building: " + "; ".join(bp))
    i = canon.get("interior") or {}
    if i:
        ip = []
        for k in ("wall_finish", "floor_finish", "ceiling",
                  "lighting_fixtures", "general_clutter_level"):
            v = i.get(k)
            if v:
                if isinstance(v, list): v = ", ".join(map(str, v))
                ip.append(f"{k}={v}")
        if ip: parts.append("Interior: " + "; ".join(ip))
    return ". ".join(parts).strip()


def panorama_prompt(canon_text: str) -> str:
    return (
        "Photorealistic 360-degree EQUIRECTANGULAR INTERIOR PANORAMA of the room shown in the "
        "architectural floor plan reference image. The output is a single horizontal strip image "
        "where all 4 walls of the room are visible side by side as if the camera spun a full 360 "
        "degrees from the room's center.\n\n"
        "Strict requirements:\n"
        "- Equirectangular panoramic projection (typical for 360 viewers): wide aspect ratio with "
        "natural perspective foreshortening near top/bottom edges, horizontally stitched seamless.\n"
        "- Camera position: standing at the geometric center of the room, eye-level approx 1.6m height.\n"
        "- Reading order LEFT to RIGHT corresponds to camera rotating clockwise: NORTH wall first "
        "(left edge) → EAST wall (left-center) → SOUTH wall (center-right) → WEST wall (right edge), "
        "wrapping back to NORTH at the seam.\n"
        "- Maintain EXACT wall layout, door positions, window positions, and furniture placement "
        "from the floor plan reference. Do not invent walls or furniture.\n"
        "- Photorealistic, 35mm cinematic still aesthetic. Soft natural daylight from windows mixed "
        "with dim domestic practicals (weak floor lamp, faint television glow). Realistic shadows.\n"
        "- Lived-in details: dust motes, scuff marks, subtle wear.\n"
        "- No people, no characters, empty room.\n"
        "- No overlaid text, no diagram lines, no architectural label letters, no captions.\n\n"
        f"Canon: {canon_text or 'aged residential interior, modest domestic clutter'}"
    )


def quad_prompt(canon_text: str) -> str:
    return (
        "Photorealistic 4-QUADRANT COMPOSITE image showing 4 different eye-level camera views of "
        "the SAME interior room (matching the architectural floor plan reference), arranged in a "
        "2x2 grid with thin black dividers between quadrants.\n\n"
        "Layout (2x2 grid):\n"
        "- TOP-LEFT quadrant: camera facing NORTH wall, label 'N' in top-left corner of this quadrant.\n"
        "- TOP-RIGHT quadrant: camera facing EAST wall (90 degrees clockwise from N), label 'E'.\n"
        "- BOTTOM-LEFT quadrant: camera facing WEST wall (270 degrees from N), label 'W'.\n"
        "- BOTTOM-RIGHT quadrant: camera facing SOUTH wall (180 degrees from N), label 'S'.\n\n"
        "Each quadrant must:\n"
        "- Be a distinct camera angle showing different walls/furniture (NOT identical compositions).\n"
        "- Camera at room center, eye-level approx 1.6m height, slight wide angle ~28mm.\n"
        "- Maintain EXACT wall layout, door, window, furniture placement from floor plan reference.\n"
        "- Visual identity (wall finish, floor finish, ceiling, lighting, color palette) "
        "consistent across all 4 quadrants — same room, same lighting, just different angles.\n"
        "- Photorealistic 35mm cinematic still, soft natural daylight + dim practicals.\n"
        "- No people, empty room.\n"
        "- No overlaid text inside quadrants, no diagram lines (other than the grid divider).\n"
        "- Quadrant corner labels (N/E/W/S) in ENGLISH ONLY, small clean sans-serif.\n\n"
        f"Canon: {canon_text or 'aged residential interior, modest domestic clutter'}"
    )


def edit_with_plan(client: OpenAI, model: str, plan_path: Path, prompt: str,
                   size: str, quality: str, out_path: Path) -> Path:
    logger.info("[image edit] %s + plan ref → %s (%s, %s)",
                model, out_path.name, size, quality)
    with open(plan_path, "rb") as f:
        resp = client.images.edit(
            model=model, image=[f], prompt=prompt,
            size=size, quality=quality, n=1,
        )
    b64 = resp.data[0].b64_json
    if not b64:
        raise RuntimeError(f"empty b64 for {out_path.name}")
    out_path.write_bytes(base64.b64decode(b64))
    logger.info("  saved %d KB", out_path.stat().st_size // 1024)
    return out_path


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--plan-png", required=True)
    p.add_argument("--out-dir", required=True)
    p.add_argument("--run-dir", default=None,
                   help="(선택) v4 run dir에서 canon 자동 추출")
    p.add_argument("--canon", default="")
    p.add_argument("--model", default="gpt-image-2")
    p.add_argument("--panorama-size", default="1792x1024",
                   help="파노라마 크기 (가로 긴 비율). 1792x1024 / 2048x1024 등")
    p.add_argument("--quad-size", default="1024x1024",
                   help="4분할 크기 (정사각)")
    p.add_argument("--quality", default="high")
    args = p.parse_args()

    plan_png = Path(args.plan_png).resolve()
    if not plan_png.exists():
        logger.error("plan-png 없음"); return 1
    out_dir = Path(args.out_dir).resolve()
    out_dir.mkdir(parents=True, exist_ok=True)

    canon_text = args.canon
    if not canon_text and args.run_dir:
        sp = Path(args.run_dir) / "step1_spatial.json"
        if sp.exists():
            spatial = json.loads(sp.read_text(encoding="utf-8"))
            canon_text = compact_canon_text(spatial.get("environment_canon") or {})
            logger.info("canon: %d chars", len(canon_text))

    if not os.getenv("OPENAI_API_KEY"):
        logger.error("OPENAI_API_KEY not set"); return 1
    client = OpenAI()

    # 1) Panorama
    pano_p = out_dir / "interior_panorama.png"
    try:
        edit_with_plan(client, args.model, plan_png, panorama_prompt(canon_text),
                       args.panorama_size, args.quality, pano_p)
    except Exception as e:
        logger.error("panorama failed: %s", e)

    # 2) Quad
    quad_p = out_dir / "interior_quad_NESW.png"
    try:
        edit_with_plan(client, args.model, plan_png, quad_prompt(canon_text),
                       args.quad_size, args.quality, quad_p)
    except Exception as e:
        logger.error("quad failed: %s", e)

    # prompts 저장
    (out_dir / "prompts.json").write_text(
        json.dumps({
            "panorama": panorama_prompt(canon_text),
            "quad": quad_prompt(canon_text),
            "canon": canon_text,
        }, ensure_ascii=False, indent=2), encoding="utf-8"
    )
    logger.info("=== DONE === %s", out_dir)
    return 0


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