"""Datetime-related helper functions"""

import calendar
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta
from typing import Literal, get_args

import dateutil.parser
from dateutil.relativedelta import relativedelta

PeriodScale = Literal["recent", "day", "week", "month"]


@dataclass
class TimeRangeInfo:
    date: datetime
    min_date: datetime
    max_date: datetime
    navigation_dates: dict[PeriodScale, tuple['date', 'date', 'date']]
    range_desc: str
    scale: PeriodScale


def get_minmax_ts(
    input_date: date, scale: PeriodScale, round_to: timedelta = timedelta(minutes=30)
) -> tuple[datetime, datetime]:
    match scale:
        case "recent":
            # ignore date
            today_as_datetime = datetime.combine(datetime.utcnow().date(), datetime.min.time())
            now_as_timedelta = datetime.utcnow() - today_as_datetime
            round_factor = int(round_to.total_seconds())
            rounded_seconds = round(now_as_timedelta.total_seconds() / round_factor) * round_factor
            rdt = today_as_datetime + timedelta(seconds=rounded_seconds)

            return rdt - timedelta(days=1), rdt

        case "day":

            return (
                datetime.combine(input_date, time.min),
                datetime.combine(input_date, time.max),
            )

        case "week":
            input_date = beginning_of_week(input_date)

            return (
                datetime.combine(beginning_of_week(input_date), time.min),
                datetime.combine(next_week(input_date), time.min),
            )

        case "month":
            input_date = input_date.replace(day=1)
            last_day = calendar.monthrange(input_date.year, input_date.month)[1]

            return (
                datetime.combine(input_date.replace(day=1), time.min),
                datetime.combine(input_date.replace(day=last_day), time.max),
            )

        case _:
            raise Exception("Invalid scale")


def surrounding_dates_for_scale(input_date: date, scale: PeriodScale) -> tuple[date, date, date]:
    match scale:
        case "recent":  # not meaningful
            delta = relativedelta(hours=24)

        case "day":
            delta = relativedelta(days=1)

        case "week":
            input_date = beginning_of_week(input_date)
            delta = relativedelta(days=7)

        case "month":
            input_date = input_date.replace(day=1)
            delta = relativedelta(months=1)

        case _:
            raise Exception("Invalid scale")

    return input_date - delta, input_date + delta, input_date


def date_range_description(input_date: datetime, scale: PeriodScale) -> str:
    match scale:
        case "recent":
            return "the past 24 hours"

        case "day":
            return input_date.strftime("%B %d, %Y")

        case "week":
            min_date = beginning_of_week(input_date)
            max_date = next_week(input_date)

            return f"{min_date.strftime("%B %d, %Y")} - {max_date.strftime("%B %d, %Y")}"

        case "month":
            return input_date.strftime("%B %Y")


def beginning_of_week(input_date: date) -> date:
    days_to_monday = input_date.weekday() - 1 if input_date.weekday() != 0 else 6

    return input_date - timedelta(days=days_to_monday)


def next_week(input_datetime: date) -> date:
    return beginning_of_week(input_datetime + timedelta(days=7))


def parse_scale_string(date_string: str | None, scale: PeriodScale = "recent") -> tuple[TimeRangeInfo, bool]:
    valid_date = True

    if date_string is None:
        parsed_date = datetime.now()
    else:
        try:
            parsed_date = dateutil.parser.parse(date_string)
        except dateutil.parser.ParserError:
            parsed_date = datetime.now()
            valid_date = False

    nav: dict[PeriodScale, tuple[date, date, date]] = {}

    for period in get_args(PeriodScale):
        nav[period] = surrounding_dates_for_scale(parsed_date.date(), period)

    desc = date_range_description(parsed_date, scale)

    (min_date, max_date) = get_minmax_ts(parsed_date, scale)

    return TimeRangeInfo(parsed_date, min_date, max_date, nav, desc, scale), valid_date
