import re
from collections import defaultdict
from urllib.parse import urljoin

from ansi2html import Ansi2HTMLConverter
from django.contrib.auth.models import AbstractUser
from django.urls import reverse
from django.utils.safestring import mark_safe
from lxml.html import Element

from judge import lxml_tree
from judge.models import Contest, Problem, Profile
from judge.ratings import rating_class, rating_progress
from . import registry

rereference = re.compile(r'\[(r?user):(\w+)\]')


def get_user(username, data):
    if not data:
        element = Element('span')
        element.text = username
        return element

    element = Element('span', {'class': Profile.get_user_css_class(*data)})
    link = Element('a', {'href': reverse('user_page', args=[username])})
    link.text = username
    element.append(link)
    return element


def get_user_rating(username, data):
    if not data:
        element = Element('span')
        element.text = username
        return element

    rating = data[1]
    element = Element('a', {'class': 'rate-group', 'href': reverse('user_page', args=[username])})
    if rating:
        rating_css = rating_class(rating)
        rate_box = Element('span', {'class': 'rate-box ' + rating_css})
        rate_box.append(Element('span', {'style': 'height: %3.fem' % rating_progress(rating)}))
        user = Element('span', {'class': 'rating ' + rating_css})
        user.text = username
        element.append(rate_box)
        element.append(user)
    else:
        element.text = username
    return element


def get_user_info(usernames):
    return {name: (rank, rating) for name, rank, rating in
            Profile.objects.filter(user__username__in=usernames)
                   .values_list('user__username', 'display_rank', 'rating')}


def get_user_from_text(text):
    user_list = set()
    for i in rereference.finditer(text):
        user_list.add(text[i.start() + 6: i.end() - 1])
    return Profile.objects.filter(user__username__in=user_list)


reference_map = {
    'user': (get_user, get_user_info),
    'ruser': (get_user_rating, get_user_info),
}


def process_reference(text):
    # text/tail -> text/tail + elements
    last = 0
    tail = text
    prev = None
    elements = []
    for piece in rereference.finditer(text):
        if prev is None:
            tail = text[last:piece.start()]
        else:
            prev.append(text[last:piece.start()])
        prev = list(piece.groups())
        elements.append(prev)
        last = piece.end()
    if prev is not None:
        prev.append(text[last:])
    return tail, elements


def populate_list(queries, list, element, tail, children):
    if children:
        for elem in children:
            queries[elem[0]].append(elem[1])
        list.append((element, tail, children))


def update_tree(list, results, is_tail=False):
    for element, text, children in list:
        after = []
        for type, name, tail in children:
            child = reference_map[type][0](name, results[type].get(name))
            child.tail = tail
            after.append(child)

        after = iter(reversed(after))
        if is_tail:
            element.tail = text
            link = element.getnext()
            if link is None:
                link = next(after)
                element.getparent().append(link)
        else:
            element.text = text
            link = next(after)
            element.insert(0, link)
        for child in after:
            link.addprevious(child)
            link = child


@registry.filter
def reference(text):
    tree = lxml_tree.fromstring(text)
    texts = []
    tails = []
    queries = defaultdict(list)
    for element in tree.iter():
        if element.text:
            populate_list(queries, texts, element, *process_reference(element.text))
        if element.tail:
            populate_list(queries, tails, element, *process_reference(element.tail))

    results = {type: reference_map[type][1](values) for type, values in queries.items()}
    update_tree(texts, results, is_tail=False)
    update_tree(tails, results, is_tail=True)
    return tree


@registry.filter
def item_title(item):
    if isinstance(item, Problem):
        return item.name
    elif isinstance(item, Contest):
        return item.name
    return '<Unknown>'


@registry.function
@registry.render_with('user/link.html')
def link_user(user):
    if isinstance(user, Profile):
        user, profile = user.user, user
    elif isinstance(user, AbstractUser):
        profile = user.profile
    elif type(user).__name__ == 'ContestRankingProfile':
        user, profile = user.user, user
    else:
        raise ValueError('Expected profile or user, got %s' % (type(user),))
    return {'user': user, 'profile': profile}


@registry.function
@registry.render_with('user/link-list.html')
def link_users(users):
    return {'users': users}


@registry.function
@registry.render_with('runtime-version-fragment.html')
def runtime_versions(versions):
    return {'runtime_versions': versions}


@registry.filter(name='absolutify')
def absolute_links(text, url):
    tree = lxml_tree.fromstring(text)
    for anchor in tree.xpath('.//a'):
        href = anchor.get('href')
        if href:
            anchor.set('href', urljoin(url, href))
    return tree


@registry.function(name='urljoin')
def join(first, second, *rest):
    if not rest:
        return urljoin(first, second)
    return urljoin(urljoin(first, second), *rest)


@registry.filter(name='ansi2html')
def ansi2html(s):
    return mark_safe(Ansi2HTMLConverter(inline=True).convert(s, full=False))