from datetime import timedelta
from typing import List

from app.data_structures.motion_detection import MotionDetectionResult
from app.data_structures.time_interval import VideoTimeInterval


def get_timedelta_from_milliseconds(msec: float) -> timedelta:
    """
    Converts milliseconds to timedelta.
    :param msec: milliseconds
    :return: timedelta object
    """
    if msec < 0:
        return timedelta(0)
    return timedelta(milliseconds=msec)


def add_margin_to_interval(
    interval: VideoTimeInterval, margin_sec: int
) -> VideoTimeInterval:
    """
    Adds margin to the interval.
    For example, if interval is 10-20 seconds and margin is 5 seconds, the result will be 5-25 seconds.
    :param interval: interval to add margin to
    :param margin_sec: margin in seconds
    :return: interval object with margin
    """
    if not margin_sec:
        return interval
    margin_msec = margin_sec * 1000
    if interval.start_msec > margin_msec:
        start_msec = interval.start_msec - margin_msec
    else:
        start_msec = 0
    return VideoTimeInterval.from_msec(start_msec, interval.end_msec + margin_msec)


def filter_small_intervals(
    motion_intervals: List[MotionDetectionResult], min_interval_length: int
) -> List[MotionDetectionResult]:
    """
    Filters small intervals from the list of motion intervals.
    :param motion_intervals: list of motion intervals
    :param min_interval_length: minimum length of the interval in seconds
    :return: list of motion intervals without small intervals
    """
    return [
        interval
        for interval in motion_intervals
        if interval.interval.duration >= timedelta(seconds=min_interval_length)
    ]


def are_close_intervals(ts1: float, ts2: float, threshold: int) -> bool:
    """
    Checks if two intervals are close with each other.
    :param ts1: timestamp of the first interval
    :param ts2: timestamp of the second interval
    :param threshold: threshold in milliseconds
    :return: True if intervals are close, False otherwise
    """
    return abs(ts1 - ts2) <= threshold


def join_near_intervals(
    motion_intervals: List[MotionDetectionResult], interval_threshold: int
) -> List[MotionDetectionResult]:
    """
    Recursively joins motion intervals that timestamps are close with each other.
    :param motion_intervals: motion intervals
    :param interval_threshold: threshold for joining intervals (seconds)
    :return: list of joined intervals
    """
    # if there are no intervals - return empty list
    if not motion_intervals:
        return []
    # if there is only one interval - return it
    if len(motion_intervals) == 1:
        return motion_intervals
    result = []
    # sort intervals by start time
    motion_intervals.sort(key=lambda x: x.interval.start_msec)
    for motion_interval in motion_intervals:
        # if intervals are not near with each other - add interval to result
        if not result or not are_close_intervals(
            result[-1].interval.end_msec,
            motion_interval.interval.start_msec,
            interval_threshold * 1000,
        ):
            result.append(motion_interval)
        # else merge intervals
        else:
            # merge the two intervals
            result[-1] = MotionDetectionResult(
                motions=result[-1].motions + motion_interval.motions,
                interval=VideoTimeInterval.from_msec(
                    start_msec=result[-1].interval.start_msec,
                    end_msec=motion_interval.interval.end_msec,
                ),
            )
    return result
