import datetime as dt
from typing import List, Optional, Tuple

from schedint.core.storage import OverridesFile, Request
from schedint.logging_config import get_logger

logger = get_logger("core.validate")


class OverlapError(Exception):
    pass


def _intervals_overlap(
    a_start: dt.datetime,
    a_end: dt.datetime,
    b_start: dt.datetime,
    b_end: dt.datetime,
) -> bool:

    return a_start < b_end and b_start < a_end


def request_effective_window(
    req: Request,
) -> Optional[Tuple[dt.datetime, dt.datetime]]:

    starts: List[dt.datetime] = []
    ends: List[dt.datetime] = []

    for override_window in req.overrides.values():
        if override_window.value is None:
            continue
        if override_window.start is None or override_window.end is None:
            continue
        starts.append(override_window.start)
        ends.append(override_window.end)

    if not starts:
        return None

    return min(starts), max(ends)


def find_overlaps_for_source(
    state: OverridesFile, *, source: str, start: dt.datetime, end: dt.datetime
) -> List[Tuple[str, dt.datetime, dt.datetime]]:

    overlaps: List[Tuple[str, dt.datetime, dt.datetime]] = []

    # Loop through each request in the overrides
    # and determine if the source for which a new
    # request is being made, already has an override
    # applied to it
    for req in state.requests:
        if req.source != source:
            continue

        # Get the effective window for any existing
        # request for that source - this is the earliest
        # window for all overrides for that source
        win = request_effective_window(req)
        if win is None:
            continue
        existing_start, existing_end = win

        if _intervals_overlap(start, end, existing_start, existing_end):
            overlaps.append((req.request_id, existing_start, existing_end))

    return overlaps


def assert_no_overlap_for_source(
    state: OverridesFile, *, source: str, start: dt.datetime, end: dt.datetime
) -> None:

    overlaps = find_overlaps_for_source(
        state, source=source, start=start, end=end
    )
    if overlaps:
        request_id, overlap_start, overlap_end = overlaps[0]
        raise OverlapError(
            f"Rejected override for {source} [{start} -> {end}] overlaps "
            f"existing request {request_id} [{overlap_start} -> {overlap_end}]"
        )
