# GstMini
# Copyright (C) 2025 Rafael Caricio <rafael@caricio.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.

"""S3Sink - Uploads segments with synchronization."""

import time
from ..core.element import GstElement
from ..core.buffer import GstBuffer, BufferFlags
from ..core.pad import GstFlowReturn


class S3Sink(GstElement):
    """
    Sink element that simulates S3 upload with synchronization.

    Demonstrates gst_base_sink_do_sync and gst_base_sink_wait_clock
    from gstbasesink.c:2665-2828 and gstbasesink.c:2333-2404
    """

    def __init__(self, name: str, bucket: str = "my-bucket", sync: bool = True):
        """
        Create an S3 sink.

        Args:
            name: Element name
            bucket: S3 bucket name (simulated)
            sync: Enable synchronization to clock
        """
        super().__init__(name)
        self.bucket = bucket
        self.sync = sync
        self.last_rendered_pts = 0.0
        self.segment_count = 0

        # Create sink pad and set chain function
        self.sink_pad = self.create_sink_pad("sink")
        self.sink_pad.set_chain_function(self._chain)

    def _chain(self, buffer: GstBuffer) -> GstFlowReturn:
        """
        Chain function - receives buffers from upstream.

        This is where synchronization happens (similar to gst_base_sink_do_sync).

        Args:
            buffer: Incoming buffer

        Returns:
            GstFlowReturn status
        """
        # Check for EOS
        if buffer.has_flag(BufferFlags.EOS):
            self.log("Received EOS")
            return GstFlowReturn.EOS

        # Extract timestamp
        pts = buffer.pts

        # Convert stream time to running time (gst_segment_to_running_time)
        # Corresponds to gstbasesink.c:2207
        running_time = self.segment.to_running_time(pts)

        if running_time < 0:
            self.log("Buffer outside segment, dropping")
            return GstFlowReturn.OK

        # Synchronization (gst_base_sink_do_sync)
        if self.sync and self.state.name == 'PLAYING':
            # Calculate clock time: running_time + base_time
            # Corresponds to gstbasesink.c:2356
            clock_time = running_time + self.pipeline.base_time

            # Wait on clock (gst_base_sink_wait_clock at gstbasesink.c:2381)
            self.log(f"Waiting for clock_time={clock_time:.3f}s...")
            _, jitter = self.pipeline.clock.wait_until(clock_time)
            jitter_ms = jitter * 1000

            if jitter >= 0:
                self.log(f"Clock wait complete (jitter: {jitter_ms:+.1f}ms)")
            else:
                self.log(f"Buffer late (jitter: {jitter_ms:+.1f}ms)")

        # Simulate S3 upload (the "render" vmethod)
        self._upload_segment(buffer)

        # Update position
        self.last_rendered_pts = pts

        return GstFlowReturn.OK

    def _upload_segment(self, buffer: GstBuffer):
        """
        Simulate uploading a segment to S3.

        Args:
            buffer: Buffer containing segment data
        """
        segment_data = buffer.data

        if isinstance(segment_data, dict) and 'segment_num' in segment_data:
            # This is a segment buffer
            segment_num = segment_data['segment_num']
            num_frames = len(segment_data['buffers'])
            duration = segment_data['duration']

            self.log(f"Uploading segment_{segment_num}.cmfv ({num_frames} frames, "
                     f"{duration:.1f}s) to s3://{self.bucket}/")

            # Simulate upload delay
            time.sleep(0.017)  # 17ms simulated upload time

            self.log("Upload complete")
            self.segment_count += 1

        else:
            # Regular buffer
            self.log(f"Processing buffer at PTS={buffer.pts:.3f}s")

    def get_position(self) -> float:
        """Get current playback position."""
        return self.last_rendered_pts

    def on_null(self):
        """Called when going to NULL state."""
        self.log(f"Total segments uploaded: {self.segment_count}")
