import logging
from typing import TypeVar, Dict
from datetime import datetime, timedelta

logger = logging.getLogger("desktopenv.getters.misc")

R = TypeVar("Rule")

day_of_week_mapping = {
    0: 'Mon',
    1: 'Tue',
    2: 'Wed',
    3: 'Thu',
    4: 'Fri',
    5: 'Sat',
    6: 'Sun'
}

month_mapping = {
    1: 'Jan',
    2: 'Feb',
    3: 'Mar',
    4: 'Apr',
    5: 'May',
    6: 'Jun',
    7: 'Jul',
    8: 'Aug',
    9: 'Sep',
    10: 'Oct',
    11: 'Nov',
    12: 'Dec'
}

Month_Mapping_Full = {
    1: "January",
    2: "February",
    3: "March",
    4: "April",
    5: "May",
    6: "June",
    7: "July",
    8: "August",
    9: "September",
    10: "October",
    11: "November",
    12: "December"
}

month_mapping_full = {
    1: 'january',
    2: 'february',
    3:'march',
    4: 'april',
    5:'may',
    6: 'june',
    7: 'july',
    8: 'august',
    9:'september',
    10: 'october',
    11: 'november',
    12: 'december'
}

relativeTime_to_IntDay = {
    "tomorrow": 1,
    "5th next month": "special",
    "10th next month": "special",
    "11th next month": "special",
    "this month": "special",
    "this Saturday": "special",
    "this Sunday": "special",
    "next Monday": "special",
    "next Friday": "special",
    "first monday four months later": "special"
}

async def get_rule(env, config: Dict[str, R]) -> R:
    """
    Returns the rule as-is.
    """
    return config["rules"]

async def get_rule_relativeTime(env, config: Dict[str, R]) -> R:
    """
    According to the rule definded in funciton "apply_rules_to_timeFormat", convert the relative time to absolute time.
    config:
        'relativeTime': {
            "from": must exist; indicates the relativeTime.
            "to": optional; indicates the relativeTime.
        } 
        If relativeTime only has key "from", then the key of time in "expected" dict must be "time".
        If relativeTime has key "to", then the key of time in "expected" dict must be "from" and "to". 
    """
    relativeRules = config["rules"]
    relativeTime = relativeRules["relativeTime"] # int, "+" means future, "-" means past
    # get the date now
    now = datetime.now()
    # calculate the relative time
    if "to" not in relativeTime.keys():
        start_relative_time = relativeTime["from"] 
        if relativeTime_to_IntDay[start_relative_time] != "special":
            # relativeTime can be represented by actual int days
            start_relative_time_IntDat = relativeTime_to_IntDay[start_relative_time]
            timediff = timedelta(days=start_relative_time_IntDat) 
            absoluteDay = now + timediff
        else:
            # special case, you can add more special cases here
            if start_relative_time == "5th next month":
                next_year = now.year + 1 if now.month == 12 else now.year
                next_month = now.month + 1 if now.month < 12 else 1
                next_day = 5
                absoluteDay = datetime(next_year, next_month, next_day)
            elif start_relative_time == "10th next month":
                next_year = now.year + 1 if now.month == 12 else now.year
                next_month = now.month + 1 if now.month < 12 else 1
                next_day = 10
                absoluteDay = datetime(next_year, next_month, next_day)
            elif start_relative_time == "this month":
                absoluteDay = now
            elif start_relative_time == "next Monday":
                absoluteDay = now + timedelta(days=((6-now.weekday())+1))
            elif start_relative_time == "first monday four months later":
                next_year = now.year + 1 if now.month >=9 else now.year
                next_month = (now.month + 4)%12
                # get the first monday of the next_month
                temp_date = datetime(next_year, next_month, 1)
                absoluteDay = temp_date + timedelta(days=((6-temp_date.weekday())+1)%7)
        regular_time = apply_rules_to_timeFormat(relativeRules["expected"]["time"], absoluteDay)
        config["rules"]["expected"]["time"] = regular_time

    else:
        from_time = relativeTime["from"]
        to_time = relativeTime["to"]
        # deal with from_time first
        if relativeTime_to_IntDay[from_time] != "special":
            from_time_IntDat = relativeTime_to_IntDay[from_time]
            from_timediff = timedelta(days=from_time_IntDat) 
            from_absoluteDay = now + from_timediff
        else:
            if from_time == "this Saturday":
                from_absoluteDay = now + timedelta(days=(5-now.weekday()))
            elif from_time == "10th next month":
                next_year = now.year + 1 if now.month == 12 else now.year
                next_month = now.month + 1 if now.month < 12 else 1
                next_day = 10
                from_absoluteDay = datetime(next_year, next_month, next_day)
            elif from_time == "next Monday":
               from_absoluteDay = now + timedelta(days=((6-now.weekday())+1)) 
            else:
                pass # more rules here
        regular_from_time = apply_rules_to_timeFormat(relativeRules["expected"]["from"], from_absoluteDay)
        config["rules"]["expected"]["from"] = regular_from_time

        # deal with to_time
        if relativeTime_to_IntDay[to_time] != "special":
            to_time_IntDat = relativeTime_to_IntDay[to_time]
            to_timediff = timedelta(days=to_time_IntDat) 
            to_absoluteDay = now + to_timediff
        else:
            if to_time == "this Sunday":
                to_absoluteDay = now + timedelta(days=(6-now.weekday()))
            elif to_time == "11th next month":
                next_year = now.year + 1 if now.month == 12 else now.year
                next_month = now.month + 1 if now.month < 12 else 1
                next_day = 11
                to_absoluteDay = datetime(next_year, next_month, next_day)
            elif to_time == "next Friday":
                if now.weekday() < 4 and from_time in ["next Monday"]:
                    to_absoluteDay = now + timedelta(days=((4-now.weekday())+7))
                else:
                    to_absoluteDay = now + timedelta(days=((4-now.weekday()) if now.weekday() < 4 else (6-now.weekday()) + 5))
            else:
                pass # more rules here
        regular_to_time = apply_rules_to_timeFormat(relativeRules["expected"]["to"], to_absoluteDay)
        config["rules"]["expected"]["to"] = regular_to_time
            
    return config["rules"]


async def apply_rules_to_timeFormat(timeFormat: str, absoluteDay: datetime):
    timeFormat = timeFormat.replace("{DoW}", day_of_week_mapping[absoluteDay.weekday()], 1)
    timeFormat = timeFormat.replace("{Month}", month_mapping[absoluteDay.month], 1)
    timeFormat = timeFormat.replace("{DayD}", str(absoluteDay.day), 1)
    timeFormat = timeFormat.replace("{Year}", str(absoluteDay.year), 1)
    timeFormat = timeFormat.replace("{Month0D}", "0"+str(absoluteDay.month) if absoluteDay.month < 10 else str(absoluteDay.month), 1)
    timeFormat = timeFormat.replace("{month}", month_mapping_full[absoluteDay.month], 1)
    timeFormat = timeFormat.replace("{MonthFull}", Month_Mapping_Full[absoluteDay.month], 1)
    timeFormat = timeFormat.replace("{Day0D}", "0"+str(absoluteDay.day) if absoluteDay.day < 10 else str(absoluteDay.day), 1)
    # you can add other replace rules here

    return timeFormat


async def get_accessibility_tree(env, *args) -> str:
    accessibility_tree: str = await env.controller.get_accessibility_tree()
    logger.debug("AT@eval: %s", accessibility_tree)
    return accessibility_tree

async def get_time_diff_range(env, config) -> str:
    try:
        return config["diff_range_in_minutes"]
    except:
        logger.error("diff_range_in_minutes not found in config.")
        return None