import logging
import os
from datetime import datetime
from time import mktime

from django.conf import settings
from ids.services.at.repositories.utils import is_club_id

from intranet.search.core.snippets.at import ThreadSnippet
from intranet.search.core.sources.utils import get_text_content, date_as_factor
from intranet.search.core.swarm.indexer import Indexer

from intranet.search.abovemeta.utils import string_to_bool

from . import utils

log = logging.getLogger(__name__)

POST_TYPES = {
    'unfriend': 'Ссора',
    'friend': 'Дружба',
    'status': 'Смена статуса',
    'link': 'Ссылка',
    'poll': 'Опрос',
    'text': 'Пост',
    'summon': 'Призыв',
    'congratulation': 'Поздравление',
}


TRASH_CLUBS = {
    4611686018427388039,        # мозг рака
    4611686018427388026,        # ненависть
    4611686018427387976,        # bbs
    4611686018427388291,        # популярное в этушке
    4611686018427388280,        # трансляция корпблога
    4611686018427388014,        # Новости личного состава
}


def user_posts(uid):
    """ Итератор по постам пользователя по uid """
    for p_type in POST_TYPES:
        url = (settings.ISEARCH['api']['at']['person_posts'].url()
               .format(uid=uid, type=p_type))

        yield from utils.get_posts(url)


def club_posts(club_id):
    """ Итератор по постам клуба по id клуба """
    url = settings.ISEARCH['api']['at']['club_posts'].url().format(
        feed_id=club_id)
    return utils.get_posts(url)


def emit_post_type_factor(post_type, doc):
    """ Создает фаактор "тип поста" """
    if post_type in {'text', 'poll', 'status'}:
        doc.emit_factor('isTextType', 1)
    elif post_type == 'link':
        doc.emit_factor('isLinkType', 1)
    elif post_type == 'photo':
        doc.emit_factor('isPictureType', 1)
    else:
        doc.emit_factor('isTrashType', 1)


def commentators(comments):
    """ Возвращает число уникальных комментаторов """
    if not comments:
        return 0
    return len({reply.get('author', {'login': ''})['login']
                   for reply in comments if reply})


def clean_comments(comments):
    """ Очищает комментарии от лишнего для того чтобы потом добавить их в
    тред. Создает зоны для зонных факторов.

    """

    res = {}

    if not comments:
        return {}

    for comment in comments:
        # urn:ya.ru:comment/1120000000000529/103
        name = 'reply-' + comment['id'].rsplit('/', 1)[1]
        res[name] = body = {}

        for key, value in comment.items():
            if key in {'content', 'title', 'author'}:
                body['reply_' + key] = value

    return res


class Source(Indexer):
    def __init__(self, options):
        super().__init__(options)
        self.uids = utils.UIDs(self.cache_storage)
        self.keys = []

    def do_setup(self, **kwargs):
        if self.options['from_cache']:
            self.next('load')
            return

        if self.options['keys']:
            users = utils.get_staff_users({'login': ','.join(self.options['keys'])})
        else:
            users = utils.get_staff_users()

        for user in users:
            for uid in self.userinfo(user):
                self.next('walk', uid=uid)

    def userinfo(self, user):
        """Создаёт словарь, где ключ это id клуба или uid пользователя,
        а значение это данные про эту сущность (про человека или клуб)

        возвращает список отработанных uid'ов
        """
        keys = []

        self.uids[user['uid']] = utils.get_user_info(user)
        keys.append(int(user['uid']))

        # ISEARCH-4259: почему-то тут по уиду юзера отдается КЛУБ ИМЕНИ ЮЗЕРА
        for club in utils.user_clubs(user['uid']):
            uid = int(utils.get_id(club['id']))
            if uid == int(user['uid']):
                continue
            keys.append(uid)
            self.uids[uid] = self.club_info(club)

        return keys

    def club_info(self, data):
        """ Убирает из ответа этушки про клуб лишнее """
        res = {
            'community_type': data['community_type'],
            'id': data['id'],
            'members_count': data['members_count'],
            'name': data['name'],
            'links': {
                'www': utils.repair_link(data['links']['www']),
                'userpic': data['links']['userpic'],
            },
        }
        return res

    def do_walk(self, uid, **kwargs):
        if is_club_id(uid):
            posts = club_posts(uid)
        else:
            posts = user_posts(uid)

        for post in posts:
            post['is_trash'] = uid in TRASH_CLUBS

            if post.get('access') == 'public' and (post.get('content') or post.get('title')):
                self.next('fetch', uid=uid, post=post)
            else:
                log.info('Private or empty post %s, skip', post.get('id'))

    def do_fetch(self, uid, post, **kwargs):
        [obj_uid, post_id] = list(map(int, post['id'].split('/')[-2:]))
        assert obj_uid == int(uid)

        if post['comments_disabled']:
            comments = []
        else:
            comments = self.flat_comments(
                utils.get_comments(uid, post_id), post['id'])

        self.next('create', type_='thread',
                  uid=uid, post=post, comments=comments)

        for comment in comments:
            self.next('create', type_='comment',
                      uid=uid, comment=comment, post=post)

    def _parse_cache_row(self, row):
        # FIXME У нас в кеше не хватает данных для того, чтобы понять -
        # для поста это или для коммента запись, можно только косвенно определить это:
        # для поста у нас хранится [uid, post, comments], а для коммента: [uid, comment, post]
        # В рамках ISEARCH-5779 будет делаться новый индексатор этушки
        # и там уже стоит начать хранить тип документа в кеше и использовать его.
        is_thread = isinstance(row['raw'][-1], list)
        if is_thread:
            uid, post, comments = row['raw']
            data = dict(type_='thread', uid=uid, post=post, comments=comments)
        else:
            uid, comment, post = row['raw']
            data = dict(type_='comment', uid=uid, comment=comment, post=post)
        return data

    def do_create(self, type_, *args, **kwargs):
        assert type_ in ('thread', 'comment')
        if type_ == 'thread':
            self.index_thread(*args, **kwargs)
        else:
            self.index_comment(*args, **kwargs)

    def flat_comments(self, comments, parent=''):
        """ Наполняет data комментариями """
        data = []

        for comment in comments:
            if comment['author_uid'] is None or not comment:
                continue

            txt_content = get_text_content(
                utils.replace_ya(comment.get('content', ''), self.uids))

            content = {
                'id': comment['id'],
                'content_type': comment.get('content_type', ''),
                'entry_type': comment.get('comment_type', ''),
                'published': utils.parse_date(comment['published']).strftime("%s"),
                'title': comment['title'],
                'content': txt_content,
                'parent': parent,
                'trackback': comment['trackback'],
                'url': comment['links']['alternate'],
            }

            try:
                content['author'] = self.uids[comment['author_uid']]
            except KeyError:
                log.exception('Unknown uid %s', comment['author_uid'])

            data.append(content)

            if comment['replies']:
                data.extend(
                    self.flat_comments(comment['replies'], comment['id']))
        return data

    def post_author(self, post):
        # urn:ya.ru:person/1120000000006275
        try:
            user_uid = utils.get_id(post['author']['id'])
            return self.uids[user_uid]
        except KeyError:
            return {}

    def index_thread(self, uid, post, comments):
        real_url = utils.repair_link(post['links']['alternate'])

        updated = utils.parse_date(post['updated'])
        edited = utils.parse_date(post.get('edited', post['published']))
        published = utils.parse_date(post['published'])
        mod_time = edited.strftime('%s')
        post_updated = updated.strftime('%Y-%m')

        if 'author' not in post:
            post['author'] = {'id': "urn:ya.ru:person/%s" % uid}

        doc = self.create_document(real_url, edited)
        doc.emit_group_attr('thread_id_grp', post['id'],
                            label=post['title'],
                            url=real_url)
        doc.emit_search_attr('s_type', 'post')
        doc.emit_factor('isRepost', int(bool(post.get('in-reply-to'))))
        doc.emit_factor('isTrashClub', int(post['is_trash']))

        tags = []
        post_type = 'text'
        for tag in post['categories']:
            if tag['scheme'] == 'urn:ya.ru:posttypes':
                post_type = tag['term']
            tags.append(tag['term'])

        emit_post_type_factor(post_type, doc)
        doc.emit_facet_attr('post_updated', post_updated, label=post_updated)

        if comments:
            doc.emit_factor('lastCommentDate', date_as_factor(updated))

            lifetime = mktime(updated.timetuple()) - mktime(published.timetuple())
            # нормализуем двумя годами
            doc.emit_factor('postLifetime', lifetime / 63072000.0)

            doc.emit_factor('commentatorsCount', commentators(comments) / 1000.0)

            trackbacks = sum(1 for c in comments if c['trackback'])

            doc.emit_factor('repostsCount', trackbacks / 100.0)
        else:
            trackbacks = 0

        doc.emit_factor('postDate', date_as_factor(edited))
        doc.emit_factor('isThread', 1)

        # лайки
        if post.get('likes') and post['likes'][0]:
            ltotal = int(post['likes'][0]['total'])
            lcount = int(post['likes'][0]['count'])
            likes = (ltotal + lcount) / 2
            dislikes = (ltotal - lcount) / 2
        else:
            likes = dislikes = 0

        doc.emit_factor('likesCount', likes)
        doc.emit_factor('dislikesCount', dislikes)

        post_content = get_text_content(
            utils.replace_ya(post['content'], self.uids))

        # получаем автора
        author = self.post_author(post)
        if author:
            doc.emit_facet_attr('post_author', author['login'],
                                label=author['name'])
        else:
            log.error("Can't find author for post %s", post['id'])

        body = {
            'post_content': post_content,
            '!hidden': {
                'mod_time': mod_time,
                'post_title': post['title'],
                'post_tags': tags,
                'comments': clean_comments(comments),
                'post_author': author or '',
            },
        }

        snippet = {
            'url': doc.url,
            'description': '',
            'title': post['title'],
            'updated': edited,
            'tags': tags,
            'entry_type': post_type,
            'type': 'post',
            'content': post_content[:300],
            'author': author,
            'comments_count': len(comments),
            'likes': likes,
            'dislikes': dislikes,
            'reposts_count': trackbacks,
        }

        if is_club_id(uid):
            club_info = self.uids[uid]
            body['!hidden']['club_name'] = club_info['name']
            doc.emit_factor('membersCount',
                            (club_info.get('members_count') or 0) / 10000.0)
            doc.emit_factor('inClub', 1)
            doc.emit_facet_attr('club', utils.get_id(club_info['id']),
                                label=club_info['name'])

            snippet['club'] = club_info

            if string_to_bool(os.getenv('ISEARCH_ENABLE_AT_SUGGEST')):
                doc.emit_suggest_attr(club_info['name'])

        doc.emit_factor('authorQu', author.get('qu', utils.DEFAULT_QU) / 100.0)
        if string_to_bool(os.getenv('ISEARCH_ENABLE_AT_SUGGEST')):
            doc.emit_suggest_attr(post['title'])
            doc.emit_suggest_attr(author)
        doc.emit_body(body)
        doc.emit_snippet(ThreadSnippet(snippet), 'ru')

        self.next('content', url=real_url, raw_data=[uid, post, comments], updated_ts=doc.updated_ts)
        self.next('store', document=doc, body_format='json')

    def index_comment(self, uid, comment, post):
        if not comment:
            return

        # urn:ya.ru:comment/1120000000000296/1650/1655
        [id_, post_no, parent_id] = list(map(int, comment['id'].split('/')[-3:]))
        assert id_ == int(uid)

        post_info = {
            'author': self.post_author(post),
            'post_updated': utils.parse_date(post['updated']).strftime('%Y-%m'),
            'is_trash': post['is_trash'],
        }

        if is_club_id(uid):
            post_info['club'] = self.uids[uid]

        mod_time = comment['published']

        doc = self.create_document(comment['url'], datetime.fromtimestamp(int(mod_time)))

        doc.emit_group_attr('thread_id_grp', f'urn:ya.ru:post/{uid}/{post_no}',
                            url=utils.repair_link(post['links']['alternate']))

        doc.emit_facet_attr('post_updated', post_info['post_updated'],
                            label=post_info['post_updated'])
        doc.emit_search_attr('s_type', 'comment')
        doc.emit_factor('isTrashClub', int(post_info['is_trash']))

        if 'club' in post_info:
            doc.emit_facet_attr('club', utils.get_id(post_info['club']['id']),
                                label=post_info['club']['name'])

        if 'author' in post_info and post_info['author']:
            doc.emit_facet_attr('post_author', post_info['author']['login'],
                                label=post_info['author']['name'])

        body = {
            '!hidden': {
                'reply_' + i: comment[i]
                for i in ['title', 'author']
                if i in comment
            },
            'reply_content': comment.get('content', ''),
        }

        doc.emit_body(body)

        # факторы
        post_type = comment.get('entry_type')
        emit_post_type_factor(post_type, doc)

        if 'author' in comment:
            doc.emit_factor('authorQu',
                            comment['author'].get('qu', utils.DEFAULT_QU) / 100.0)

        doc.emit_factor('likesCount', comment.get('like', 0))
        doc.emit_factor('dislikesCount', comment.get('dislike', 0))
        doc.emit_factor('postDate', date_as_factor(comment['published']))
        doc.emit_factor('isThread', 0)
        doc.emit_factor('isRepost', int(bool(comment.get('trackback'))))

        snippet = comment.copy()
        snippet['content'] = snippet['content'][:150]
        snippet['type'] = 'comment'

        doc.emit_snippet(snippet, 'ru')

        self.next('content', url=doc.url, raw_data=[uid, comment, post], updated_ts=doc.updated_ts)
        self.next('store', document=doc, body_format='json')

    def do_push(self, data, delete=False, **kwargs):
        if delete:
            url = data['links']['alternate']
            doc = self.create_document(url)
            self.next('content', url=url, delete=True)
            self.next('store', document=doc, delete=True)
        else:
            url = settings.ISEARCH['api']['at']['resource'].url(query=data)
            data = utils.get_data(url, _ok_statuses=(200, 403, 404))

            if data is None:
                log.warning('Cannot get resource %s', url)
                return

            uid, post_id = utils.parse_long_id(data['id'])

            data['is_trash'] = uid in TRASH_CLUBS
            if data.get('access', '') == 'public' and (data.get('content') or data.get('title')):
                self.next('fetch', uid=uid, post=data)
