"""
MoviePy backend for FMUS-VID.

This module provides the MoviePy implementation of the Backend interface,
focusing on animation and compositing capabilities.
"""

import numpy as np
from typing import List, Dict, Tuple, Optional, Any, Union
from pathlib import Path
import logging
from moviepy.editor import VideoFileClip, AudioFileClip, ColorClip, CompositeVideoClip
from moviepy.video.fx.all import resize, rotate, crop, speedx, colorx
from moviepy.video.tools.drawing import circle, color_gradient
from moviepy.video.io.bindings import mplfig_to_npimage
import matplotlib.pyplot as plt

from .backend import Backend

logger = logging.getLogger(__name__)

class MoviePyBackend(Backend):
    """MoviePy implementation of the Backend interface."""

    def __init__(self):
        """Initialize MoviePy backend."""
        self.clips = {}  # Dictionary to store active clips

    @staticmethod
    def is_available() -> bool:
        """Check if MoviePy is available."""
        try:
            import moviepy.editor
            return True
        except ImportError:
            return False

    def load(self, path: Union[str, Path], **kwargs) -> Dict[str, Any]:
        """
        Load a video file using MoviePy.

        Args:
            path: Path to video file
            **kwargs: Additional options
                - start: Start time in seconds
                - end: End time in seconds

        Returns:
            Dictionary with video information
        """
        path = str(path)

        try:
            clip = VideoFileClip(path)

            # Apply time range if specified
            start = kwargs.get("start", 0)
            end = kwargs.get("end")
            if start > 0 or end is not None:
                clip = clip.subclip(start, end)

            # Store clip reference
            clip_id = id(clip)
            self.clips[clip_id] = clip

            return {
                "clip_id": clip_id,
                "duration": clip.duration,
                "size": clip.size,
                "fps": clip.fps,
                "audio": clip.audio is not None
            }
        except Exception as e:
            logger.error(f"Failed to load video: {e}")
            raise

    def create(self, width: int, height: int, duration: float, fps: float,
               color: Tuple[int, int, int], **kwargs) -> Dict[str, Any]:
        """
        Create a blank video using MoviePy.

        Args:
            width: Frame width
            height: Frame height
            duration: Duration in seconds
            fps: Frames per second
            color: Background color (RGB)

        Returns:
            Dictionary with video information
        """
        try:
            clip = ColorClip(size=(width, height), color=color, duration=duration)
            clip = clip.set_fps(fps)

            clip_id = id(clip)
            self.clips[clip_id] = clip

            return {
                "clip_id": clip_id,
                "duration": clip.duration,
                "size": clip.size,
                "fps": clip.fps,
                "audio": False
            }
        except Exception as e:
            logger.error(f"Failed to create video: {e}")
            raise

    def save(self, video: Dict[str, Any], output_path: Union[str, Path],
             **kwargs) -> None:
        """
        Save video using MoviePy.

        Args:
            video: Video dictionary
            output_path: Output file path
            **kwargs: Export options
                - codec: Video codec
                - bitrate: Video bitrate
                - audio_codec: Audio codec
                - audio_bitrate: Audio bitrate
        """
        clip = self.clips[video["clip_id"]]
        output_path = str(output_path)

        try:
            clip.write_videofile(
                output_path,
                codec=kwargs.get("codec", "libx264"),
                bitrate=kwargs.get("bitrate"),
                audio_codec=kwargs.get("audio_codec", "aac"),
                audio_bitrate=kwargs.get("audio_bitrate"),
                verbose=False,
                logger=None
            )
        except Exception as e:
            logger.error(f"Failed to save video: {e}")
            raise
        finally:
            # Clean up clip
            clip.close()
            del self.clips[video["clip_id"]]

    def animate(self, video: Dict[str, Any],
                animation_func: Callable[[float], Dict[str, Any]],
                duration: float, fps: float) -> Dict[str, Any]:
        """
        Create an animated video using a function.

        Args:
            video: Base video dictionary
            animation_func: Function that takes time (0-1) and returns transform parameters
            duration: Animation duration
            fps: Frames per second

        Returns:
            New video dictionary
        """
        base_clip = self.clips[video["clip_id"]]

        def make_frame(t):
            # Get animation parameters for current time
            progress = t / duration
            params = animation_func(progress)

            # Get base frame
            frame = base_clip.get_frame(t)

            # Apply transformations
            if "position" in params:
                x, y = params["position"]
                frame = np.roll(frame, shift=int(x), axis=1)
                frame = np.roll(frame, shift=int(y), axis=0)

            if "scale" in params:
                scale = params["scale"]
                new_size = (int(frame.shape[1] * scale), int(frame.shape[0] * scale))
                frame = resize(frame, new_size)

            if "rotation" in params:
                frame = rotate(frame, params["rotation"])

            if "opacity" in params:
                frame = frame.astype(float) * params["opacity"]

            return frame

        # Create animated clip
        animated_clip = VideoFileClip(make_frame, duration=duration)
        animated_clip = animated_clip.set_fps(fps)

        # Store clip
        clip_id = id(animated_clip)
        self.clips[clip_id] = animated_clip

        return {
            "clip_id": clip_id,
            "duration": animated_clip.duration,
            "size": animated_clip.size,
            "fps": animated_clip.fps,
            "audio": False
        }

    def create_motion_graphics(self, width: int, height: int,
                             render_func: Callable[[float, int, int], np.ndarray],
                             duration: float, fps: float) -> Dict[str, Any]:
        """
        Create motion graphics using a render function.

        Args:
            width: Frame width
            height: Frame height
            render_func: Function that takes (time, width, height) and returns a frame
            duration: Animation duration
            fps: Frames per second

        Returns:
            Video dictionary
        """
        def make_frame(t):
            return render_func(t, width, height)

        # Create clip
        clip = VideoFileClip(make_frame, duration=duration)
        clip = clip.set_fps(fps)

        # Store clip
        clip_id = id(clip)
        self.clips[clip_id] = clip

        return {
            "clip_id": clip_id,
            "duration": clip.duration,
            "size": clip.size,
            "fps": clip.fps,
            "audio": False
        }

    def create_matplotlib_animation(self,
                                  plot_func: Callable[[float, plt.Figure], None],
                                  duration: float, fps: float,
                                  figsize: Tuple[int, int] = (8, 6),
                                  dpi: int = 100) -> Dict[str, Any]:
        """
        Create animation from Matplotlib plots.

        Args:
            plot_func: Function that takes (time, figure) and updates the plot
            duration: Animation duration
            fps: Frames per second
            figsize: Figure size in inches
            dpi: Dots per inch

        Returns:
            Video dictionary
        """
        fig = plt.figure(figsize=figsize, dpi=dpi)

        def make_frame(t):
            fig.clear()
            plot_func(t, fig)
            return mplfig_to_npimage(fig)

        # Create clip
        clip = VideoFileClip(make_frame, duration=duration)
        clip = clip.set_fps(fps)

        # Store clip
        clip_id = id(clip)
        self.clips[clip_id] = clip

        return {
            "clip_id": clip_id,
            "duration": clip.duration,
            "size": clip.size,
            "fps": clip.fps,
            "audio": False
        }

    def composite(self, videos: List[Dict[str, Any]],
                 positions: List[Tuple[int, int]],
                 size: Optional[Tuple[int, int]] = None) -> Dict[str, Any]:
        """
        Create a composite video from multiple clips.

        Args:
            videos: List of video dictionaries
            positions: List of (x, y) positions for each video
            size: Output size (width, height)

        Returns:
            New video dictionary
        """
        clips = [self.clips[v["clip_id"]] for v in videos]

        # Set clip positions
        clips = [clip.set_position(pos) for clip, pos in zip(clips, positions)]

        # Create composite
        composite = CompositeVideoClip(clips, size=size)

        # Store clip
        clip_id = id(composite)
        self.clips[clip_id] = composite

        return {
            "clip_id": clip_id,
            "duration": composite.duration,
            "size": composite.size,
            "fps": composite.fps,
            "audio": any(clip.audio is not None for clip in clips)
        }

    def cleanup(self):
        """Clean up all clips."""
        for clip in self.clips.values():
            try:
                clip.close()
            except:
                pass
        self.clips.clear()

    def __del__(self):
        """Ensure cleanup on deletion."""
        self.cleanup()

    def grid(self, videos: List[Any], rows: int, cols: int) -> Any:
        """Arrange videos in a grid layout."""
        # This is a more complex operation that would be better
        # handled with a custom implementation using clips.CompositeVideoClip
        # For simplicity, just log a warning
        logger.warning("Grid operation not yet implemented for MoviePy backend")
        return videos[0] if videos else None

    def add_subtitles(self, video: Any, entries: List,
                     font: str = "Arial", size: int = 24,
                     color: Union[str, Tuple[int, int, int]] = "white",
                     position: Optional[Tuple[int, int]] = None) -> Any:
        """
        Add subtitles from a list of entries.

        Args:
            video: Video dictionary
            entries: List of SubtitleEntry objects
            font: Font name or path
            size: Font size in pixels
            color: Text color (name or RGB tuple)
            position: Default position (None for bottom center)

        Returns:
            Updated video dictionary
        """
        # This could be implemented using TextClip and CompositeVideoClip
        # For simplicity, just log a warning
        logger.warning("Subtitle operation not yet implemented for MoviePy backend")
        return video

    def add_subtitle_text(self, video: Any, entry,
                         font: str = "Arial", size: int = 24,
                         color: Union[str, Tuple[int, int, int]] = "white") -> Any:
        """
        Add a single subtitle entry.

        Args:
            video: Video dictionary
            entry: SubtitleEntry object
            font: Font name or path
            size: Font size in pixels
            color: Text color (name or RGB tuple)

        Returns:
            Updated video dictionary
        """
        # This could be implemented using TextClip and CompositeVideoClip
        # For simplicity, just log a warning
        logger.warning("Subtitle operation not yet implemented for MoviePy backend")
        return video
