"""
Runway ML Video Provider – converts images to short video clips.
"""

from __future__ import annotations

import asyncio
import logging
import os
import time
from collections.abc import Callable
from pathlib import Path
from typing import Any

import httpx

from providers.ports import VideoProvider

logger = logging.getLogger(__name__)

RUNWAY_API_BASE = "https://api.dev.runwayml.com/v1"


class RunwayVideoProvider(VideoProvider):
    """Wraps the Runway ML REST API for image-to-video generation."""

    name = "runway"

    def __init__(self):
        self.api_key = os.getenv("RUNWAY_API_KEY")
        if not self.api_key:
            raise EnvironmentError(
                "RUNWAY_API_KEY is required for video generation. "
                "Set it in your .env file."
            )
        self.model = os.getenv("RUNWAY_MODEL", "gen3a_turbo")
        self._headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "Accept": "application/json",
        }

    def _generate_sync(
        self,
        image_url: str,
        motion_prompt: str,
        output_path: str | Path,
        duration: int = 5,
        poll_interval: int = 10,
        max_wait: int = 300,
    ) -> Path:
        """Create a video from *image_url* + *motion_prompt* and save it."""
        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)

        logger.info(
            "Creating video task  model=%s  duration=%ds → %s",
            self.model,
            duration,
            output_path,
        )

        # 1. Start generation task
        payload = {
            "model": self.model,
            "promptImage": image_url,
            "promptText": motion_prompt,
            "duration": duration,
        }
        resp = httpx.post(
            f"{RUNWAY_API_BASE}/image_to_video",
            headers=self._headers,
            json=payload,
            timeout=30,
        )
        resp.raise_for_status()
        task_id = resp.json()["id"]
        logger.info("Video task created: %s", task_id)

        # 2. Poll for completion
        elapsed = 0
        while elapsed < max_wait:
            time.sleep(poll_interval)
            elapsed += poll_interval
            status_resp = httpx.get(
                f"{RUNWAY_API_BASE}/tasks/{task_id}",
                headers=self._headers,
                timeout=30,
            )
            status_resp.raise_for_status()
            data = status_resp.json()
            status = data.get("status", "UNKNOWN")
            logger.debug("Task %s status: %s (%ds elapsed)", task_id, status, elapsed)

            if status == "SUCCEEDED":
                video_url = data["output"][0]
                break
            if status in ("FAILED", "CANCELLED"):
                raise RuntimeError(
                    f"Runway task {task_id} ended with status {status}: "
                    f"{data.get('error', 'unknown error')}"
                )
        else:
            raise TimeoutError(
                f"Runway task {task_id} did not complete within {max_wait}s"
            )

        # 3. Download video
        video_data = httpx.get(video_url, timeout=120).content
        with open(output_path, "wb") as f:
            f.write(video_data)

        logger.info("Video saved: %s", output_path)
        return output_path

    async def generate(
        self,
        image_path: Path,
        video_prompt: str,
        output_path: Path,
        on_progress: Callable[[int], None] | None = None,
    ) -> Path:
        """Async wrapper for image-to-video generation.
        
        Maps ABC signature to internal sync method.
        Note: image_path is Path but passed as str to _generate_sync.
        """
        return await asyncio.to_thread(
            self._generate_sync,
            str(image_path),
            video_prompt,
            output_path,
        )
