"""Helpers for CLI argument str to timedelta."""

import re
from datetime import datetime, timedelta

import typer
from pytz import UTC

STR_TO_TIMEDELTA_PATTERN = re.compile(r"^([0-9]+) *([a-z]+)$")


STR_TO_TIMEDELTA_UNITS = [
    "s",
    "second",
    "seconds",
    "min",
    "minute",
    "minutes",
    "h",
    "hour",
    "hours",
    "d",
    "day",
    "days",
    "w",
    "week",
    "weeks",
]
STR_TO_TIMEDELTA_HELP = f"'<number>[optional space]<unit>' (where unit one of: {', '.join(STR_TO_TIMEDELTA_UNITS)})"


def str_to_timedelta(duration: str) -> timedelta:
    """Convert a str to a timedelta object.

    This assumes <count:int>[<space>]<unit:str>
    Where unit is one of: s/second/seconds min/minute/minutes h/hour/hours d/day/days w/week/weeks

    Raises ValueError for non convertible str.
    """
    match = STR_TO_TIMEDELTA_PATTERN.match(duration.strip())
    if not match:
        raise ValueError(f"Invalid duration: {duration}")
    count = int(match.group(1))
    unit = match.group(2)
    if unit in ["s", "second", "seconds"]:
        return timedelta(seconds=count)
    if unit in ["m", "minute", "minutes"]:
        return timedelta(minutes=count)
    if unit in ["h", "hour", "hours"]:
        return timedelta(hours=count)
    if unit in ["d", "day", "days"]:
        return timedelta(days=count)
    if unit in ["w", "week", "weeks"]:
        return timedelta(weeks=count)
    raise ValueError(f"Invalid duration unit: {duration}")


def validate_timedelta_str(ctx: typer.Context, param: typer.CallbackParam, value: str) -> str:  # noqa: ARG001
    """Validate a textual timedelta (used for typer arguments)."""
    try:
        str_to_timedelta(value)
    except ValueError as e:
        # raise typer.BadParameter(f"Invalid --{param.name} value {value!r}") from e
        raise typer.BadParameter(
            f"{value!r} is not a valid duration. A duration must be of the format: {STR_TO_TIMEDELTA_HELP}"
        ) from e
    return value


DATETIME_FORMATS = [
    "%Y-%m-%d",
    "%Y-%m-%dT%H:%M",
    "%Y-%m-%d %H:%M",
]


def validate_datetime_str(value: str) -> datetime:  # noqa: ARG001
    """Validate a textual datetime (used for typer arguments)."""
    for datetime_format in DATETIME_FORMATS:
        try:
            return datetime.strptime(value, datetime_format).astimezone()
        except ValueError:  # noqa: PERF203
            pass
    raise typer.BadParameter(f"{value!r} does not match any of the formats: {', '.join(DATETIME_FORMATS)}")


def duration_or_datetime(value: str) -> datetime:  # noqa: ARG001
    """Parse a textual timedelta or datetime (used for typer arguments).

    When a timedelta is detected, the result is the current datetime plus the timedelta.
    """

    try:
        td = str_to_timedelta(value)
        return datetime.now(UTC).replace(second=0, microsecond=0) + td
    except ValueError:
        pass

    try:
        return validate_datetime_str(value)
    except typer.BadParameter:
        pass

    raise typer.BadParameter(
        f"{value!r} is not a date, nor a duration.\n"
        f"A date must match any of the formats: {', '.join(DATETIME_FORMATS)}.\n"
        f"A duration must be of the format: {STR_TO_TIMEDELTA_HELP}"
    )
