# pylint: disable=import-outside-toplevel,too-many-locals

import logging
import os
import json
from typing import List, Tuple
import cv2
import numpy as np
from cv2.typing import MatLike

logger = logging.getLogger(__name__)


def get_frames_from_dir(frames_dir: str, include_frame_index: bool = True) -> List[Tuple]:
    """
    Get frame paths from pre-extracted frames directory.

    Supports loading frame metadata from frames_metadata.json (created by video_to_images)
    or falls back to reading frame files directly.

    Args:
        frames_dir: Directory containing pre-extracted frames
        include_frame_index: If True, returns (frame_path, timestamp, frame_index) tuples.
                           If False, returns (frame_path, timestamp) tuples.

    Returns:
        List of tuples with frame information
    """
    metadata_path = os.path.join(frames_dir, "frames_metadata.json")
    if os.path.exists(metadata_path):
        with open(metadata_path, "r", encoding="utf-8") as f:
            metadata = json.load(f)
            if include_frame_index:
                return [
                    (
                        os.path.join(frames_dir, frame["filename"]),
                        frame["timestamp"],
                        frame["frame_index"],
                    )
                    for frame in metadata["frames"]
                ]
            return [
                (
                    os.path.join(frames_dir, frame["filename"]),
                    frame["timestamp"],
                )
                for frame in metadata["frames"]
            ]

    frames = sorted(
        [
            os.path.join(frames_dir, f)
            for f in os.listdir(frames_dir)
            if f.startswith("frame") and f.lower().endswith(".png")
        ]
    )
    if include_frame_index:
        return [(frame_path, float(idx), idx) for idx, frame_path in enumerate(frames)]
    return [(frame_path, float(idx)) for idx, frame_path in enumerate(frames)]


def video_to_images(video_path: str, output_folder: str, num_frames: int = 30) -> None:
    """
    Convert a video to a sequence of images and save them to the output folder.

    Also saves frame metadata (timestamps, indices) to frames_metadata.json.

    Parameters:
    video_path (str): The path to the video file.
    output_folder (str): The path to the folder to save the images to.
    num_frames (int): Number of frames to extract (default: 30).

    """
    os.environ["IMAGEIO_FFMPEG_EXE"] = os.environ.get("IMAGEIO_FFMPEG_EXE", "ffmpeg")
    import warnings

    with warnings.catch_warnings():
        warnings.filterwarnings("ignore")
        from moviepy import VideoFileClip

    os.makedirs(output_folder, exist_ok=True)
    clip = VideoFileClip(video_path)
    duration = clip.duration

    times = np.linspace(0, duration, num_frames, endpoint=False)

    frames_metadata = {"duration": duration, "num_frames": num_frames, "frames": []}

    for frame_idx, t in enumerate(times):
        frame = clip.get_frame(t)

        frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        timestamp = f"{t:.2f}s"

        font = cv2.FONT_HERSHEY_SIMPLEX
        position = (10, 30)
        font_scale = 1
        color = (0, 255, 255)
        thickness = 2

        frame_bgr = cv2.putText(
            frame_bgr,
            timestamp,
            position,
            font,
            font_scale,
            color,
            thickness,
            cv2.LINE_AA,
        )

        frame_filename = f"frame{frame_idx:04d}.png"
        out_path = os.path.join(output_folder, frame_filename)
        cv2.imwrite(out_path, frame_bgr)

        frames_metadata["frames"].append(
            {
                "filename": frame_filename,
                "frame_index": frame_idx,
                "timestamp": float(t),
            }
        )

    metadata_path = os.path.join(output_folder, "frames_metadata.json")
    with open(metadata_path, "w", encoding="utf-8") as f:
        json.dump(frames_metadata, f, indent=2)

    logger.info(f"Saved frame metadata to {metadata_path}")


def _are_frames_similar(frame: MatLike, prev_frame: MatLike, diff_threshold: int) -> bool:
    gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    gray_prev_frame = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
    frame_diff = cv2.absdiff(gray_frame, gray_prev_frame)
    mean_diff = np.mean(frame_diff)
    return mean_diff < diff_threshold


def eliminate_duplicates(folder_path: str, diff_threshold=25) -> None:
    frame_files = sorted(
        [f for f in os.listdir(folder_path) if f.startswith("frame") and f.endswith(".png")],
        key=lambda x: int(x[5:-4]),
    )

    if not frame_files:
        logger.debug("No frames found in the folder.")
        return

    prev_frame = None
    keep_counter = 0
    removed_count = 0

    for _, frame_file in enumerate(frame_files):
        frame_path = os.path.join(folder_path, frame_file)
        frame = cv2.imread(frame_path)

        if frame is None:
            logger.warning(f"Could not read {frame_file}. Skipping.")
            continue

        remove_frame = False
        if prev_frame is not None:
            if _are_frames_similar(frame, prev_frame, diff_threshold):
                if keep_counter < 4:
                    remove_frame = True
                    keep_counter += 1

        if remove_frame:
            logger.debug(f"Removing duplicate frame: {frame_file}")
            os.remove(frame_path)
            removed_count += 1
        else:
            prev_frame = frame
            keep_counter = 0

    if removed_count > 0:
        logger.info(f"Removed {removed_count} duplicate frames from {folder_path}")
