from datetime import date, timedelta

from .utils import str_to_date


class BaseValidator:
    """
    Базовый класс валидатора
    """
    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __call__(self, *args, **kwargs):
        return self.validate(*args, **kwargs)

    def validate(self, value):
        raise NotImplementedError("Method validate() must be implemented.")


class MatchValidator(BaseValidator):
    """
    Валидатор соответствия значения
    """
    def __init__(self, value, **kwargs):
        self.value = value

        super().__init__(**kwargs)

    def validate(self, value):
        return self.value == value


class DateRangeValidator(BaseValidator):
    """
    Валидатор вхождения даты в период

    Входные параметры:
        from - дата начала периода
        to - дата окончания периода

        одна из дат может быть не указана,
        тогда будет проверяться либо начало, либо конец периода

    Формат дат: YYYY-MM-DD

    """
    def __init__(self, **kwargs):
        self.from_date = kwargs.pop('from', None)
        if self.from_date is not None:
            self.from_date = str_to_date(self.from_date)

        self.to_date = kwargs.pop('to', None)
        if self.to_date is not None:
            self.to_date = str_to_date(self.to_date)

        super().__init__(**kwargs)

    def validate(self, value: date):
        if self.from_date is None and self.to_date is None:
            return False

        if self.from_date is None:
            return value <= self.to_date
        if self.to_date is None:
            return self.from_date <= value

        return self.from_date <= value <= self.to_date


class PeriodRangeValidator(BaseValidator):
    """
    Валидатор вхождения даты в определенный срок.

    Входные параметры:
        from - кол-во от
        to - кол-во до
        period - размерность: (d)ay, (w)eek, (m)onth, (y)ear. по умолчанию: (d)ay

        может быть указано только одно ограничение.

    Пример:
        from: 1, to: 5, period: 'd'
        дата должна быть старше 1 дня и меньше 5 от текущего момента

        from: 1, to: 3, period: 'y'
        дата должна быть старше 1 года и меньше 3 лет от текущего момента

        from: 1, to: none,
        дата должна быть старше 1 дня от текущего момента

        from: none, to: 5
        дата должна быть не старше 5 дней от текущего момента

    """

    DAY = 'd'
    WEEK = 'w'
    MONTH = 'm'
    YEAR = 'y'

    multipliers = {
        DAY: 1,
        WEEK: 7,
        MONTH: 30,
        YEAR: 365,
    }

    def __init__(self, **kwargs):
        self.period = self.multipliers.get(kwargs.pop('period', self.DAY), 1)

        self.from_days = kwargs.pop('from', None)
        if self.from_days is not None:
            self.from_days = int(self.from_days) * self.period

        self.to_days = kwargs.pop('to', None)
        if self.to_days is not None:
            self.to_days = int(self.to_days) * self.period

        super().__init__(**kwargs)

    def validate(self, value: date):
        today = date.today()

        try:
            from_period = today - timedelta(days=self.from_days) if self.from_days else None
        except OverflowError:
            return False

        try:
            to_period = today - timedelta(days=self.to_days) if self.to_days else None
        except OverflowError:
            to_period = date.min

        if from_period is None and to_period is None:
            return False

        if from_period is None:
            return value >= to_period

        if to_period is None:
            return value <= from_period
        return to_period <= value <= from_period


class SubsetValidator(BaseValidator):
    """
    Валидатор подмножества
    """
    def __init__(self, values, **kwargs):
        if not isinstance(values, (list, set, tuple)):
            values = [values]

        self.values = values

        super().__init__(**kwargs)

    def validate(self, value):
        return any(elem in value for elem in self.values)
