from datetime import datetime, date
from typing import Optional, Union

from dateutil.parser import isoparse
from dateutil.tz import tzutc
from marshmallow import fields


class StrippedString(fields.Function):   # lgtm [py/missing-call-to-init]
    def __init__(self, *args, **kwargs):
        kwargs['serialize'] = self._strip
        kwargs['deserialize'] = self._strip
        super().__init__(*args, **kwargs)

    def _serialize(self, value, attr, obj, **kwargs) -> Optional[str]:
        if obj.get(attr) is not None:
            return self._strip(obj.get(attr))

    @staticmethod
    def _strip(value: str):
        return value.strip()


class DateTime(fields.Function):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.metadata = {
            'example': datetime.now(tz=tzutc()).isoformat(),
            **(self.metadata or {}),
        }

    def _serialize(self, value, attr, obj, **kwargs) -> Optional[str]:
        value = normalize_datetime(obj.get(attr))
        if value:
            return value.isoformat()

    def _deserialize(self, value, attr, data, **kwargs) -> Optional[datetime]:
        return normalize_datetime(value)


class Date(fields.Function):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.metadata = {
            'example': date.today().isoformat(),
            **(self.metadata or {}),
        }

    def _serialize(self, value, attr, obj, **kwargs) -> Optional[str]:
        value = normalize_datetime(obj.get(attr))
        if value:
            return date(value.year, value.month, value.day).isoformat()

    def _deserialize(self, value, attr, data, **kwargs) -> Optional[date]:
        dt = normalize_datetime(value)
        return date.fromtimestamp(dt.timestamp())


def normalize_datetime(dt: Union[str, date, datetime]) -> Optional[Union[date, datetime]]:
    if not dt:
        return
    if isinstance(dt, datetime) or isinstance(dt, date):
        return dt

    try:
        dt = float(dt)
        return datetime.fromtimestamp(dt)
    except (TypeError, ValueError):
        return isoparse(dt)
