import re
import datetime
import pytz
from dateutil.relativedelta import relativedelta


# Dataiku non-ambiguous dates formats, with ISO-8601 dates
# as documented in https://doc.dataiku.com/dss/latest/preparation/dates.html#meanings-and-types:

## Pattern to detect a dataiku ISO_8601 date in UTC:
DATAIKU_ISO_8601_UTC_DATE_PATTERN = ".+Z$"

## Format of a Dataiku ISO_8601 date:
DATAIKU_ISO_8601_DATE_FORMAT_IN_UTC = "%Y-%m-%dT%H:%M:%S.%fZ" # # Example: '2013-05-30T15:16:13.764Z'

## Format of a Dataiku ISO_8601 date that has a timezone:
DATAIKU_ISO_8601_DATE_FORMAT_WITH_TIME_ZONE= "%Y-%m-%dT%H:%M:%S.%f%z" # Example: '2013-05-30T15:16:13.764+0200'


def list_pytz_timezones():
    """
    Lists all the timezones that we can interact with in pytz.

    :returns: pytz_timezones: list: List of all pytz timezones.
    """
    pytz_timezones = pytz.all_timezones
    return pytz_timezones


def check_timezone_is_defined_in_pytz(focus_timezone):
    """
    Checks whether a time zone is defined or not in pytz.

    :param: focus_timezone: str: Timezone to check. 
    """
    pytz_timezones = list_pytz_timezones()
    if focus_timezone not in pytz_timezones:
        log_message = f"You asked for using the time zone '{focus_timezone}' that is not defined in pytz. "\
        f"Please choose a time zone in '{pytz_timezones}'"
        raise Exception(log_message)
    pass

# Timezones dates conversions:
def convert_datetime_date_in_timezone(datetime_date, target_timezone="UTC"):
    """
    Converts a datetime date in another timezone.

    :param: datetime_date: datetime.datetime: The datetime date to convert in another timezone.
    :param: target_timezone: str: Timezone where to convert the date.
        Supported timezones are the ones listed in pytz using 'pytz.all_timezones'.
    :returns: datetime_date_in_timezone: datetime.datetime: 'datetime_date' converted in 'target_timezone'.
    """
    check_timezone_is_defined_in_pytz(target_timezone)
    datetime_date_in_timezone = datetime_date.astimezone(pytz.timezone(target_timezone))
    return datetime_date_in_timezone


def compute_antecedent_date(reference_date, time_unit, time_value):
    if time_unit=="years":
        time_delta = relativedelta(years=-time_value)
        
    elif time_unit=="months":
        time_delta = relativedelta(months=-time_value)
        
    elif time_unit=="weeks":
        time_delta = relativedelta(weeks=-time_value)
        
    elif time_unit=="days":
        time_delta = relativedelta(days=-time_value)
        
    elif time_unit=="hours":
        time_delta = relativedelta(hours=-time_value)
        
    antecedent_date = reference_date + time_delta
    return antecedent_date
    

def from_dss_string_date_to_datetime(dss_string_date, target_timezone="UTC"):
    """
    Converts a string datetime value into a datetime object.

    :param: dss_string_date: str: The string date to convert. This date might be formated with 
        dataiku non-ambiguous dates format, also known as 'ISO-8601'.
    :param: target_timezone: str: Timezone where to convert the date. 
        Supported timezones are the ones listed in pytz using 'pytz.all_timezones'.
    
    :returns: datetime_date: datetime.datetime: datetime date associated with the 'dss_string_date'.
    """
    if re.match(DATAIKU_ISO_8601_UTC_DATE_PATTERN, dss_string_date):
        date_format = DATAIKU_ISO_8601_DATE_FORMAT_IN_UTC
    else:
        date_format = DATAIKU_ISO_8601_DATE_FORMAT_WITH_TIME_ZONE
    
    datetime_date = datetime.datetime.strptime(dss_string_date, date_format)
    datetime_date = convert_datetime_date_in_timezone(datetime_date, target_timezone)
    return datetime_date


def fatten_time_value(time_value):
    str_time_value = str(time_value)
    if len(str_time_value) <2:
        return "0{}".format(str_time_value)
    else:
        return str_time_value
    

def from_datetime_to_dss_string_date(datetime_value):
    year = datetime_value.year
    month = fatten_time_value(datetime_value.month)
    day = fatten_time_value(datetime_value.day)
    hour = fatten_time_value(datetime_value.hour)
    minute = fatten_time_value(datetime_value.minute)
    return "{}-{}-{}T{}:{}:00.000Z".format(year, month, day, hour, minute)


class datesFilteringManager():
    
    def __init__(self, dates_filtering_strategy, filtering_reference_date, filtering_time_unit,
                 filtering_time_frame, oldest_date, newest_date):
        
        self.dates_filtering_strategy = dates_filtering_strategy
        self.filtering_reference_date = self.preprocess_dss_string_date(filtering_reference_date)
        self.filtering_time_unit = filtering_time_unit
        self.filtering_time_frame = filtering_time_frame
        self.oldest_date = self.preprocess_dss_string_date(oldest_date)
        self.newest_date = self.preprocess_dss_string_date(newest_date)
    
    def preprocess_dss_string_date(self, dss_string_date):
        if dss_string_date is not None:
            return from_dss_string_date_to_datetime(dss_string_date)
        else:
            return None
    
    def recompute_filtering_parameters(self):
        if self.dates_filtering_strategy == "keep_transactions_before_and_until_today":
            self.newest_date = datetime.datetime.now()
            self.oldest_date = compute_antecedent_date(self.newest_date,
                                                       self.filtering_time_unit,
                                                       self.filtering_time_frame)
        
        elif self.dates_filtering_strategy == "keep_transactions_before_and_until_a_past_date":
            self.newest_date = self.filtering_reference_date
            self.oldest_date = compute_antecedent_date(self.newest_date,
                                                       self.filtering_time_unit,
                                                       self.filtering_time_frame)
        
        elif self.dates_filtering_strategy == "keep_transactions_before_a_reference_date":
            self.newest_date = self.filtering_reference_date
            self.oldest_date = from_dss_string_date_to_datetime("1900-01-01T00:00:00.000Z")
        
        elif self.dates_filtering_strategy == "keep_transactions_after_a_reference_date":
            self.newest_date = from_dss_string_date_to_datetime("2999-12-31T23:59:59.999Z")
            self.oldest_date = self.filtering_reference_date
        
        elif self.dates_filtering_strategy == "keep_all_transactions":
            self.oldest_date = from_dss_string_date_to_datetime("1900-01-01T00:00:00.000Z")
            self.newest_date = from_dss_string_date_to_datetime("2999-12-31T23:59:59.999Z")
        pass