"""
GStreamer backend for FMUS-VID.

This module provides the GStreamer implementation of the Backend interface,
focusing on real-time processing and streaming capabilities.
"""

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
import numpy as np
from typing import List, Dict, Tuple, Optional, Any, Union, Callable
from pathlib import Path
import logging
import threading
from queue import Queue
import time

from .backend import Backend

logger = logging.getLogger(__name__)

class GStreamerBackend(Backend):
    """GStreamer implementation of the Backend interface."""

    def __init__(self):
        """Initialize GStreamer backend."""
        # Initialize GStreamer
        Gst.init(None)

        self.pipelines = {}  # Dictionary to store active pipelines
        self.mainloop = None
        self.mainloop_thread = None
        self._frame_callbacks = {}

    @staticmethod
    def is_available() -> bool:
        """Check if GStreamer is available."""
        try:
            import gi
            gi.require_version('Gst', '1.0')
            from gi.repository import Gst
            Gst.init(None)
            return True
        except (ImportError, ValueError):
            return False

    def _start_mainloop(self):
        """Start GLib mainloop in a separate thread."""
        if self.mainloop is None:
            self.mainloop = GLib.MainLoop()
            self.mainloop_thread = threading.Thread(target=self.mainloop.run)
            self.mainloop_thread.daemon = True
            self.mainloop_thread.start()

    def _create_pipeline(self, description: str) -> Tuple[Gst.Pipeline, str]:
        """Create a GStreamer pipeline from description."""
        pipeline = Gst.parse_launch(description)
        pipeline_id = str(id(pipeline))
        self.pipelines[pipeline_id] = pipeline
        return pipeline, pipeline_id

    def _on_new_sample(self, sink: Gst.Element, callback: Callable) -> Gst.FlowReturn:
        """Handle new video sample."""
        sample = sink.emit("pull-sample")
        if sample:
            buffer = sample.get_buffer()
            caps = sample.get_caps()

            # Get frame info from caps
            structure = caps.get_structure(0)
            width = structure.get_value("width")
            height = structure.get_value("height")

            # Get frame data
            success, map_info = buffer.map(Gst.MapFlags.READ)
            if success:
                # Convert to numpy array
                frame = np.ndarray(
                    (height, width, 3),
                    buffer=map_info.data,
                    dtype=np.uint8
                ).copy()  # Make a copy to avoid buffer issues

                buffer.unmap(map_info)

                # Call callback with frame
                callback(frame)

                return Gst.FlowReturn.OK

        return Gst.FlowReturn.ERROR

    def create_stream(self, width: int, height: int, fps: float,
                     output_url: str, **kwargs) -> Dict[str, Any]:
        """
        Create a video streaming pipeline.

        Args:
            width: Frame width
            height: Frame height
            fps: Frames per second
            output_url: Streaming destination URL
            **kwargs: Additional options
                - codec: Video codec (e.g., "x264", "vp8")
                - bitrate: Video bitrate in bits per second
                - format: Output format (e.g., "rtmp", "rtp")

        Returns:
            Dictionary with stream information
        """
        # Determine codec and format settings
        codec = kwargs.get("codec", "x264")
        bitrate = kwargs.get("bitrate", 2000000)  # Default 2Mbps
        format = kwargs.get("format", "rtmp")

        # Build pipeline description
        elements = [
            f"appsrc name=src do-timestamp=true format=time",
            f"video/x-raw,format=RGB,width={width},height={height},framerate={fps}/1",
            "videoconvert",
        ]

        # Add codec
        if codec == "x264":
            elements.extend([
                f"x264enc bitrate={bitrate//1000} tune=zerolatency",
                "video/x-h264,profile=baseline",
                "h264parse",
            ])
        elif codec == "vp8":
            elements.extend([
                f"vp8enc target-bitrate={bitrate} deadline=1",
                "video/x-vp8",
            ])

        # Add output format
        if format == "rtmp":
            elements.append(f"flvmux streamable=true")
            elements.append(f"rtmpsink location={output_url}")
        elif format == "rtp":
            elements.append(f"rtph264pay")
            elements.append(f"udpsink host={output_url.split(':')[0]} port={output_url.split(':')[1]}")

        # Create pipeline
        pipeline_desc = " ! ".join(elements)
        pipeline, pipeline_id = self._create_pipeline(pipeline_desc)

        # Get appsrc element
        src = pipeline.get_by_name("src")

        # Start pipeline
        pipeline.set_state(Gst.State.PLAYING)
        self._start_mainloop()

        return {
            "pipeline_id": pipeline_id,
            "src": src,
            "width": width,
            "height": height,
            "fps": fps
        }

    def write_frame(self, stream: Dict[str, Any], frame: np.ndarray) -> None:
        """
        Write a frame to the stream.

        Args:
            stream: Stream dictionary
            frame: Frame data (RGB numpy array)
        """
        src = stream["src"]

        # Create buffer from frame data
        data = frame.tobytes()
        buffer = Gst.Buffer.new_wrapped(data)

        # Push buffer to pipeline
        src.emit("push-buffer", buffer)

    def create_rtsp_server(self, width: int, height: int, fps: float,
                          path: str = "/stream", port: int = 8554,
                          **kwargs) -> Dict[str, Any]:
        """
        Create an RTSP streaming server.

        Args:
            width: Frame width
            height: Frame height
            fps: Frames per second
            path: Stream path
            port: Server port
            **kwargs: Additional options
                - codec: Video codec
                - bitrate: Video bitrate

        Returns:
            Dictionary with server information
        """
        from gi.repository import GstRtspServer

        # Create server
        server = GstRtspServer.RTSPServer()
        server.set_service(str(port))

        # Create factory
        factory = GstRtspServer.RTSPMediaFactory()

        # Build pipeline description
        codec = kwargs.get("codec", "x264")
        bitrate = kwargs.get("bitrate", 2000000)

        elements = [
            f"appsrc name=src do-timestamp=true format=time",
            f"video/x-raw,format=RGB,width={width},height={height},framerate={fps}/1",
            "videoconvert",
        ]

        if codec == "x264":
            elements.extend([
                f"x264enc bitrate={bitrate//1000} tune=zerolatency",
                "video/x-h264,profile=baseline",
                "h264parse",
                "rtph264pay"
            ])

        pipeline_desc = " ! ".join(elements)
        factory.set_launch(pipeline_desc)

        # Add factory to server
        server.get_mount_points().add_factory(path, factory)

        # Start server
        server.attach(None)
        self._start_mainloop()

        # Create pipeline for writing frames
        pipeline, pipeline_id = self._create_pipeline(pipeline_desc)
        src = pipeline.get_by_name("src")
        pipeline.set_state(Gst.State.PLAYING)

        return {
            "server": server,
            "pipeline_id": pipeline_id,
            "src": src,
            "width": width,
            "height": height,
            "fps": fps,
            "url": f"rtsp://localhost:{port}{path}"
        }

    def create_display(self, width: int, height: int, fps: float,
                      title: str = "Video Display") -> Dict[str, Any]:
        """
        Create a real-time video display window.

        Args:
            width: Frame width
            height: Frame height
            fps: Frames per second
            title: Window title

        Returns:
            Dictionary with display information
        """
        # Build pipeline description
        elements = [
            f"appsrc name=src do-timestamp=true format=time",
            f"video/x-raw,format=RGB,width={width},height={height},framerate={fps}/1",
            "videoconvert",
            f"autovideosink window-title=\"{title}\""
        ]

        pipeline_desc = " ! ".join(elements)
        pipeline, pipeline_id = self._create_pipeline(pipeline_desc)

        # Get source element
        src = pipeline.get_by_name("src")

        # Start pipeline
        pipeline.set_state(Gst.State.PLAYING)
        self._start_mainloop()

        return {
            "pipeline_id": pipeline_id,
            "src": src,
            "width": width,
            "height": height,
            "fps": fps
        }

    def create_webcam_source(self, device: int = 0,
                           callback: Callable[[np.ndarray], None] = None) -> Dict[str, Any]:
        """
        Create a webcam video source.

        Args:
            device: Camera device index
            callback: Function to call with each frame

        Returns:
            Dictionary with source information
        """
        # Build pipeline description
        elements = [
            f"v4l2src device=/dev/video{device}",
            "video/x-raw,format=RGB",
            "videoconvert",
            "appsink name=sink emit-signals=true max-buffers=1 drop=true"
        ]

        pipeline_desc = " ! ".join(elements)
        pipeline, pipeline_id = self._create_pipeline(pipeline_desc)

        # Get sink element and set callback
        sink = pipeline.get_by_name("sink")
        if callback:
            self._frame_callbacks[pipeline_id] = callback
            sink.connect("new-sample", self._on_new_sample, callback)

        # Start pipeline
        pipeline.set_state(Gst.State.PLAYING)
        self._start_mainloop()

        return {
            "pipeline_id": pipeline_id,
            "sink": sink
        }

    def cleanup(self):
        """Clean up all pipelines and stop mainloop."""
        # Stop all pipelines
        for pipeline in self.pipelines.values():
            pipeline.set_state(Gst.State.NULL)

        # Clear pipelines and callbacks
        self.pipelines.clear()
        self._frame_callbacks.clear()

        # Stop mainloop
        if self.mainloop:
            self.mainloop.quit()
            if self.mainloop_thread:
                self.mainloop_thread.join()
            self.mainloop = None
            self.mainloop_thread = None

    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 require
        # multiple GStreamer pipelines and video mixing
        # For simplicity, just log a warning
        logger.warning("Grid operation not yet implemented for GStreamer 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
        """
        logger.warning("Subtitle operation not yet implemented for GStreamer 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
        """
        logger.warning("Subtitle operation not yet implemented for GStreamer backend")
        return video
