"""
LibVLC backend for FMUS-VID.

This module provides the LibVLC implementation of the Backend interface,
focusing on high-quality video playback and streaming capabilities.
"""

import vlc
import numpy as np
from typing import List, Dict, Tuple, Optional, Any, Union, Callable
from pathlib import Path
import logging
import threading
import time
import ctypes
from queue import Queue

from .backend import Backend

logger = logging.getLogger(__name__)

class VLCBackend(Backend):
    """LibVLC implementation of the Backend interface."""

    def __init__(self):
        """Initialize LibVLC backend."""
        # Create VLC instance with options
        self.instance = vlc.Instance([
            "--no-xlib",  # Disable X11 dependency
            "--quiet",    # Disable console output
            "--no-video-title-show"  # Don't show title
        ])

        self.players = {}  # Dictionary to store active players
        self._frame_callbacks = {}
        self._frame_buffers = {}

    @staticmethod
    def is_available() -> bool:
        """Check if LibVLC is available."""
        try:
            import vlc
            instance = vlc.Instance()
            return True
        except (ImportError, NameError):
            return False

    def create_player(self, video_path: Union[str, Path] = None,
                     **kwargs) -> Dict[str, Any]:
        """
        Create a video player.

        Args:
            video_path: Optional path to video file
            **kwargs: Additional options
                - width: Window width
                - height: Window height
                - title: Window title
                - fullscreen: Fullscreen mode
                - loop: Loop playback

        Returns:
            Dictionary with player information
        """
        # Create media player
        player = self.instance.media_player_new()

        # Configure player
        try:
            if "width" in kwargs and "height" in kwargs:
                player.set_size(kwargs["width"], kwargs["height"])
        except AttributeError:
            pass  # set_size may not be available in all VLC versions

        try:
            if "title" in kwargs:
                player.set_title(kwargs["title"])
        except AttributeError:
            pass  # set_title may not be available in all VLC versions

        if kwargs.get("fullscreen", False):
            player.set_fullscreen(True)

        # Load media if path provided
        if video_path:
            media = self.instance.media_new(str(video_path))
            if kwargs.get("loop", False):
                media.add_option("input-repeat=65535")
            player.set_media(media)

        # Store player
        player_id = str(id(player))
        self.players[player_id] = {
            "player": player,
            "callbacks": {}
        }

        return {
            "player_id": player_id,
            "player": player
        }

    def set_callback(self, player_info: Dict[str, Any],
                    event_type: str, callback: Callable) -> None:
        """
        Set event callback for player.

        Args:
            player_info: Player dictionary
            event_type: Event type (e.g., "playing", "stopped", "time-changed")
            callback: Callback function
        """
        player = self.players[player_info["player_id"]]

        # Create event manager if not exists
        if "event_manager" not in player:
            player["event_manager"] = player["player"].event_manager()

        # Map event type to VLC event
        event_map = {
            "playing": vlc.EventType.MediaPlayerPlaying,
            "paused": vlc.EventType.MediaPlayerPaused,
            "stopped": vlc.EventType.MediaPlayerStopped,
            "ended": vlc.EventType.MediaPlayerEndReached,
            "time-changed": vlc.EventType.MediaPlayerTimeChanged,
            "position-changed": vlc.EventType.MediaPlayerPositionChanged,
            "error": vlc.EventType.MediaPlayerEncounteredError
        }

        if event_type in event_map:
            player["event_manager"].event_attach(
                event_map[event_type],
                callback
            )
            player["callbacks"][event_type] = callback

    def set_frame_callback(self, player_info: Dict[str, Any],
                          callback: Callable[[np.ndarray], None]) -> None:
        """
        Set callback for video frames.

        Args:
            player_info: Player dictionary
            callback: Function to call with each frame
        """
        player = self.players[player_info["player_id"]]["player"]

        # Get video dimensions
        width = player.video_get_width()
        height = player.video_get_height()

        if not width or not height:
            raise ValueError("Video dimensions not available")

        # Create frame buffer
        buffer_size = width * height * 4  # RGBA format
        frame_buffer = (ctypes.c_uint8 * buffer_size)()

        def _frame_callback(opaque, planes):
            # Convert frame buffer to numpy array
            frame = np.frombuffer(frame_buffer, dtype=np.uint8)
            frame = frame.reshape((height, width, 4))
            frame = frame[:, :, :3]  # Convert RGBA to RGB

            # Call user callback
            callback(frame)

            return None

        # Set video callbacks
        player.video_set_format("RGBA", width, height, width * 4)
        player.video_set_callbacks(
            _frame_callback,
            None,
            None,
            frame_buffer
        )

        # Store callback info
        self._frame_callbacks[player_info["player_id"]] = callback
        self._frame_buffers[player_info["player_id"]] = frame_buffer

    def play(self, player_info: Dict[str, Any]) -> None:
        """
        Start video playback.

        Args:
            player_info: Player dictionary
        """
        player = self.players[player_info["player_id"]]["player"]
        player.play()

    def pause(self, player_info: Dict[str, Any]) -> None:
        """
        Pause video playback.

        Args:
            player_info: Player dictionary
        """
        player = self.players[player_info["player_id"]]["player"]
        player.pause()

    def stop(self, player_info: Dict[str, Any]) -> None:
        """
        Stop video playback.

        Args:
            player_info: Player dictionary
        """
        player = self.players[player_info["player_id"]]["player"]
        player.stop()

    def seek(self, player_info: Dict[str, Any], position: float) -> None:
        """
        Seek to position in video.

        Args:
            player_info: Player dictionary
            position: Position in seconds
        """
        player = self.players[player_info["player_id"]]["player"]
        player.set_time(int(position * 1000))

    def set_position(self, player_info: Dict[str, Any], position: float) -> None:
        """
        Set playback position (0-1).

        Args:
            player_info: Player dictionary
            position: Position (0-1)
        """
        player = self.players[player_info["player_id"]]["player"]
        player.set_position(position)

    def get_position(self, player_info: Dict[str, Any]) -> float:
        """
        Get current playback position (0-1).

        Args:
            player_info: Player dictionary

        Returns:
            Current position (0-1)
        """
        player = self.players[player_info["player_id"]]["player"]
        return player.get_position()

    def get_time(self, player_info: Dict[str, Any]) -> float:
        """
        Get current playback time.

        Args:
            player_info: Player dictionary

        Returns:
            Current time in seconds
        """
        player = self.players[player_info["player_id"]]["player"]
        return player.get_time() / 1000.0

    def get_duration(self, player_info: Dict[str, Any]) -> float:
        """
        Get video duration.

        Args:
            player_info: Player dictionary

        Returns:
            Duration in seconds
        """
        player = self.players[player_info["player_id"]]["player"]
        return player.get_length() / 1000.0

    def set_rate(self, player_info: Dict[str, Any], rate: float) -> None:
        """
        Set playback rate.

        Args:
            player_info: Player dictionary
            rate: Playback rate (1.0 = normal speed)
        """
        player = self.players[player_info["player_id"]]["player"]
        player.set_rate(rate)

    def get_rate(self, player_info: Dict[str, Any]) -> float:
        """
        Get current playback rate.

        Args:
            player_info: Player dictionary

        Returns:
            Current playback rate
        """
        player = self.players[player_info["player_id"]]["player"]
        return player.get_rate()

    def set_volume(self, player_info: Dict[str, Any], volume: int) -> None:
        """
        Set audio volume.

        Args:
            player_info: Player dictionary
            volume: Volume level (0-100)
        """
        player = self.players[player_info["player_id"]]["player"]
        player.audio_set_volume(volume)

    def get_volume(self, player_info: Dict[str, Any]) -> int:
        """
        Get current audio volume.

        Args:
            player_info: Player dictionary

        Returns:
            Current volume level (0-100)
        """
        player = self.players[player_info["player_id"]]["player"]
        return player.audio_get_volume()

    def cleanup(self):
        """Clean up all players."""
        for player_info in self.players.values():
            try:
                player_info["player"].stop()
                player_info["player"].release()
            except:
                pass

        self.players.clear()
        self._frame_callbacks.clear()
        self._frame_buffers.clear()

        try:
            self.instance.release()
        except:
            pass

    def __del__(self):
        """Clean up when the object is deleted."""
        try:
            self.cleanup()
        except Exception as e:
            logger.warning(f"Error during VLC backend cleanup: {e}")

    # Implement abstract methods from Backend interface
    def load(self, path: Union[str, Path], **kwargs) -> Any:
        """Load a video file."""
        return self.create_player(path, **kwargs)

    def create(self, width: int, height: int, duration: float, fps: float,
               color: Tuple[int, int, int], **kwargs) -> Any:
        """Create a blank video."""
        # VLC doesn't support creating blank videos directly
        # For now, return a dummy player
        return self.create_player(None, width=width, height=height, **kwargs)

    def save(self, video: Any, output_path: Union[str, Path],
             progress_callback: Optional[Callable[[float], None]] = None, **kwargs) -> None:
        """Save video to file."""
        logger.warning("VLC backend doesn't support saving videos")
        if progress_callback:
            progress_callback(1.0)  # Simulate completion

    def get_info(self, video: Any) -> Dict[str, Any]:
        """Get video information."""
        player = self.players[video["player_id"]]["player"]
        media = player.get_media()

        if not media:
            return {
                "width": 1280,
                "height": 720,
                "duration": 0,
                "fps": 30,
                "has_audio": False,
                "codec": "unknown"
            }

        media.parse()
        # Check for audio tracks using get_tracks
        has_audio = False
        try:
            tracks = media.get_tracks()
            has_audio = any(t.track_type == 'audio' for t in tracks) if tracks else False
        except Exception:
            has_audio = False

        return {
            "width": player.video_get_width() or 1280,
            "height": player.video_get_height() or 720,
            "duration": media.get_duration() / 1000.0,
            "fps": 30,  # VLC doesn't expose FPS easily
            "has_audio": has_audio,
            "codec": "unknown"  # VLC doesn't expose codec easily
        }

    # Implement operation stubs
    def trim(self, video: Any, start: float, end: Optional[float] = None) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def resize(self, video: Any, width: Optional[int] = None,
               height: Optional[int] = None, keep_aspect: bool = True) -> Any:
        player = self.players[video["player_id"]]["player"]
        if width and height:
            player.set_size(width, height)
        return video

    def crop(self, video: Any, x: int, y: int, width: int, height: int) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def rotate(self, video: Any, degrees: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def grayscale(self, video: Any) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def blur(self, video: Any, radius: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def brightness(self, video: Any, factor: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def contrast(self, video: Any, factor: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def mute(self, video: Any) -> Any:
        player = self.players[video["player_id"]]["player"]
        player.audio_set_mute(True)
        return video

    def volume(self, video: Any, level: float) -> Any:
        player = self.players[video["player_id"]]["player"]
        player.audio_set_volume(int(level * 100))
        return video

    def add_audio(self, video: Any, audio_path: Union[str, Path],
                  start: float = 0, volume: float = 1.0) -> Any:
        logger.warning("VLC backend doesn't support adding audio tracks")
        return video

    def extract_frame(self, video: Any, time: float) -> Any:
        logger.warning("VLC backend doesn't support frame extraction in batch mode")
        return {"frame": None, "time": time}

    def overlay(self, video: Any, overlay_video: Any,
                position: Tuple[int, int] = (0, 0),
                start: float = 0, duration: Optional[float] = None,
                opacity: float = 1.0) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def concat(self, videos: List[Any]) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return videos[0] if videos else None

    def grid(self, videos: List[Any], rows: int, cols: int) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        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
        """
        logger.warning("VLC backend doesn't support adding subtitles in batch mode")
        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
        """
        logger.warning("VLC backend doesn't support adding subtitles in batch mode")
        return video

    # New abstract methods
    def speed(self, video: Any, factor: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def reverse(self, video: Any) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def fade_in(self, video: Any, duration: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def fade_out(self, video: Any, duration: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def fade_audio_in(self, video: Any, duration: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def fade_audio_out(self, video: Any, duration: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def chroma_key(self, video: Any, color: Tuple[int, int, int],
                   similarity: float = 0.1, blend: float = 0.0) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def normalize_audio(self, video: Any, target_db: float = -16.0) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def crossfade(self, video1: Any, video2: Any, duration: float) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video1

    def replace_audio(self, video: Any, audio_path: Union[str, Path]) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def export_gif(self, video: Any, output_path: Union[str, Path],
                   fps: Optional[float] = None, quality: int = 95,
                   loop: int = 0, width: Optional[int] = None) -> None:
        logger.warning("VLC backend doesn't support GIF export")

    def export_frames(self, video: Any, output_dir: Union[str, Path],
                      prefix: str = "frame_", format: str = "png",
                      start: Optional[float] = None, end: Optional[float] = None,
                      fps: Optional[float] = None) -> List[Path]:
        logger.warning("VLC backend doesn't support frame export")
        return []

    def add_text(self, video: Any, overlay: Any) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video

    def add_image(self, video: Any, overlay: Any) -> Any:
        logger.warning("VLC backend doesn't support video editing operations")
        return video
