#!/usr/bin/env python
# -*- coding: utf-8 -*-

#  SPDX-FileCopyrightText: 2019-2022 Alliander N.V.
#  SPDX-License-Identifier: MPL-2.0

from datetime import datetime, timedelta
from typing import Optional, Union

import numpy as np
import pandas as pd
from fastapi import HTTPException
from loguru import logger
from pytz import UTC


def parse_datetime(
    datetime_string,
    round_missing_time_up=False,
    round_to_days=False,
    raise_errors=False,
    loc=None,
) -> Optional[datetime]:
    if datetime_string is None:
        return None

    dt = pd.to_datetime(datetime_string, dayfirst=False, errors="coerce")

    if pd.isnull(dt):
        logger.exception("Error while parsing datetime string", input=datetime_string)
        if raise_errors:
            # Note: replace when FastAPI supports Pydantic models to define query parameters
            # (meaning Validators can be used)
            error_msg = {
                "loc": loc,
                "msg": "invalid datetime format",
                "type": "type_error.datetime",
            }
            raise HTTPException(status_code=422, detail=[error_msg])

        dt = None

    if dt is not None and (round_missing_time_up or round_to_days) and time_unknown(dt, datetime_string):
        if round_to_days:
            dt = dt + timedelta(days=1)
        else:
            dt = dt.replace(hour=23, minute=59, second=59)

    if dt is not None:
        dt = np.datetime64(dt).astype(datetime)

    return dt


def time_unknown(dt: datetime, datetime_string: str):  # pragma: no cover
    if dt.hour == 0 and dt.minute == 0 and dt.second == 0 and ":" not in datetime_string:
        return True
    return False


def validate_begin_and_end(
    start: datetime,
    end: datetime,
    data_start: Union[datetime, None] = None,
    data_end: Union[datetime, None] = None,
):
    """Checks the given date parameters and replaces them with default values if they aren't valid.
    The resulting values are then returned.
    """
    start = start.astimezone(UTC) if start else None
    end = end.astimezone(UTC) if end else None
    data_start = data_start.astimezone(UTC) if data_start else None
    data_end = data_end.astimezone(UTC) if data_end else None

    if data_end is None:
        # Assuming predictions fill in this value, the most recent value for the past is before "now".
        data_end = datetime.now(UTC)

    if data_start is not None and data_start > start:
        # If the starting moment lies before what can be requested, put it at the moment from which it can be requested
        start = data_start

    if data_end is not None and data_end < end:
        end = data_end

    if start >= data_end:
        raise HTTPException(
            422,
            f"Invalid [start] value [{start}]: value lies after last available moment for model ({data_end})",
        )
    if data_start is not None and end <= data_start:
        raise HTTPException(
            422,
            f"Invalid [end] value [{end}]: value lies before first available moment for model ({data_start})",
        )

    if end < start:
        raise HTTPException(
            422,
            f"Invalid [start] and [end] values: [end]({end}) lies before [start]({start})",
        )

    return start, end
