from pathlib import Path
from typing import Tuple

from app.data_structures.time_interval import VideoTimeInterval
from app.exceptions import SubprocessError, VideoCutError
from app.loggers import video_cutter as logger
from app.utils.ffmpeg import ffmpeg_extract_subclip
from app.utils.format import convert_time_to_safe_string, create_safe_string
from app.utils.time import add_margin_to_interval


class VideoCutter:
    """
    Class for cutting video files.
    """

    def __init__(
        self,
        folder: Path,
        margin: int = 0,
    ) -> None:
        self.folder = folder
        self.margin = margin

    def cut(
        self, file_path: Path, time_interval: VideoTimeInterval
    ) -> Tuple[Path, VideoTimeInterval]:
        """
        Cuts video file by given time interval. If margin was set, it will be added to the interval.
        :param file_path: path to the video file to cut
        :param time_interval: time interval to cut
        :return:
        """
        logger.info(
            f"Cutting {file_path} by interval {time_interval} "
            f"with margin {self.margin}s"
        )
        cut_interval = self._prepare_interval(time_interval, margin=self.margin)
        safe_start_time = convert_time_to_safe_string(time_interval.start_time)
        safe_end_time = convert_time_to_safe_string(time_interval.end_time)
        target_path = generate_video_cut_path(
            folder=self.folder,
            original_file_path=file_path,
            start_time=safe_start_time,
            end_time=safe_end_time,
            margin=self.margin,
        )
        try:
            ffmpeg_extract_subclip(
                file_path=str(file_path),
                t1=cut_interval.start_msec / 1000,
                t2=cut_interval.end_msec / 1000,
                target_path=str(target_path),
            )
        except SubprocessError as e:
            raise VideoCutError(f"Error while cutting video: {e}") from e
        return target_path, cut_interval

    @staticmethod
    def _prepare_interval(
        time_interval: VideoTimeInterval, margin: int
    ) -> VideoTimeInterval:
        """
        Adds margin to the time interval.
        :param time_interval: time interval to add margin to
        :param margin: time in seconds to add to the interval
        :return: time interval with margin
        """
        if margin == 0:
            return time_interval
        return add_margin_to_interval(time_interval, margin)


def generate_video_cut_path(
    folder: Path,
    original_file_path: Path,
    start_time: str,
    end_time: str,
    margin: int = 0,
) -> Path:
    """
    Generates a path for the video cut file.
    :param folder: folder to save the cut file
    :param original_file_path: file path of the original video
    :param start_time: start time of the cut
    :param end_time: end time of the cut
    :param margin: time margin of the cut
    :return: path to the cut file
    """
    file_name = "{stem}_{start}-{end}{margin}{ext}".format(
        stem=create_safe_string(original_file_path.stem),
        start=start_time,
        end=end_time,
        margin=f"_margin-{margin}" if margin else "",
        ext=original_file_path.suffix,
    )
    return folder / file_name
