from . import command, ensure_config, reply_fn
from .inline_keyboard_utils import generate_keyboard
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
import logging
import traceback
from collections import defaultdict
from html import escape
import uuid

logger = logging.getLogger('poll')

config = None

def poll_reply(update):
    poll_message = update.message.reply_to_message
    if not poll_message or not poll_message.poll:
        raise ValueError

    return poll_message.poll


@command('copy poll', additional_match_fn=poll_reply)
def pollcopy(bea, bot, update, poll):
    """ Copy a poll. Send this in reply to a poll to send the same poll, again. """
    opts = [opt.text for opt in poll.options]
    update.message.reply_poll(poll.question, opts, poll.is_anonymous, poll.type, poll.allows_multiple_answers)


def update_poll(bea, bot, update):
    query = update.callback_query
    logger.info('update_poll called')

    button_data = query.data[2:]
    option_id = int(button_data)

    poll_id = config['cmid_to_pid'].get((query.message.chat_id, query.message.message_id), None)
    if not poll_id:
        logger.warning(f'Unable to find poll id for cmid: {query.message.chat_id}, {query.message.message_id}')
        query.answer()
        return

    poll = config['polls'][poll_id]

    user = query.from_user

    if user.id in poll['voted_for'][option_id]:
        # rescind vote
        poll['voted_for'][option_id].remove(user.id)
    else:
        # new vote
        already_voted = any(user.id in voters for voters in poll['voted_for'])
        if already_voted and not poll['is_multi']:
            query.answer()
            return
        else:
            poll['voted_for'][option_id].add(user.id)

    update_poll_messages(bea, bot, poll_id)
    query.answer()


def update_poll_messages(bea, bot, poll_id, keeb_changed=False):
    poll = config['polls'][poll_id]
    mids = config['pid_to_cmids'][poll_id]

    text, keeb = generate_poll_message(bea, poll_id, poll)

    invalid_cmids = []

    for (chat_id, mid) in mids:
        try:
            bot.edit_message_text(text=text, chat_id=chat_id, message_id=mid, reply_markup=keeb, parse_mode='HTML')
        except:
            traceback.print_exc()
            invalid_cmids.append((chat_id, mid))

    for cmid in invalid_cmids:
        config['pid_to_cmids'][poll_id].remove(cmid)


def generate_poll_message(bea, poll_id, poll):
    text = f"<b>{poll['title']}</b>" + '\n\n'
    for option_id, voters in enumerate(poll['voted_for']):
        # <unknown> should never occur; how did they vote without having their name registered?
        names = [bea.known_names.get(voter, '<unknown>') for voter in sorted(voters)]
        text += f'{escape(poll["options"][option_id])} \u2014 ({", ".join(names) or "no votes"})\n'

    keeb = generate_keyboard(poll['options'], [f'?;{id}' for id in range(len(poll['options']))])
    if poll['is_ext']:
        button = InlineKeyboardButton(text='Add option', switch_inline_query_current_chat=f'?add poll option to {poll_id}: ')
        keeb.append([button])
    return (text, InlineKeyboardMarkup(keeb))


@command('add poll option(?: to ([a-z0-9-]+))?: (.+)', pass_groups=True, inline=True)
def add_poll_option(bea, bot, update, groups):
    """ Add a new option to a poll. If the poll is marked user-extensible, any user can add an option. """
    if (update.inline_query is not None):
        bea.inline_reply_text('Click here to add the option!', f'?add poll option to {groups[0]}: {groups[1]}')
        return

    if groups[0]:
        poll_id = groups[0]
    else:
        try:
            reply = reply_fn(update)
        except ValueError:
            return

        poll_id = config['cmid_to_pid'].get((reply.chat.id, reply.message_id))
        if not poll_id:
            return bea.reply('Dat is geen poll.')

    poll = config['polls'].get(poll_id)
    if not poll:
        return bea.reply('Dat is geen poll.')

    option = escape(groups[1])

    if not poll['is_ext'] and not poll['creator'] == update.effective_user.id:
        return bea.reply('You are not authorized to add an option to that poll.')

    poll['options'].append(option)
    poll['voted_for'].append(set())

    update_poll_messages(bea, bot, poll_id, keeb_changed=True)

    try:
        update.message.delete()
    except:
        pass


@command('send poll: (.+)', pass_groups=True)
def send_poll(bea, bot, update, groups):
    poll_id = groups[0]
    poll = config['polls'].get(poll_id)
    if not poll:
        return bea.reply('Dat is geen valide poll-id.')

    text, keeb = generate_poll_message(bea, poll_id, poll)
    msg = bea.reply(text, reply_markup=keeb, parse_mode='HTML')

    config['cmid_to_pid'][(msg.chat_id, msg.message_id)] = poll_id
    config['pid_to_cmids'][poll_id].append((msg.chat_id, msg.message_id))


@command('new (user-extensible )?(multi)?poll: (.+); options ([^;]+(?:;[^;]+)*)', pass_groups=True)
def new_poll(bea, bot, update, groups):
    is_ext = groups[0] is not None
    is_multi = groups[1] is not None
    title = groups[2]
    options = [opt for opt in groups[3].split(';')]
    poll = {'options': options,
            'title': escape(title),
            'is_ext': is_ext,
            'creator': update.effective_user.id,
            'is_multi': is_multi,
            'voted_for': [set() for i in range(len(options))]}

    poll_id = str(uuid.uuid4())
    config['polls'][poll_id] = poll

    bea.reply(f"Constructed your poll. To send it in a chat, use ?send poll: {poll_id}")


def init(bea, config_):
    global config
    ensure_config(config_, polls={}, cmid_to_pid={}, pid_to_cmids=defaultdict(list))
    config = config_

    bea.callback_handler['?'] = update_poll

    return [new_poll, send_poll, add_poll_option, pollcopy]
