"""
Video Processing Module for Cinespinner Pro

Optimized video analysis with sequential frame reading for maximum performance.
Sequential reading is 10-100x faster than random seeking in video files.
"""

import os
import time
from pathlib import Path
from typing import List, Tuple, Optional, Callable
from dataclasses import dataclass

import cv2
import numpy as np

from color_profiles import ProfileType, get_profile, ColorProfile


# Default number of wedges (segments) in the palette
NUM_WEDGES = 360


@dataclass
class VideoMetadata:
    """Metadata about a video file."""
    path: Path
    duration: float  # seconds
    fps: float
    total_frames: int
    width: int
    height: int


def get_video_metadata(video_path: Path) -> Optional[VideoMetadata]:
    """Extract metadata from a video file."""
    cap = cv2.VideoCapture(str(video_path))
    if not cap.isOpened():
        return None

    try:
        fps = cap.get(cv2.CAP_PROP_FPS)
        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        duration = total_frames / fps if fps > 0 else 0

        if duration <= 0:
            return None

        return VideoMetadata(
            path=video_path,
            duration=duration,
            fps=fps,
            total_frames=total_frames,
            width=width,
            height=height
        )
    finally:
        cap.release()


class VideoProcessor:
    """
    Processes video files to extract color palettes.

    Uses optimized sequential frame reading - much faster than random seeking.
    """

    def __init__(
        self,
        cpu_limit: float = 0.75,
        downsample_size: int = 16
    ):
        """
        Initialize the video processor.

        Args:
            cpu_limit: CPU throttle (0.25 - 1.0). Lower = more sleep between frames.
            downsample_size: Size to downsample frames to for analysis
        """
        self.cpu_limit = max(0.25, min(1.0, cpu_limit))
        self.downsample_size = downsample_size
        self._cancelled = False

    def cancel(self):
        """Request cancellation of processing."""
        self._cancelled = True

    def reset(self):
        """Reset the processor state."""
        self._cancelled = False

    def extract_colors(
        self,
        video_path: Path,
        profile_type: ProfileType,
        num_wedges: int = NUM_WEDGES,
        progress_callback: Optional[Callable[[int, int], None]] = None
    ) -> List[Tuple[int, int, int]]:
        """
        Extract colors using optimized sequential reading.

        This is MUCH faster than random seeking because video codecs
        are optimized for sequential access.
        """
        self.reset()

        metadata = get_video_metadata(video_path)
        if metadata is None:
            raise RuntimeError("Could not open video file")

        profile = get_profile(profile_type)

        # Calculate which frames we need to sample
        # Sample ~1 frame per second, distributed across wedges
        interval_duration = metadata.duration / num_wedges

        # Build a map of frame_number -> list of wedge indices that need it
        # and collect all unique frame numbers we need
        frames_needed = set()
        wedge_frame_map = {i: [] for i in range(num_wedges)}

        for wedge_idx in range(num_wedges):
            start_time = wedge_idx * interval_duration
            end_time = (wedge_idx + 1) * interval_duration

            # Sample frames at ~1 second intervals within this wedge
            num_samples = max(1, min(int(interval_duration), 20))
            sample_times = np.linspace(start_time, end_time, num_samples, endpoint=False)

            for t in sample_times:
                frame_num = int(t * metadata.fps)
                frame_num = min(frame_num, metadata.total_frames - 1)
                frames_needed.add(frame_num)
                wedge_frame_map[wedge_idx].append(frame_num)

        # Sort frames for sequential reading
        frames_needed = sorted(frames_needed)
        total_frames_to_read = len(frames_needed)

        # Storage for pixel data per wedge
        wedge_pixels = {i: [] for i in range(num_wedges)}

        # Create reverse map: frame_num -> list of wedges that need it
        frame_to_wedges = {}
        for wedge_idx, frame_nums in wedge_frame_map.items():
            for fn in frame_nums:
                if fn not in frame_to_wedges:
                    frame_to_wedges[fn] = []
                frame_to_wedges[fn].append(wedge_idx)

        # Open video
        cap = cv2.VideoCapture(str(video_path))
        if not cap.isOpened():
            raise RuntimeError("Could not open video file")

        try:
            current_frame = 0
            frames_read = 0
            frame_idx = 0  # Index into frames_needed

            # Calculate throttle sleep time based on cpu_limit
            # At 100% cpu_limit, no sleep. At 25%, sleep 3ms per frame.
            sleep_time = (1.0 - self.cpu_limit) * 0.004

            while frame_idx < len(frames_needed):
                if self._cancelled:
                    return []

                target_frame = frames_needed[frame_idx]

                # Skip frames until we reach target
                # For large gaps, use seek; for small gaps, just read through
                gap = target_frame - current_frame

                if gap > 50:
                    # Large gap - seek (still faster than reading all frames)
                    cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame)
                    current_frame = target_frame
                else:
                    # Small gap - read through (sequential is fast)
                    while current_frame < target_frame:
                        cap.grab()  # grab() is faster than read() when we don't need the frame
                        current_frame += 1

                # Read the frame we need
                ret, frame = cap.read()
                if ret and frame is not None:
                    # Downsample
                    small = cv2.resize(frame, (self.downsample_size, self.downsample_size),
                                       interpolation=cv2.INTER_AREA)
                    small_rgb = cv2.cvtColor(small, cv2.COLOR_BGR2RGB)
                    pixels = small_rgb.reshape(-1, 3)

                    # Add to all wedges that need this frame
                    for wedge_idx in frame_to_wedges.get(target_frame, []):
                        wedge_pixels[wedge_idx].append(pixels)

                current_frame += 1
                frames_read += 1
                frame_idx += 1

                # Throttle CPU if needed
                if sleep_time > 0:
                    time.sleep(sleep_time)

                # Report progress (frame reading is ~60% of work)
                if progress_callback and frames_read % 50 == 0:
                    pct = (frames_read / total_frames_to_read) * 60
                    progress_callback(int(pct * num_wedges / 100), num_wedges)

        finally:
            cap.release()

        # Now process all wedges with the color profile
        colors = []

        for wedge_idx in range(num_wedges):
            if self._cancelled:
                return []

            pixels_list = wedge_pixels[wedge_idx]

            if pixels_list:
                all_pixels = np.vstack(pixels_list)
                color = profile.extract(all_pixels)
            else:
                color = (0, 0, 0)

            colors.append(color)

            # Throttle during color processing too
            if sleep_time > 0 and wedge_idx % 10 == 0:
                time.sleep(sleep_time * 5)

            # Report progress (color processing is ~40% of work)
            if progress_callback:
                pct = 60 + (wedge_idx / num_wedges) * 40
                progress_callback(int(pct * num_wedges / 100), num_wedges)

        if progress_callback:
            progress_callback(num_wedges, num_wedges)

        return colors


class PreviewProcessor:
    """Quick preview with fewer wedges."""

    def __init__(self, num_wedges: int = 36, downsample_size: int = 8):
        self.num_wedges = num_wedges
        self.downsample_size = downsample_size

    def extract_colors(
        self,
        video_path: Path,
        profile_type: ProfileType,
        progress_callback: Optional[Callable[[int, int], None]] = None
    ) -> List[Tuple[int, int, int]]:
        processor = VideoProcessor(
            cpu_limit=1.0,  # Full speed for preview
            downsample_size=self.downsample_size
        )
        return processor.extract_colors(
            video_path,
            profile_type,
            num_wedges=self.num_wedges,
            progress_callback=progress_callback
        )
