from __future__ import annotations

import time
from pathlib import Path

from openai import OpenAI
from tenacity import retry, stop_after_attempt, wait_exponential


class SoraVideoGenerator:
    """OpenAI Sora (Videos API) — original short-form marketing clips.

    Generates fully new motion footage from a storyboard prompt.
    Does NOT animate static images. Audio is intentionally omitted
    (see src/generators/audio.py for a future modular add-on).
    """

    def __init__(
        self,
        api_key: str,
        model: str = "sora-2",
        *,
        default_seconds: str = "12",
        poll_interval: float = 5.0,
        timeout: float = 900.0,
    ):
        self.client = OpenAI(api_key=api_key)
        self.model = model
        self.default_seconds = str(default_seconds)
        self.poll_interval = poll_interval
        self.timeout = timeout

    def build_prompt(
        self,
        *,
        hook: str,
        topic: str,
        why_it_matters: str,
        key_fact: str,
        creative_direction: str,
        video_style: str,
        news_category: str,
        beats: list[dict] | None = None,
    ) -> str:
        """Compress cinematic reel storyboard into one Sora prompt."""
        beat_lines = []
        if beats:
            for b in beats:
                beat_lines.append(
                    f"- {b.get('beat', 'scene')}: on-screen feel '{b.get('on_screen', '')}'; "
                    f"visual {b.get('visual') or b.get('voiceover') or ''}; "
                    f"camera/motion {b.get('motion', 'cinematic move')}"
                )
        beats_block = "\n".join(beat_lines) if beat_lines else (
            f"- hook: {hook}\n"
            f"- news: {topic}\n"
            f"- why it matters: {why_it_matters}\n"
            f"- key fact: {key_fact}\n"
            f"- ending CTA energy: follow for daily tech drops"
        )

        return (
            "Premium vertical short-form marketing reel for an AI/tech media brand "
            "(Instagram Reels / TikTok / YouTube Shorts quality). "
            "Cinematic, original motion footage — NOT a slideshow, NOT animating a still poster, "
            "NOT PowerPoint. Multiple dynamic scenes with camera moves, parallax, light streaks, "
            "kinetic energy, product/UI showcases, futuristic AI environments. "
            "NO readable text overlays, NO real company logos, NO watermarks, NO voiceover requirement. "
            f"Creative direction: {creative_direction or video_style}. "
            f"Visual genre: {news_category}. Style: {video_style}. "
            f"Narrative arc:\n{beats_block}\n"
            "Pacing: punchy hook first 2 seconds, then explanatory visuals, then impactful ending. "
            "Photoreal + high-end motion graphics hybrid, rich lighting, depth, professional color grade."
        )[:4000]

    def size_for_aspect(self, aspect: str = "portrait") -> str:
        # sora-2 common sizes
        if aspect == "landscape":
            return "1280x720"
        return "720x1280"  # vertical social default

    @retry(stop=stop_after_attempt(2), wait=wait_exponential(min=3, max=20), reraise=True)
    def generate(
        self,
        prompt: str,
        out_path: Path,
        *,
        seconds: str | None = None,
        size: str | None = None,
        aspect: str = "portrait",
    ) -> dict:
        out_path.parent.mkdir(parents=True, exist_ok=True)
        secs = seconds or self.default_seconds
        if secs not in {"4", "8", "12"}:
            secs = "12"
        sz = size or self.size_for_aspect(aspect)

        video = self.client.videos.create(
            model=self.model,
            prompt=prompt[:4000],
            seconds=secs,
            size=sz,
        )
        video_id = video.id
        status = getattr(video, "status", "queued")
        started = time.time()

        while status not in {"completed", "failed"}:
            if time.time() - started > self.timeout:
                raise TimeoutError(f"Sora job {video_id} timed out after {self.timeout}s")
            time.sleep(self.poll_interval)
            video = self.client.videos.retrieve(video_id)
            status = getattr(video, "status", "unknown")

        if status != "completed":
            err = getattr(video, "error", None)
            raise RuntimeError(f"Sora generation failed: {err or status}")

        # Download MP4 bytes
        content = self.client.videos.download_content(video_id)
        data = content.read() if hasattr(content, "read") else bytes(content)
        out_path = out_path.with_suffix(".mp4")
        out_path.write_bytes(data)

        return {
            "path": out_path,
            "video_id": video_id,
            "model": self.model,
            "seconds": secs,
            "size": sz,
            "status": status,
        }
