"""
Simple functions for converting from strings to datetype DateTimes.

These all take an optional and ignored second argument
so they can be used as cattrs structure hooks.
"""

import datetime as dt
import re
from typing import cast
import datetype
from datetype import (
    AwareDateTime,
    DateTime,
    NaiveDateTime,
    Date,
    AwareTime,
    NaiveTime,
    Time,
)


# @overload
# def temporal_from_iso(data: str, type_: type[AwareDateTime]) -> AwareDateTime: ...
# @overload
# def temporal_from_iso(data: str, type_: type[DateTime]) -> DateTime: ...
# @overload
# def temporal_from_iso(data: str, type_: type[NaiveDateTime]) -> NaiveDateTime: ...
# @overload
# def temporal_from_iso(data: str, type_: type[Date]) -> Date: ...
# @overload
# def temporal_from_iso(data: str, type_: type[AwareTime]) -> AwareTime: ...
# @overload
# def temporal_from_iso(data: str, type_: type[NaiveTime]) -> NaiveTime: ...
# @overload
# def temporal_from_iso(data: str, type_: type[Time]) -> Time: ...

# def temporal_from_iso[T](data: str, type_: type[T]) -> T:
#     raise NotImplementedError

# TODO: configurable datetime-allowed-from-date?


def dt_from_iso(data: str, _ignored=None, /) -> DateTime:
    "Deserialize a DateTime from an iso-formatted string."

    if not isinstance(data, str):
        raise TypeError(f"Need string not {type(data)} for datetime")

    match re.split(r"[ T]", data):
        case [_date, _time]:
            return datetype.fromisoformat(data)
        case _:
            err = ValueError(
                f"expected to two parts for datetime, date and time, but didn't look right: {data}"
            )
            err.add_note(
                "plain dates are not accepted as datetimes, "
                "add your own struct hook to change this behavior"
            )
            raise err


def naive_dt_from_iso(data: str, _ignored=None, /) -> NaiveDateTime:
    """Deserialize a NaiveDateTime from an iso-formatted string.

    Raises a :exc:`TypeError` if the string had timezone information.
    """
    return datetype.naive(cast(dt.datetime, dt_from_iso(data)))


def aware_dt_from_iso(data: str, _ignored=None, /) -> AwareDateTime:
    """Deserialize an AwareDateTime from an iso-formatted string.

    Raises a :exc:`TypeError` if the string was missing timezone information.
    """
    return datetype.aware(cast(dt.datetime, dt_from_iso(data)))


def time_from_iso(data: str, _ignored=None, /) -> Time:
    if not isinstance(data, str):
        raise TypeError(f"Need string not {type(data)} for time")

    return cast(Time, dt.time.fromisoformat(data))


def aware_time_from_iso(data: str, _ignored=True, /) -> AwareTime:
    return datetype.aware(cast(dt.time, time_from_iso(data)))


# TODO: return type said aware before but mypy didn't complain?


def naive_time_from_iso(data: str, _ignored=True, /) -> NaiveTime:
    return datetype.naive(cast(dt.time, time_from_iso(data)))


def date_from_iso(data: str, _ignored=None, /) -> Date:
    if not isinstance(data, str):
        raise TypeError(f"Need string not {type(data)} for date")

    return cast(Date, dt.date.fromisoformat(data))
