import re
from logging import getLogger

from django.conf import settings
from django.utils.functional import cached_property

from intranet.search.core.query import parse_query
from intranet.search.core.query.ast import Constr
from intranet.search.core.utils.lemmer import generate_query_word_forms


log = getLogger(__name__)


class Hiliter:
    """ Класс для подсветки найденных слов в тексте и сниппетах
    """
    def __init__(self, query, start=settings.HILITE_START_TAG, end=settings.HILITE_END_TAG, save_pure=False):
        self.initial_query = query
        self.start = start
        self.end = end
        self.save_pure = save_pure

    @cached_property
    def query(self):
        query = self.initial_query
        if isinstance(query, str):
            query = parse_query(query, 5)

        # не будем подсвечивать параметры из уточняющей части запроса
        if isinstance(query, Constr):
            query = query.left

        return query

    @cached_property
    def pattern(self):
        to_partial_hilite = [val.text.rstrip('*') for val in self.query.filter_text_nodes()]
        # преобразуем в гигантскую регулярку
        try:
            generated = generate_query_word_forms(self.query)
        except RuntimeError:
            log.exception('Cannot parse query for highlighting')
            generated = []

        # паттерны внутри sub не должны пересекаться, поэтому используем
        # хитрые регуллярки с lookahead и lookbehind
        return re.compile(
            # Точное вхождение подсвечиваем в начале слова или части составного слова
            r'(((?<=\W)|(?<=^)|(?<=-)|(?<=_))'
            r'(?P<orig_word>(?:' +
            '|'.join('%s' % re.escape(word) for word in to_partial_hilite) +
            r'))'
            r'(?=($|\W|\w|-)))'
            # Словоформы подсвечиваем целиком и в полной части составного слова
            r'|(((?<=\W)|(?<=^)|(?<=_)|(?<=-))'
            r'(?P<gen_word>(?:' +
            '|'.join('%s' % re.escape(word) for word in generated) +
            r'))'
            r'(?=($|\W|_|-)))',
            flags=re.IGNORECASE | re.UNICODE,
        )

    def hilite(self, data, fields=None, exclude=('url', )):
        """ Подсвечивает найденные совпадения в сниппете

        :param data: снипеет, который нужно подсветить
        :param fields: поля, которые нужно подсвечивать, если не передано - подсвечивает все строки
        :param exclude: поля, которые нужно исключить из подстветки
        :param save_pure: флаг, обозначающий, что нужно сохранить неподсвеченное значение.
            Оно будет добавлено в словарь рядом с нужным, в поле <field>_pure.
        """
        # рекурсивно обходим данные и подсвечиваем нужные поля
        def rec(data_to_hilite, hilite=False):
            if hasattr(data_to_hilite, 'copy'):
                data = data_to_hilite.copy()
            else:
                data = data_to_hilite

            if isinstance(data, dict):
                return hilite_dict(data, hilite)
            elif isinstance(data, (list, tuple, set)):
                return hilite_list(data, hilite)
            elif isinstance(data, str):
                return hilite_string(data, hilite)
            else:
                return data_to_hilite

        def hilite_string(data, hilite):
            if hilite:
                repl = '{}{}{}'.format(self.start, r'\g<orig_word>\g<gen_word>', self.end)
                return self.pattern.sub(repl, data)
            else:
                return data

        def hilite_dict(data, hilite):
            hilited = {}
            for key, value in data.items():
                if fields and key in fields:
                    need_hilite = True
                elif exclude and key in exclude:
                    need_hilite = False
                else:
                    need_hilite = hilite
                hilited[key] = rec(value, hilite=need_hilite)
                if isinstance(value, str) and need_hilite and self.save_pure:
                    hilited['%s_pure' % key] = value

            return hilited

        def hilite_list(data, hilite):
            return data.__class__([rec(x, hilite) for x in data])

        return rec(data, hilite=fields is None)

    def hilite_whole_string(self, string):
        return f'{self.start}{string}{self.end}'

    def rehilite(self, string, rehilite_tag):
        """ Заменяет rehilite_tag на теги подсветки
        """
        rehilite_start = '<%s>' % rehilite_tag
        rehilite_end = '</%s>' % rehilite_tag
        return string.replace(rehilite_start, self.start).replace(rehilite_end, self.end)


def hilite(query, data, fields=None, start=settings.HILITE_START_TAG, end=settings.HILITE_END_TAG):
    return Hiliter(query, start, end).hilite(data, fields)
