"""
Basic video operations for FMUS-VID.

This module provides fundamental video manipulation functions.
"""

import os
import time
from pathlib import Path
from typing import List, Union, Optional, Tuple, Dict

import cv2
import numpy as np

from ..core.video import Video
from ..core.frame import Frame


def video_to_images(
    video: Union[str, Path, Video],
    output_dir: Union[str, Path],
    start: Optional[float] = None,
    end: Optional[float] = None,
    interval: Optional[float] = None,
    frame_count: Optional[int] = None,
    with_timestamp: bool = False,
    with_frame_number: bool = False,
    format: str = "png",
    prefix: str = "frame_",
    **kwargs
) -> List[Path]:
    """
    Extract frames from a video to image files.

    Args:
        video: Video object or path to video file
        output_dir: Directory to save the images
        start: Start time in seconds (None for beginning)
        end: End time in seconds (None for end of video)
        interval: Time interval between frames in seconds
        frame_count: Number of frames to extract (evenly distributed)
        with_timestamp: Add timestamp text overlay to images
        with_frame_number: Add frame number text overlay to images
        format: Image format ('png', 'jpg', etc.)
        prefix: Filename prefix for the saved images
        **kwargs: Additional options

    Returns:
        List of paths to the saved images

    Example:
        >>> # Extract all frames
        >>> frames = fmusvid.video_to_images("input.mp4", "frames_dir")
        >>> # Extract 10 evenly distributed frames
        >>> frames = fmusvid.video_to_images("input.mp4", "frames_dir", frame_count=10)
        >>> # Extract frames at 1-second intervals with timestamps
        >>> frames = fmusvid.video_to_images("input.mp4", "frames_dir", interval=1.0, with_timestamp=True)
    """
    # Create output directory
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)

    # Get video path
    if isinstance(video, Video):
        video_path = video._path
    else:
        video_path = Path(video)

    # Open the video
    cap = cv2.VideoCapture(str(video_path))

    # Check if video opened successfully
    if not cap.isOpened():
        raise ValueError(f"Could not open video: {video_path}")

    # Get video properties
    fps = cap.get(cv2.CAP_PROP_FPS)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    duration = total_frames / fps

    # Convert time to frame numbers
    start_frame = 0
    if start is not None:
        start_frame = int(start * fps)

    end_frame = total_frames - 1
    if end is not None:
        end_frame = min(int(end * fps), total_frames - 1)

    # Determine frames to extract
    if frame_count is not None:
        # Evenly distributed frames
        if frame_count <= 1:
            raise ValueError("frame_count must be greater than 1")

        frames_to_extract = []
        if frame_count == 1:
            # Just the middle frame
            frames_to_extract = [start_frame + (end_frame - start_frame) // 2]
        else:
            # Calculate step
            step = (end_frame - start_frame) / (frame_count - 1)
            frames_to_extract = [int(start_frame + i * step) for i in range(frame_count)]
    elif interval is not None:
        # Frames at regular intervals
        frame_interval = int(interval * fps)
        if frame_interval < 1:
            frame_interval = 1
        frames_to_extract = list(range(start_frame, end_frame + 1, frame_interval))
    else:
        # All frames in range
        frames_to_extract = list(range(start_frame, end_frame + 1))

    # Set up font for text overlay
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 1.0
    font_color = (255, 255, 255)  # White
    thickness = 2

    # Extract and save frames
    saved_paths = []

    for i, frame_num in enumerate(frames_to_extract):
        # Set frame position
        cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)

        # Read the frame
        ret, frame = cap.read()
        if not ret:
            continue

        # Add timestamp if requested
        if with_timestamp or with_frame_number:
            # Calculate time
            frame_time = frame_num / fps
            hours, remainder = divmod(frame_time, 3600)
            minutes, seconds = divmod(remainder, 60)
            milliseconds = int((seconds - int(seconds)) * 1000)
            seconds = int(seconds)

            # Position for text (bottom left)
            position = (10, frame.shape[0] - 10)

            if with_timestamp and with_frame_number:
                # Both timestamp and frame number
                text = f"Frame: {frame_num} - Time: {int(hours):02d}:{int(minutes):02d}:{seconds:02d}.{milliseconds:03d}"
            elif with_timestamp:
                # Only timestamp
                text = f"Time: {int(hours):02d}:{int(minutes):02d}:{seconds:02d}.{milliseconds:03d}"
            else:
                # Only frame number
                text = f"Frame: {frame_num}"

            # Add text to frame
            frame = cv2.putText(frame, text, position, font, font_scale, font_color, thickness, cv2.LINE_AA)

        # Create filename with padding for sequential order
        if len(frames_to_extract) > 1:
            padding = len(str(len(frames_to_extract)))
            filename = f"{prefix}{i:0{padding}d}.{format}"
        else:
            filename = f"{prefix}single.{format}"

        # Save the frame
        output_file = output_path / filename
        cv2.imwrite(str(output_file), frame)
        saved_paths.append(output_file)

    # Release the video capture
    cap.release()

    return saved_paths


def reverse_video(
    video: Union[str, Path, Video],
    output: Optional[Union[str, Path]] = None,
    **kwargs
) -> Video:
    """
    Create a reversed version of a video.

    Args:
        video: Video object or path to video file
        output: Path to save the reversed video (None for temporary file)
        **kwargs: Additional options

    Returns:
        Video object for the reversed video

    Example:
        >>> # Create reversed video
        >>> reversed_video = fmusvid.reverse_video("input.mp4", "reversed.mp4")
    """
    # Get video path
    if isinstance(video, Video):
        video_path = video._path
    else:
        video_path = Path(video)

    # Create output path if not provided
    if output is None:
        # Create a temporary file
        import tempfile
        temp_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
        output_path = Path(temp_file.name)
        temp_file.close()
    else:
        output_path = Path(output)

    # Open the video
    cap = cv2.VideoCapture(str(video_path))

    # Check if video opened successfully
    if not cap.isOpened():
        raise ValueError(f"Could not open video: {video_path}")

    # Get video properties
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

    # Set up video writer
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    out = cv2.VideoWriter(str(output_path), fourcc, fps, (width, height))

    # Start from the last frame
    for frame_idx in range(total_frames - 1, -1, -1):
        # Set position
        cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)

        # Read the frame
        ret, frame = cap.read()
        if not ret:
            continue

        # Write the frame
        out.write(frame)

    # Release resources
    cap.release()
    out.release()

    # Return Video object
    return Video(output_path)


def create_thumbnails(
    video: Union[str, Path, Video],
    count: int = 9,
    output_path: Optional[Union[str, Path]] = None,
    grid: bool = True,
    scale: float = 0.25,
    with_timestamp: bool = True,
    **kwargs
) -> Union[List[Path], Path]:
    """
    Generate thumbnail images from a video.

    Args:
        video: Video object or path to video file
        count: Number of thumbnails to generate
        output_path: Path to save the grid image or directory for individual thumbnails
        grid: Whether to combine thumbnails into a grid image
        scale: Scale factor for the thumbnails
        with_timestamp: Add timestamp to the thumbnails
        **kwargs: Additional options

    Returns:
        Path to grid image if grid=True, otherwise list of paths to individual thumbnails

    Example:
        >>> # Create a 3x3 grid of thumbnails
        >>> grid_path = fmusvid.create_thumbnails("input.mp4", count=9, output_path="thumbs.jpg")
        >>> # Create individual thumbnails
        >>> thumb_paths = fmusvid.create_thumbnails("input.mp4", count=5, grid=False, output_path="thumbs_dir")
    """
    # Get video path
    if isinstance(video, Video):
        video_path = video._path
    else:
        video_path = Path(video)

    # Open the video
    cap = cv2.VideoCapture(str(video_path))

    # Check if video opened successfully
    if not cap.isOpened():
        raise ValueError(f"Could not open video: {video_path}")

    # Get video properties
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    duration = total_frames / fps

    # Calculate frames to capture
    interval = duration / (count + 1)  # +1 to avoid capturing the first and last frames
    frame_times = [interval * (i + 1) for i in range(count)]

    # Set up font for timestamps
    font = cv2.FONT_HERSHEY_SIMPLEX
    font_scale = 0.7 * scale
    font_color = (255, 255, 255)  # White
    thickness = 1

    # Capture thumbnails
    thumbnails = []

    for time_pos in frame_times:
        # Set position
        frame_pos = int(time_pos * fps)
        cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos)

        # Read the frame
        ret, frame = cap.read()
        if not ret:
            continue

        # Add timestamp if requested
        if with_timestamp:
            # Format timestamp
            hours, remainder = divmod(time_pos, 3600)
            minutes, seconds = divmod(remainder, 60)
            time_str = f"{int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}"

            # Add timestamp
            position = (10, frame.shape[0] - 10)
            frame = cv2.putText(frame, time_str, position, font, font_scale, font_color, thickness, cv2.LINE_AA)

        # Resize thumbnail
        if scale != 1.0:
            new_width = int(width * scale)
            new_height = int(height * scale)
            thumbnail = cv2.resize(frame, (new_width, new_height))
        else:
            thumbnail = frame

        thumbnails.append(thumbnail)

    # Release the video capture
    cap.release()

    # Process thumbnails based on output mode
    if grid:
        # Calculate grid dimensions
        grid_size = int(np.ceil(np.sqrt(len(thumbnails))))
        rows = grid_size
        cols = grid_size

        # Create black canvas
        thumb_height, thumb_width = thumbnails[0].shape[:2]
        grid_img = np.zeros((thumb_height * rows, thumb_width * cols, 3), dtype=np.uint8)

        # Place thumbnails on grid
        for i, thumbnail in enumerate(thumbnails):
            if i >= rows * cols:
                break

            row = i // cols
            col = i % cols

            y_start = row * thumb_height
            y_end = y_start + thumb_height
            x_start = col * thumb_width
            x_end = x_start + thumb_width

            grid_img[y_start:y_end, x_start:x_end] = thumbnail

        # Save the grid
        if output_path is None:
            # Create default output path
            output_path = Path(f"{video_path.stem}_thumbnails.jpg")
        else:
            output_path = Path(output_path)

        cv2.imwrite(str(output_path), grid_img)
        return output_path
    else:
        # Save individual thumbnails
        if output_path is None:
            # Create default output directory
            output_path = Path(f"{video_path.stem}_thumbnails")

        output_path = Path(output_path)
        output_path.mkdir(parents=True, exist_ok=True)

        # Save each thumbnail
        saved_paths = []
        for i, thumbnail in enumerate(thumbnails):
            thumb_path = output_path / f"thumb_{i:03d}.jpg"
            cv2.imwrite(str(thumb_path), thumbnail)
            saved_paths.append(thumb_path)

        return saved_paths
