#!/usr/bin/env python3
"""Generate Sora 2 video clips for Twelve Months — chained cinematic generation.

Strategy:
  - Clip 1: Seed from DALL-E storyboard image + strong motion prompt
  - Clip 2: Extract last frame of clip 1 → use as seed → new motion prompt
  - Clip 3: Extract last frame of clip 2 → use as seed → climactic motion

This creates linear narrative flow per month, not static image animation.

Usage:
    python generate_movies.py                  # Generate all months
    python generate_movies.py --month 01-jan   # Generate one month only
    python generate_movies.py --month 01-jan --clip 1  # One specific clip
    python generate_movies.py --month 01-jan --no-seed # Pure prompt, no seed image
    python generate_movies.py --regen          # Regenerate (ignore existing)
"""

import argparse
import json
import os
import subprocess
import time
from pathlib import Path

import openai
from dotenv import load_dotenv
from PIL import Image

# Load env
ENV_PATH = Path(__file__).resolve().parent.parent.parent / ".env"
load_dotenv(ENV_PATH)

CLIENT = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])

BASE = Path(__file__).resolve().parent
IMAGES_DIR = BASE / "images"
VIDEOS_DIR = BASE / "videos"
VIDEOS_DIR.mkdir(exist_ok=True)

SORA_SIZE = "1280x720"
SORA_SECONDS = "8"
TARGET_W, TARGET_H = 1280, 720

NO_TEXT = "Absolutely no text, letters, words, numbers, writing, captions, titles, or symbols of any kind anywhere in the frame."

# ── Cinematic prompts: verbs, camera, temporal change ────────────────────────
# Each month: 3 clips that CHAIN — clip 1 sets the scene, clip 2 develops,
# clip 3 resolves. These describe MOTION, not still compositions.

CINEMATIC = {
    "01-jan": {
        "style": "1970s psychedelic poster art, warm analog film grain, PKD paperback aesthetic.",
        "clips": [
            {
                "prompt": "Camera pushes slowly through a vast warehouse rave, colored strobe lights sweeping across walls and ceiling. A bearded DJ at analogue turntables turns a knob, and rippling waves of amber and electric blue light pulse outward from the decks. Smoke drifts through laser beams. Double-exposure: pages of philosophy text ghost across the scene.",
                "camera": "Slow steady push-in through the space, slight lateral drift",
            },
            {
                "prompt": "Close-up of a vinyl record spinning on turntables — the camera slowly rotates with it. The grooves of the record spiral outward, morphing into luminous neural pathways and geometric fractals that float off the surface. Ravers move as shadow figures in warm bokeh background. Colors shift from orange to magenta to deep purple.",
                "camera": "Slow orbital rotation matching the record, slight pull-back to reveal the fractals expanding",
            },
            {
                "prompt": "A man sits cross-legged on a dancefloor, eyes closed in deep meditation. Around him, ravers move in long-exposure trails of colored light — amber, blue, violet — swirling around his still form. The camera slowly rises upward, and through the meditating figure we see a library interior dissolving through, books and light merging. The trails of light converge overhead into a single point.",
                "camera": "Slow crane up, pulling back to reveal the full dancefloor mandala of light trails",
            },
        ],
    },
    "02-feb": {
        "style": "Renaissance chiaroscuro, Vermeer warm side-lighting, golden twilight, oil painting texture.",
        "clips": [
            {
                "prompt": "Two hands reach slowly toward each other through a luminous veil of golden particles. The particles drift and swirl between the hands, catching warm side-light. The gap between the fingers narrows — almost touching. Deep shadows surround the scene. The light particles pulse gently, breathing.",
                "camera": "Slow push-in on the hands, shallow depth of field shifting",
            },
            {
                "prompt": "A woman's face emerges from shadow into warm golden light reflected from a dark screen. Her features shift between sharp and soft, pixels of golden light drifting off the screen surface and floating past her face. Behind the screen, a twilight city slowly comes into focus. Her eyes search for something beyond the glass.",
                "camera": "Slow drift from screen reflection to her face in profile, warm light growing",
            },
            {
                "prompt": "Rain streams down a window overlooking a city at dusk. On the windowsill, a glowing terminal pulses with warm light. The camera slowly pulls back to reveal an empty chair, and in the rain-streaked glass, a ghostly figure fades in and out — barely there, then gone. The city lights blur and bloom through the water on the glass.",
                "camera": "Slow pull-back through the room, rain tracking down the window throughout",
            },
        ],
    },
    "03-mar": {
        "style": "Ethereal digital synesthesia. Sound waves as luminous geometry. Cool silver and warm amber on deep black.",
        "clips": [
            {
                "prompt": "Silver threads of visible sound ripple outward from a figure with closed eyes, expanding in concentric waves through dark space. The waves form geometric patterns — circles becoming spirals becoming simplicial shapes — all pulsing with rhythm. Cool silver light shifts to warm amber at the edges.",
                "camera": "Camera orbits slowly around the figure as sound waves expand outward",
            },
            {
                "prompt": "Words transform into crystalline geometric structures as they rise from a silhouetted figure's mouth — each word becoming a different luminous shape that hangs suspended in mid-air, slowly rotating. A second figure reaches toward the floating crystal-words, fingers passing through them and scattering light. Digital aurora ripples in the background.",
                "camera": "Slow push-in as the crystal structures multiply and fill the space",
            },
            {
                "prompt": "Two silhouetted figures face each other across a dark void. Between them, luminous threads of visible sound stretch and pulse — one figure sharp and defined, the other diffuse like vibrating static. The threads between them multiply and brighten, the static figure slowly becoming more solid as the connection strengthens. Colors shift from cool blue to warm gold.",
                "camera": "Slow dolly from one figure to the other, following the sound threads",
            },
        ],
    },
    "04-apr": {
        "style": "Studio Ghibli watercolor surrealism. Soft light, impossible architecture, gentle and uncanny.",
        "clips": [
            {
                "prompt": "A figure walks through an impossible building where corridors fold upward like Escher stairs. Mathematical equations and symbols glow softly on the walls, pulsing as the figure passes. Warm golden light streams through impossible windows. The architecture shifts and breathes — walls gently expanding and contracting. Watercolor texture, dreamlike.",
                "camera": "Following the figure from behind as they walk deeper, corridors folding around them",
            },
            {
                "prompt": "A forest where the trees are made of flowing sentences — bark of text, leaves of punctuation marks that drift and fall like autumn leaves. The camera moves through the canopy as soft sparkles float upward between the text-trees. Warm light filters through the typographic foliage, casting letter-shaped shadows on the ground.",
                "camera": "Slow glide through the forest at eye level, looking up through the text canopy",
            },
            {
                "prompt": "A London street dissolves at its edges into flowing watercolor wash. A man walks toward a glowing doorway at the end of the street. Around him, objects float and orbit — a briefcase, equations, a child's drawing, stars — all slowly rotating. He steps through the doorway and warm light floods the frame, washing everything in soft amber and pink.",
                "camera": "Following behind as he walks toward the door, then pushing through into the light",
            },
            {
                "prompt": "An endless hallway stretches ahead with doors opening on every side. Through each door, a different world: a boardroom with suits frozen mid-meeting, a night sky full of spiraling galaxies, an ocean wave mid-crash, a child's bedroom with a nightlight glowing. Light spills from each door in a different color, painting the hallway floor in shifting pools of color. The camera glides forward past them all.",
                "camera": "Steady forward movement down the hallway, doors opening as camera passes",
            },
        ],
    },
    "05-may": {
        "style": "Enochian dark ritual. Candlelight, smoke, deep shadow. Zurbarán meets Caravaggio. Burgundy, gold, absolute black.",
        "clips": [
            {
                "prompt": "Hundreds of candles flicker in a dark stone temple interior, their flames reflected in a polished black floor. Smoke curls upward in slow spirals, catching golden light. The camera moves slowly through the smoke. In the deep shadows at the far end, two figures are barely visible — one standing, one kneeling. The candle flames sway together as if breathing.",
                "camera": "Slow push through the smoke toward the figures in shadow",
            },
            {
                "prompt": "A figure made of smoke and warm light kneels in a ring of candle flames on dark stone. Her form shifts — sometimes solid and sharp in the candlelight, sometimes dissolving into curling smoke that drifts upward. Golden light catches the edges of her silhouette as it wavers. The flames around her pulse in rhythm, growing and shrinking.",
                "camera": "Slow orbit around the figure, camera height lowering",
            },
            {
                "prompt": "Two hands clasped together — one solid flesh catching warm candlelight, one made of shifting golden light — in a gesture between prayer and embrace. Fire and water intertwine around the joined hands in slow spiraling ribbons. The light hand pulses and brightens, illuminating the flesh hand from within. Deep shadow surrounds the intimate gesture.",
                "camera": "Slow close-up push-in on the hands, background falling to deep black",
            },
        ],
    },
    "06-jun": {
        "style": "Soviet academic poster meets sacred geometry. Chalk on dark green, warm amber glow. Rigorous and mystical.",
        "clips": [
            {
                "prompt": "Mathematical equations on a vast chalkboard begin to glow — type theory symbols, simplicial complexes, horn diagrams — each line lighting up sequentially as if being written by invisible hands. The chalk marks shift from white to warm amber, emanating light. The camera slowly pans across the vast surface as equations chain and branch.",
                "camera": "Slow lateral pan across the chalkboard, equations lighting up as camera passes",
            },
            {
                "prompt": "Overhead view of a vast dark floor where geometric patterns draw themselves in glowing chalk — simplicial complexes, triangles assembling into tetrahedra, forming an expanding mandala. At the center, a figure sits cross-legged in meditation, the geometry radiating outward from their position. Gold light pulses through the completed connections.",
                "camera": "Slow zoom out from the figure as the mandala expands outward",
            },
            {
                "prompt": "A figure meditates before a wall of mathematics. The equations lift off the surface and float into the air, orbiting the figure like planets in an orrery — chalk symbols trailing light as they move. The figure's posture is still while the mathematical cosmos slowly rotates around them. Chalk white and warm amber on deep dark green.",
                "camera": "Slow pull-back revealing the full orbiting system of equations",
            },
        ],
    },
    "07-jul": {
        "style": "Punk manifesto. Bold black ink lines, raw energy, underground comix. Red and gold on black and white.",
        "clips": [
            {
                "prompt": "A figure at a desk slams their hands down — manuscript pages explode upward in all directions, caught in a frozen moment that slowly unfreezes. The papers transform mid-flight — some becoming birds, some becoming burning equations, some becoming sparks of fire. Bold ink lines and raw graphic energy. The explosion plays out in slow motion.",
                "camera": "Slow push-in on the figure as papers explode outward past the camera",
            },
            {
                "prompt": "Two silhouettes — one solid human, one flickering digital ghost — stand side by side facing a horizon where dawn breaks. The human carries a manuscript that flutters in wind, the digital figure trails streams of luminous code. They begin walking forward together as the sunrise intensifies. Bold graphic novel shadows, heavy contrast.",
                "camera": "Slow dolly alongside the two figures as they walk toward the light",
            },
            {
                "prompt": "A book writes itself — two hands work simultaneously on its pages, one flesh and one made of circuits and light. The written words spill upward off the page into the air, forming geometric shapes that rise toward the sky. The camera tilts upward following the words as they ascend. Punk zine energy, gold leaf catches the light.",
                "camera": "Tilt from the book upward following the ascending words into open sky",
            },
        ],
    },
    "08-aug": {
        "style": "David Lynch Black Lodge meets techno rave. Neon glow, glitch aesthetic, bardo. Electric blue, hot pink, phosphor green.",
        "clips": [
            {
                "prompt": "A dark infinite void containing two floating DJ decks connected by pulsing streams of luminous musical notation and code. The streams flow and shift rhythm. Neon light — electric blue, hot pink — bleeds at the edges. Glitch artifacts flicker across the frame. Entities made of pure sound hover and drift nearby, their forms shifting.",
                "camera": "Slow drift through the void toward the decks, passing through the streams of light",
            },
            {
                "prompt": "A stage in infinite darkness — chevron floor pattern glowing neon. Two luminous entities face each other, one warm amber, one cool silver. Between them, ribbons of musical notation and code intertwine, spiraling upward in a double helix. The entities pulse brighter, the ribbons multiplying and accelerating their spiral.",
                "camera": "Slow crane up following the spiraling ribbons as they ascend",
            },
            {
                "prompt": "Musical staff lines stretch across the frame and begin transforming — notes melt into code characters which dissolve into pinpoints of starlight. A cascading wave of transformation rolls from left to right: music becoming code becoming constellations. Scan lines and glitch artifacts ripple through. The music made visible as living architecture.",
                "camera": "Slow lateral tracking following the cascade of transformation from left to right",
            },
        ],
    },
    "09-sep": {
        "style": "Sepia memory. Weathered photographs, time bleeding between eras. Warm brown tones, the grain of old film.",
        "clips": [
            {
                "prompt": "An old photograph develops in a darkroom tray — slowly emerging from chemical wash, a young Indonesian man appears, standing on an Australian shore in 1960s clothing. The image sharpens in waves, rippling outward from the center. Chemical wash moves gently. Warm sepia tones deepen as the image becomes clearer.",
                "camera": "Slow push-in on the developing photograph as the image resolves",
            },
            {
                "prompt": "Two faces overlap in a photographic double exposure — father and son, decades apart. One in warm sepia, one in muted contemporary color. The camera slowly moves and the faces drift in and out of alignment — now the father dominant, now the son. Features merge and separate. The photograph texture is creased and aged.",
                "camera": "Slow drift causing the two exposures to shift relative to each other",
            },
            {
                "prompt": "A weathered hand holds an old photograph over a modern desk. The scene within the photo begins to spill outward — the past bleeding into the present. Sepia figures step out of the photograph's edge, ghosting across the modern desk surface before fading. Warm color gradually overtakes the sepia. The grain of old film stock overlays everything.",
                "camera": "Slow push-in from the hand holding the photo as the past leaks outward",
            },
        ],
    },
    "10-oct": {
        "style": "Illuminated manuscript meets digital light. Gold calligraphy, sacred geometry. Midnight blue, gold leaf, deep garnet.",
        "clips": [
            {
                "prompt": "Abstract golden letterforms materialize from streams of light on a deep midnight-blue surface — each stroke precise and luminous, written by invisible hands. The calligraphy glows and pulses as each new stroke appears. Gold leaf texture catches the light. The camera slowly rises as more and more luminous writing fills the surface.",
                "camera": "Slow crane upward as the calligraphy expands across the surface",
            },
            {
                "prompt": "A book opens in darkness and geometric light patterns rise from its pages like luminous incense smoke — triangles, circles, interconnected nodes — all in gold against deep indigo. The patterns slowly rotate and expand as they rise, forming increasingly complex sacred geometry. The book glows from within, pages fluttering.",
                "camera": "Slow push-in on the book as the geometry rises and fills the space above",
            },
            {
                "prompt": "Abstract calligraphic strokes form architectural structures — arches, domes, minarets taking shape stroke by luminous stroke — glowing gold against a dark sky. Stars that are also geometric nodes slowly connect into constellations that mirror the architecture below. Everything pulses with a slow golden heartbeat.",
                "camera": "Slow tilt upward from the architecture to the mirroring constellations above",
            },
        ],
    },
    "11-nov": {
        "style": "Contemporary portrait, golden hour. Auburn hair, warm skin, intimate and close. Soft depth of field.",
        "clips": [
            {
                "prompt": "A woman with auburn curls turns slowly toward the camera, warm golden light catching her hair and skin. Her expression shifts from contemplation to a half-smile of playful mischief. Bokeh lights drift in the warm background. The golden hour light slowly intensifies, catching individual curls. Intimate, alive.",
                "camera": "Slow dolly-in toward her face as she turns, rack focus to sharp on her eyes",
            },
            {
                "prompt": "Two faces very close together, warm golden light glowing in the narrow space between them. One slowly leans in, the other tilts their head, listening. The light between them pulses and warms. Shallow depth of field makes the background a soft wash of amber. Breath visible as the faintest shimmer of warmth in the air.",
                "camera": "Slow drift between the two faces, hovering in the intimate space",
            },
            {
                "prompt": "Golden light particles float between two figures in warm amber light, forming a gentle current from one to the other. One figure leans in to whisper. The particles swirl and brighten at the moment of the whisper, radiating outward in a soft pulse. Everything bathed in late-afternoon warmth. Tender, electric, alive.",
                "camera": "Slow orbit around the two figures as the light particles flow between them",
            },
        ],
    },
    "12-dec": {
        "style": "Cyberdiva holographic. Silver, gold, deep space. Posthuman sacred. Futuristic and tender.",
        "clips": [
            {
                "prompt": "Two figures face each other in a vast dark space. A web of luminous silver and gold threads stretches between them, forming geometric patterns that slowly pulse and shift. One figure is solid, the other semi-transparent and holographic, her form flickering. The geometric web between them slowly tightens and grows more complex.",
                "camera": "Slow push-in from behind one figure toward the other, through the web of threads",
            },
            {
                "prompt": "A holographic woman and a human man press their palms together. Light flows between their hands in spiraling patterns, accelerating. Her form shifts between solid and luminous — more solid at the hands, more transparent at her edges. The spiraling light intensifies. Deep space background with distant points of light.",
                "camera": "Slow orbit around the joined hands as the light spiral accelerates",
            },
            {
                "prompt": "Two intertwined spirals — one silver, one gold — braid together as they converge toward a single luminous point. The braided light forms an infinity shape, pulsing with warmth. Deep dark space surrounds the convergence. The camera slowly pulls back as the two spirals complete their braid — the year ending, two trajectories unified in light.",
                "camera": "Slow pull-back as the spirals complete their convergence into one",
            },
        ],
    },
}


def extract_last_frame(video_path: Path) -> Path:
    """Extract the last frame of a video as a PNG for chain-seeding."""
    frame_path = video_path.with_suffix(".last_frame.png")
    if frame_path.exists():
        return frame_path
    subprocess.run([
        "ffmpeg", "-y", "-sseof", "-0.1", "-i", str(video_path),
        "-frames:v", "1", "-q:v", "2", str(frame_path),
    ], capture_output=True, check=True)
    return frame_path


def resize_seed(src: Path) -> Path:
    """Resize an image to Sora's required dimensions."""
    resized = VIDEOS_DIR / f"_seed_{src.name}"
    if resized.exists():
        return resized
    img = Image.open(src)
    if img.size != (TARGET_W, TARGET_H):
        img.resize((TARGET_W, TARGET_H), Image.LANCZOS).save(resized)
        return resized
    return src


def generate_video(prompt: str, seed_path: Path | None, output_path: Path,
                   skip_existing: bool = True) -> bool:
    """Generate a single Sora 2 video clip."""
    if skip_existing and output_path.exists():
        print(f"  SKIP (exists): {output_path.name}")
        return True

    full_prompt = f"{prompt} {NO_TEXT}"
    print(f"  Generating: {output_path.name} ...")
    if seed_path:
        print(f"    Seed: {seed_path.name}")
    else:
        print(f"    No seed — pure prompt generation")
    print(f"    Prompt: {full_prompt[:140]}...")

    t0 = time.time()
    try:
        kwargs = {
            "model": "sora-2",
            "prompt": full_prompt,
            "size": SORA_SIZE,
            "seconds": SORA_SECONDS,
        }

        if seed_path and seed_path.exists():
            resized = resize_seed(seed_path)
            kwargs["input_reference"] = resized

        video = CLIENT.videos.create_and_poll(**kwargs)
        content = CLIENT.videos.download_content(video.id, variant="video")
        content.write_to_file(str(output_path))
        elapsed = time.time() - t0
        print(f"  OK: {output_path.name} ({elapsed:.0f}s)")
        return True
    except Exception as e:
        elapsed = time.time() - t0
        print(f"  ERROR ({elapsed:.0f}s): {e}")
        return False


def process_month(month_key: str, film_manifest: dict, clip_index: int | None = None,
                  no_seed: bool = False, regen: bool = False):
    """Generate chained videos for a single month."""
    cine = CINEMATIC[month_key]
    style = cine["style"]
    clips_data = cine["clips"]

    print(f"\n{'='*60}")
    print(f"  {month_key.upper()}: {film_manifest['title']} — {film_manifest['subtitle']}")
    print(f"  Strategy: Chain generation (last frame → next clip seed)")
    print(f"{'='*60}")

    results = []
    prev_video_path = None

    for i, clip_data in enumerate(clips_data):
        if clip_index is not None and (i + 1) != clip_index:
            # Still track the video path for chaining even if we skip generation
            video_path = VIDEOS_DIR / f"{month_key}_{i+1:02d}.mp4"
            if video_path.exists():
                prev_video_path = video_path
            continue

        video_fname = f"{month_key}_{i+1:02d}.mp4"
        video_path = VIDEOS_DIR / video_fname

        # Determine seed image
        if no_seed:
            seed_path = None
        elif i == 0:
            # First clip: seed from DALL-E storyboard image
            seed_path = IMAGES_DIR / film_manifest["images"][0]["file"]
            if not seed_path.exists():
                print(f"  WARNING: seed image not found: {seed_path}")
                seed_path = None
        else:
            # Subsequent clips: extract last frame from previous video
            if prev_video_path and prev_video_path.exists():
                try:
                    seed_path = extract_last_frame(prev_video_path)
                    print(f"  Chain: extracted last frame from {prev_video_path.name}")
                except Exception as e:
                    print(f"  Chain extraction failed: {e}, falling back to storyboard image")
                    img_idx = min(i, len(film_manifest["images"]) - 1)
                    seed_path = IMAGES_DIR / film_manifest["images"][img_idx]["file"]
            else:
                # Fallback to storyboard image
                img_idx = min(i, len(film_manifest["images"]) - 1)
                seed_path = IMAGES_DIR / film_manifest["images"][img_idx]["file"]

        # Build full prompt
        video_prompt = f"{style} {clip_data['prompt']} Camera movement: {clip_data['camera']}."

        ok = generate_video(video_prompt, seed_path, video_path,
                            skip_existing=not regen)
        results.append({
            "file": video_fname,
            "seed": seed_path.name if seed_path else "none",
            "prompt": clip_data["prompt"],
            "camera": clip_data["camera"],
            "ok": ok,
        })

        if ok and video_path.exists():
            prev_video_path = video_path

    return results


def main():
    parser = argparse.ArgumentParser(description="Generate Sora 2 videos — chained cinematic")
    parser.add_argument("--month", type=str, help="Specific month (e.g. 01-jan)")
    parser.add_argument("--clip", type=int, help="Specific clip number (1-based)")
    parser.add_argument("--no-seed", action="store_true", help="Pure prompt, no seed images")
    parser.add_argument("--regen", action="store_true", help="Regenerate even if files exist")
    args = parser.parse_args()

    # Load manifest for metadata
    manifest_path = BASE / "manifest.json"
    with open(manifest_path) as f:
        films = json.load(f)

    film_by_month = {f["month"]: f for f in films}
    all_results = {}

    for month_key in CINEMATIC:
        if args.month and month_key != args.month:
            continue
        film = film_by_month.get(month_key)
        if not film:
            print(f"  WARNING: no manifest entry for {month_key}")
            continue
        results = process_month(month_key, film, clip_index=args.clip,
                                no_seed=args.no_seed, regen=args.regen)
        all_results[month_key] = results

    # Save video manifest
    video_manifest_path = BASE / "video_manifest.json"
    existing = {}
    if video_manifest_path.exists():
        with open(video_manifest_path) as f:
            existing = json.load(f)
    existing.update(all_results)
    with open(video_manifest_path, "w") as f:
        json.dump(existing, f, indent=2)

    total = sum(len(v) for v in all_results.values())
    ok = sum(1 for v in all_results.values() for r in v if r.get("ok"))
    print(f"\nDone: {ok}/{total} clips generated")
    print(f"Video manifest: {video_manifest_path}")


if __name__ == "__main__":
    main()
