import datetime
import re 
import pandas as pd
from .timezones import convert_datetime_date_in_timezone
from .date_commons import (DATAIKU_ISO_8601_UTC_DATE_PATTERN,
                           DATAIKU_ISO_8601_DATE_FORMAT_IN_UTC,
                           DATAIKU_ISO_8601_DATE_FORMAT_WITH_TIME_ZONE)


def from_string_date_to_datetime(string_date: str, date_format: str="%Y-%m-%d", target_timezone: str="UTC"):
    """
    Converts a string datetime value into a datetime object.

    :param: string_date: str: The string date to convert.
    :param: string_date: str: Format of the string date to convert.
    :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'.
    """
    datetime_date = datetime.datetime.strptime(string_date, date_format)
    datetime_date = convert_datetime_date_in_timezone(datetime_date, target_timezone)
    return datetime_date


def from_dss_string_date_to_datetime(dss_string_date, target_timezone="UTC"):
    """
    Converts a dataiku DSS 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 convert_date_to_datetime(date: any, target_timezone="UTC"):
    """
    Converts a date into a datetime object.

    :param: date: any: The date to convert. Supported formats are: 
        - datetime.datetime
        - pd._libs.tslibs.timestamps.Timestamp
        - str with formats:
            - '%Y-%m-%d'
            - Dataiku non-ambiguous dates formats, with ISO-8601 dates 
            (https://doc.dataiku.com/dss/latest/preparation/dates.html#meanings-and-types)
                - '%Y-%m-%dT%H:%M:%S.%fZ' # Example: '2013-05-30T15:16:13.764Z'
                - '%Y-%m-%dT%H:%M:%S.%f%z' # Example: '2013-05-30T15:16:13.764+0200'
    :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 initial date.'.
    """
    ALLOWED_DATE_TYPES = [datetime.datetime, pd._libs.tslibs.timestamps.Timestamp, str]
    if isinstance(date, datetime.datetime):
        datetime_date = date

    elif isinstance(date, str):
        if (len(date) <= 10):
            datetime_date = from_string_date_to_datetime(date, "%Y-%m-%d", target_timezone)
        else:
            datetime_date = from_dss_string_date_to_datetime(date, target_timezone)
    
    elif isinstance(date, pd._libs.tslibs.timestamps.Timestamp):
        datetime_date = from_pandas_timestamp_to_datetime_date(date, target_timezone)
    
    else:
        log_message = f"The date you set is '{date}' and has a type or format that is not supported by this function. "\
        f"Allowed types are {ALLOWED_DATE_TYPES}. "\
        f"Allowed date formats for strings are '%Y-%m-%d', '{DATAIKU_ISO_8601_DATE_FORMAT_IN_UTC}' and "\
        f"'{DATAIKU_ISO_8601_DATE_FORMAT_WITH_TIME_ZONE}"
        raise ValueError(log_message)

    return datetime_date


def from_datetime_to_dss_string_date(datetime_date, target_timezone="UTC"):
    """
    Converts a datetime object into a dataiku DSS string datetime value.

    :param: datetime_date:  datetime.datetime: The datetime date to convert.
    :param: target_timezone: str: Timezone where to convert the date. 
        Supported timezones are the ones listed in pytz using 'pytz.all_timezones'.
    
    :returns: dss_string_date: str: DSS string date associated with the 'datetime_date'.
    """
    datetime_date = convert_datetime_date_in_timezone(datetime_date, target_timezone)
    datetime_information = datetime_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] # We remove the end of the string to get 'milliseconds' resolution instead of 'microseconds'.
    timezone_information = datetime_date.strftime("%z")
    dss_string_date = datetime_information + timezone_information
    return dss_string_date


def from_datetime_date_to_pandas_timestamp(datetime_date):
    """
    Converts a datetime object into pandas timestamp.

    :param: datetime_date:  datetime.datetime: The datetime date to convert.
    :returns: pandas_timestamp: pandas._libs.tslibs.timestamps.Timestamp: pandas timestamp associated with 'datetime_date'.
    """
    pandas_timestamp = pd.Timestamp(datetime_date)
    return pandas_timestamp


def from_pandas_timestamp_to_datetime_date(pandas_timestamp):
    """
    Converts a pandas timestamp into a datetime object.
    :param: pandas_timestamp:  pandas._libs.tslibs.timestamps.Timestamp: The datetime date to convert.
    :returns: datetime_date: datetime.datetime: datetime date associated with 'pandas_timestamp'.
    """
    datetime_date = pandas_timestamp.to_pydatetime()
    return datetime_date