import humanize
import regex
import threading
import logging
from pytz import utc
from datetime import datetime, timedelta
from dateparser.search import search_dates
from collections import namedtuple, defaultdict

Reminder = namedtuple("Reminder", ['channel_id', 'message', 'begints', 'endts'])

dp_settings = {"RETURN_AS_TIMEZONE_AWARE": True, "TIMEZONE": "UTC"}

timers = []
reminders = []

logger = logging.Logger("reminders")

def get_seconds_for_timespan(timespan: str):
    if timespan in ('week', 'wk', 'w'):
        return 604800
    if timespan in ('day', 'd'):
        return 86400
    if timespan in ('hour', 'hr', 'h'):
        return 3600
    if timespan in ('minute', 'min', 'm'):
        return 60
    return 1

TIMESPANS = r"(?:(?P<number>\d+) ?(?P<span>week|wk|w|day|d|hour|hr|h|minute|min|m|second|sec|s)s?)"
SIMPLE_SPAN_RE = regex.compile(r"in {}( ?\+? ?{})*".format(TIMESPANS,
                                                           TIMESPANS), regex.V1)

REMIND_TELL_RE = regex.compile(r"""^
    (?:remind\sme)\s
        (?P<message_with_time>.+)$""",
        regex.X | regex.V1)

def add_reminder(bot, *args):
    reminder = Reminder(*args)
    current_time = datetime.now(utc)
    if (current_time >= reminder.endts):
        raise ValueError("That time already happened.")

    delta = reminder.endts - current_time
    timer = threading.Timer(delta.total_seconds(), remind_handler, args=(bot, reminder))
    timer.reminder = reminder
    timer.start()
    timers.append(timer)
    reminders.append(reminder)
    logger.debug("Added reminder %r (fires in %f seconds)", reminder, delta.total_seconds())

def round_to_seconds(td: timedelta) -> timedelta:
    return timedelta(seconds=round(td.total_seconds()))

def remind_handler(bot, reminder):
    timestring = humanize.naturaltime(round_to_seconds(datetime.now(utc) - reminder.begints))
    output = f"**Reminder from {timestring}**: {reminder.message}"

    bot.send_message(reminder.channel_id, output, parse_mode='Markdown')

def time_select(times): return times[0]

def get_simple_ts(msg):
    m = SIMPLE_SPAN_RE.search(msg)
    logger.debug(f"Found timestamp match: {m}")
    
    if not m: return None
    
    date_parts = [*zip(m.captures('number'), m.captures('span'))]
    length = 0
    for num, span in date_parts:
        length += get_seconds_for_timespan(span) * int(num)

    t = timedelta(seconds=length)
    logger.debug(f"timedelta: {t}")
    # single-element tuple because search_dates returns a single-element list as well.
    return ((m.group(0), datetime.now(utc) + t),)

def remind(bot, update, text):
    m = REMIND_TELL_RE.match(text)
    gr = m.groupdict()
    message_with_time = gr['message_with_time']
    times = get_simple_ts(message_with_time)

    if not times:
        times = search_dates(message_with_time, languages=['en'], settings=dp_settings)

    pronoun = 'you'

    if times:
        cut, endts = time_select(times)
        message = message_with_time.replace(cut, "").strip()
        logger.debug(f"To cut: {cut} End TS: {endts}")

        utcnow = datetime.now(utc)
        if endts < utcnow:
            update.message.reply_text("That's in the past.")
            return

        add_reminder(bot, update.effective_message.chat_id, message, utcnow, endts)

        delta = round_to_seconds(endts - utcnow)
        update.message.reply_text(f"Sure, why the fuck not. Reminding you in {delta}.")
    else:
        update.message.reply_text(f"Can't find a time in that reminder, sorry.")
