"""
Module for date type implementations
"""
import datetime
import logging
from typing import Union, Tuple

from .model import ValueSupplierInterface, Distribution

_log = logging.getLogger(__name__)


def date_supplier(date_format: str,
                  timestamp_distribution: Distribution,
                  hour_supplier: ValueSupplierInterface = None) -> ValueSupplierInterface:
    """
    Creates a value supplier that provides dates with the given format

    Args:
        date_format: format string for dates
        timestamp_distribution: distribution object that will provide the timestamps that will be formatted
        hour_supplier: supplier for hours to set on each generated date, to restrict dates to certain time periods

    Returns:
        ValueSupplierInterface that supplies dates with the given format
    """
    return _DateSupplier(timestamp_distribution, date_format, hour_supplier)


class _DateSupplier(ValueSupplierInterface):
    """
    Value Supplier implementation for dates
    """

    def __init__(self,
                 timestamp_distribution: Distribution,
                 date_format_string: str,
                 hour_supplier: ValueSupplierInterface = None):
        """
        Args:
            timestamp_distribution: distribution for timestamps
            date_format_string: format string for timestamps
            hour_supplier: supplier for hours to set on each generated date, to restrict dates to certain time periods
        """
        self.date_format = date_format_string
        self.timestamp_distribution = timestamp_distribution
        self.hour_supplier = hour_supplier

    def next(self, iteration):
        random_seconds = self.timestamp_distribution.next_value()
        next_date = datetime.datetime.fromtimestamp(random_seconds)
        if self.hour_supplier:
            next_hour = self.hour_supplier.next(iteration)
            next_date = next_date.replace(hour=int(next_hour))
        if self.date_format:
            return next_date.strftime(self.date_format)
        return next_date.replace(microsecond=0).isoformat()


def uniform_date_timestamp(
        start: str,
        end: str,
        offset: int,
        duration: int,
        date_format_string: str) -> Union[Tuple[None, None], Tuple[int, int]]:
    """
    Creates a uniform distribution for the start and end dates shifted by the offset

    Args:
        start: start date string
        end: end date string
        offset: number of days to shift the duration, positive is back negative is forward
        duration: number of days after start
        date_format_string: format for parsing dates

    Returns:
        Distribution that gives uniform seconds since epoch for the given params
    """
    offset_date = datetime.timedelta(days=offset)
    if start:
        try:
            start_date = datetime.datetime.strptime(start, date_format_string) - offset_date
        except TypeError as err:
            raise ValueError(f"TypeError. Format: {date_format_string}, may not match param: {start}") from err
    else:
        start_date = datetime.datetime.now() - offset_date
    if end:
        # buffer end date by one to keep inclusive
        try:
            end_date = datetime.datetime.strptime(end, date_format_string) \
                       + datetime.timedelta(days=1) - offset_date
        except TypeError as err:
            raise ValueError(f"TypeError. Format: {date_format_string}, may not match param: {end}") from err
    else:
        # start date already include offset, don't include it here
        end_date = start_date + datetime.timedelta(days=abs(int(duration)), seconds=1)

    start_ts = int(start_date.timestamp())
    end_ts = int(end_date.timestamp())
    if end_ts < start_ts:
        _log.warning("End date (%s) is before start date (%s)", start_date, end_date)
        return None, None
    return start_ts, end_ts


