from . import command
from .inline_keyboard_utils import generate_keyboard
from telegram import InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardButton
from . import notulenscraper as lib
from collections import defaultdict
import telegram
import logging
import threading
import traceback
from datetime import datetime, timedelta
from pytz import timezone
from html import escape as html_escape
from flask import Flask, request
from cheroot import wsgi
from thefuzz import process
import queue
import _thread
import re
import time

remind_thread = None
webhook_thread = None


EV_STOP = 1
EV_UPDATE = 2
EV_SEND_ADMIN = 3

config = None
trello = lib.get_trello()

logger = logging.getLogger('trello')

LOCAL_TZ = timezone('Europe/Amsterdam')

def generate_tasks_message():
    todos = ""
    progs = ""

    cards = trello.get_cards(fresh=True)
    for card in cards:
        if card.list == 'todo':
            todos += f"`[{card.task_id}]` *{', '.join(card.assignees)}*: {card.name}"
            if (card.due_date):
                todos += f" (op: {card.due_date.strftime('%Y-%m-%d')})"
            todos += "\n"
        if card.list == 'inprogress':
            progs += f"`[{card.task_id}]` *{', '.join(card.assignees)}*: {card.name}"
            if (card.due_date):
                progs += f" (op: {card.due_date.strftime('%Y-%m-%d')})"
            progs += "\n"
    return f"_To do_\n{todos}\n_In progress_\n{progs}"


def update_trello(bea, bot, update):
    query = update.callback_query
    keeb = generate_keyboard(['Update'], ['t;update'])
    try:
        query.edit_message_text(generate_tasks_message(), reply_markup=InlineKeyboardMarkup(keeb), parse_mode='Markdown')
    except telegram.error.BadRequest:
        query.answer()


@command('trello.reminders')
def send_reminders_to_admin(bea, bot, update):
    remind_thread.queue.put(EV_SEND_ADMIN)


@command('trello.todo')
def cards(bea, bot, update):
    keeb = generate_keyboard(['Update'], ['t;update'])
    bea.reply(generate_tasks_message(), parse_mode='Markdown', reply_markup=InlineKeyboardMarkup(keeb))


@command('trello.move ([A-Z]{4}) to ([a-z]+)', pass_groups=True)
def move_to(bea, bot, update, groups):
    task_id, list = groups
    trello.invalidate_card_cache()
    if not (card := trello.get_card(task_id)):
        return bea.reply(f"Unknown task {task_id!r}.")
    if list not in lib.TRELLO_LIST2ID:
        return bea.reply(f"Unknown list {list!r}.")

    card.list = list
    trello.insert_card(card)
    bea.reply(f"Moved card <i>{html_escape(card.name)}</i> to {list}.", parse_mode='HTML')


@command('trello.parse (.+)', pass_groups=True)
def parse(bea, bot, update, groups):
    text = groups[0]
    transaction = lib.parse_minutes(text)
    for _, card in transaction.changes(trello):
        trello.insert_card(card)

def trello_iresult_handler(bea, bot, update):
    task_id = update.chosen_inline_result.result_id.split('-')[-1]
    logger.info(f'inline result handler received result: {task_id}')
    trello.invalidate_card_cache()
    if not (card := trello.get_card(task_id)):
        return
    card.list = 'done'
    trello.insert_card(card)

def process_fn(item):
    if isinstance(item, str):
        return process.utils.full_process(item)
    elif isinstance(item, lib.Card):
        return process.utils.full_process(item.name)

@command('trello-finish(.*)', pass_groups=True, inline=True, prefix=False)
def trello_finish(bea, bot, update, groups):
    if update.inline_query is None:
        return

    match_text = groups[0].lower().strip()

    results = []

    card_matches = []

    if not match_text:
        card_matches = config['last_msg_cards']
    else:
        max_score = 0
        for it, score in process.extract(match_text, config['last_msg_cards'], processor=process_fn, limit=5):
            max_score = max(max_score, score)
            if score > (max_score / 2):
                card_matches.append(it)

    for card in card_matches:
        if card.list == 'done': continue
        result_id = f'trello;finish-card-{card.task_id}'
        title = f"{card.task_id} ({card.name})"
        text = f"Moved card <i>{html_escape(card.name)}</i> to done."
        results.append(InlineQueryResultArticle(
            id=result_id, title=title,
            input_message_content=InputTextMessageContent(text, parse_mode='HTML')))

    update.inline_query.answer(results=results, is_personal=True, cache_time=0)

class TrelloReminder(threading.Thread):
    def __init__(self, bea):
        super().__init__()
        self.bea = bea
        self.bot = bea.bot
        self.trello = lib.get_trello()
        self.running = True
        self.last_reminder_message = None
        self.queue = queue.Queue()

    def get_time_until_next_run(self):
        now = datetime.now(LOCAL_TZ)
        remind_time = now.replace(hour=14, minute=3, second=0)
        if (remind_time < now):
            remind_time += timedelta(days=1)

        wait_time = (remind_time - now).total_seconds()
        return wait_time

    @staticmethod
    def generate_message_from_cards(cards, strike_finished=False):
        cards_by_assignees = defaultdict(list)

        for card in cards:
            if (card.list in ('todo', 'inprogress', 'done' if strike_finished else None)):
                cards_by_assignees[tuple(card.assignees)].append(card)

        if not cards_by_assignees: return ''

        config['last_msg_cards'] = sum(cards_by_assignees.values(), [])

        output = "<b>Herinnering</b>: de deadline voor de volgende taken nadert:\n\n"

        for assignees, cards in cards_by_assignees.items():
                output += f'<b>{", ".join(assignees)}</b>:\n'
                rest_cards = len(cards) - 5
                cards_without_due_date = [c for c in cards if c.due_date is None]
                cards_with_due_date = [c for c in cards if c.due_date is not None]
                for card in (sorted(cards_with_due_date, key=lambda card: card.due_date) + cards_without_due_date)[:5]:
                    if card.due_date:
                        card_text = f"<b>[{card.task_id}]</b> {html_escape(card.name)} <i>(op: {card.due_date.strftime('%Y-%m-%d')})</i>"
                    else:
                        card_text = f"<b>[{card.task_id}]</b> {html_escape(card.name)}"
                    if card.list == 'done':
                        card_text = f"<strike>{card_text}</strike>"
                    output += card_text + "\n"
                if rest_cards > 0:
                    output += f"...en nog {rest_cards} andere taken.\n"
                output += '\n'

        output += 'Als een taak (al) klaar is, versleep hem dan in Trello, of gebruik\n'
        output += '<code>?trello.move ABCD to done</code> 😊'

        return output

    def generate_keyboard(self):
        markup = InlineKeyboardMarkup([[InlineKeyboardButton('Mark card as done', switch_inline_query_current_chat='trello-finish ')]])
        return markup

    def send_new_reminder_message(self, to_admin=False):
        cards = self.trello.get_cards(True)
        output = self.generate_message_from_cards(cards, False)
        mark_done_keyboard = self.generate_keyboard()

        if output:
            if to_admin:
                self.bot.send_message(self.bea.known_users['cyberslak'], output, parse_mode='HTML', reply_markup=mark_done_keyboard)
            else:
                chan = self.bea.known_channels['LIEF Bestuur 14']
                try:
                    self.bot.unpin_chat_message(config['last_msg'][0], message_id=config['last_msg'][1])
                except:
                    pass
                msg = self.bot.send_message(chan, output, parse_mode='HTML', reply_markup=mark_done_keyboard)
                try:
                    msg.pin()
                except:
                    pass
                config['last_msg'] = (msg.chat_id, msg.message_id)

    def update_reminder_message(self):
        cards = []
        msg_cards_by_id = {}

        for card in config['last_msg_cards']:
            msg_cards_by_id[card.trello_id] = card

        for card in self.trello.get_cards(True):
            # allow new cards to be added, but don't list all cards that are done
            # just the ones that have been finished since the message was sent
            if card.trello_id in msg_cards_by_id or card.list in ('todo', 'inprogress'):
                cards.append(card)

        output = self.generate_message_from_cards(cards, True)
        if output:
            chat_id, msg_id = config['last_msg']
            try:
                keeb = self.generate_keyboard()
                self.bot.edit_message_text(output, chat_id, msg_id, parse_mode='HTML', reply_markup=keeb)
            except telegram.error.RetryAfter as e:
                time.sleep(e.retry_after)
                self.update_reminder_message()
            except telegram.error.BadRequest:
                pass

    def do_remind_step(self):
        wait_time = self.get_time_until_next_run()
        try:
            ev = self.queue.get(timeout=wait_time)
        except queue.Empty:
            # no events occurred, so we should simply send a new
            # reminder message.

            # FIXME elke week tijdens de transitieperiode
            if (datetime.now(LOCAL_TZ).day % 7 == 1):
                self.send_new_reminder_message()
        else:
            if ev == EV_STOP:
                _thread.exit()
            if ev == EV_UPDATE:
                self.update_reminder_message()
            if ev == EV_SEND_ADMIN:
                self.send_new_reminder_message(to_admin=True)

    def run(self):
        while True:
            try:
                self.do_remind_step()
            except Exception:
                traceback.print_exc()

class TrelloWebhook(threading.Thread):
    def __init__(self, bea):
        super().__init__()
        self.bea = bea
        self.bot = bea.bot
        self.app = Flask(__name__)
        self.app.add_url_rule('/', '', self.handle_request, methods=["GET", "POST", "HEAD"])
        self.server = wsgi.Server(('127.0.0.1', 8081), self.app)
        self.cv = threading.Condition()
        self.trello = lib.get_trello()

    def run(self):
        logger.info('starting cheroot...')
        self.server.prepare()
        threading.Thread(target=self.server.serve).start()
        self.enable_webhook()
        with self.cv:
            self.cv.wait()
        self.disable_webhook()
        logger.info('stopping cheroot...')
        self.server.stop()

    def enable_webhook(self):
        params = {'description': 'BeaWebhook', 'callbackURL': 'http://trello.segfault.party/', 'idModel': lib.TRELLO_BOARD_ID}
        self.webhook_id = self.trello.post('/webhooks/', **params)['id']

    def disable_webhook(self):
        self.trello.delete(f'/webhooks/{self.webhook_id}')

    def handle_request(self):
        if request.method == 'POST':
            data = request.get_json()
            if data['action']['type'] == 'updateCard':
                remind_thread.queue.put(EV_UPDATE)
        return ''


def start_reminders(bea):
    global remind_thread, webhook_thread

    remind_thread = TrelloReminder(bea)
    remind_thread.start()

    webhook_thread = TrelloWebhook(bea)
    webhook_thread.start()

def fini(bea):
    remind_thread.queue.put(EV_STOP)

    with webhook_thread.cv:
        webhook_thread.cv.notify()


def init(bea, _config):
    global config
    config = _config
    bea.callback_handler['t'] = update_trello
    bea.iresult_handler['trello'] = trello_iresult_handler

    start_reminders(bea)
    return [cards, move_to, parse, send_reminders_to_admin, trello_finish]
