from itertools import chain, islice, cycle
import datetime
from collections import Counter
from copy import deepcopy

from gm_types.gaia import DIARY_ORDER_TYPE
from gm_types.doris import ANSWER_SORT_TYPE
from gm_types.doris import ARTICLE_SORT_TYPE
from gm_types.mimas import CONTENT_CLASS
from gm_types.doris import CARD_TYPE
from gm_types.gaia import CITY_LEVEL
from gm_rpcd.all import bind
import traceback
from search.utils.diary import recall_diary
from search.utils.answer import recall_answers
from search.utils.article import recall_articles
from search.utils.service import recommed_service_category_device_id_v2, recommed_service_category_device_id
from gm_rpcd.all import context
from libs.algorithms import drop_dup
from libs.cache import redis_client, redis_client2
from libs.error import logging_exception
import time

from extend.models.gaia import City, CityScale
from extend.models.gold import (
    QAQueue,
    WikiQueue,
    IconQueue,
    UserTopicQueue,
    DoctorTopicQueue,
    DiaryQueue,
    ArticleQueue,
    AnswerQueue,
    DeviceQAQueue,
    DeviceIconQueue,
    DeviceUserTopicQueue,
    DeviceDoctorTopicQueue,
    DeviceAnswerQueue,
    DeviceArticleQueue,
    DeviceDiaryQueue,
    QuestionQueue,
    DeviceQuestionQueue
)
import logging
import redis
import json
from django.conf import settings
import traceback
from recommend.utils.diary_portrait import fetch_diary_by_user_portrait, get_explore_tags_diary_id, get_device_portrait_list
from recommend.utils.diary_portrait import fetch_qa_by_user_portrait
from recommend.utils.diary_portrait import fetch_topic_by_user_portrait

MAX_LOAD = 200
logger = logging.getLogger(__name__)


@bind("doris/recommend/get_diaries")
def get_diaries(tags, city, offset=0, size=10, city_tag_id=None):
    # NOTE: city as city id
    sort_params = {}

    if city_tag_id:
        sort_params["user_city_tag_id"] = city_tag_id

    elif city:
        try:
            x = City.objects.get(id=city)
            sort_params["user_city_tag_id"] = x.tag_id
        except City.DoesNotExist:
            pass

    filters = {
        "is_sink": False,
        "has_before_cover": True,
        "has_after_cover": True,
        "content_level_is_good": True
    }
    if tags:
        filters["closure_tag_ids"] = tags

    tail = offset + size
    diaries_ids = []
    if tail < MAX_LOAD:
        diaries = recall_diary(None, 0, 200, filters, DIARY_ORDER_TYPE.RECOMMEND, sort_params, fields=["id", "user.id"])
        diaries_items = [(diary['id'], diary['user']['id']) for diary in diaries]
        drop_dup_diaries = drop_dup(diaries_items)
        drop_dup_size = len(drop_dup_diaries)
        if tail <= drop_dup_size:
            diaries_ids = [item[0] for item in drop_dup_diaries[offset:tail]]
    if len(diaries_ids) == 0:  # 如果头200条去重结束 后面的排序不去重
        diaries = recall_diary(None, offset, size, filters, DIARY_ORDER_TYPE.RECOMMEND, sort_params, fields=["id"])
        diaries_ids = [diary['id'] for diary in diaries]

    return {"diaries_ids": diaries_ids}


@bind("doris/recommend/get_articles")
def get_articles(tags, offset=0, size=10):
    filters = {
        "content_level": [CONTENT_CLASS.EXCELLENT, CONTENT_CLASS.FINE]
    }
    if tags:
        filters["tag_ids"] = tags

    articles = recall_articles(None, offset, size, filters, ARTICLE_SORT_TYPE.RECOMMEND, {})
    article_ids = [article['id'] for article in articles]
    return {"article_ids": article_ids}


@bind("doris/recommend/get_answers")
def get_answers(tags, offset=0, size=10):
    filters = {
        "content_level": [CONTENT_CLASS.EXCELLENT, CONTENT_CLASS.FINE]
    }

    if tags:
        filters["tag_ids"] = tags
    tail = offset + size
    answer_ids = []
    if tail < MAX_LOAD:
        answers = recall_answers(None, 0, MAX_LOAD, filters, ANSWER_SORT_TYPE.RECOMMEND, {}, fields=["id", "user_id"])
        answers = filter(lambda answer: "id" in answer and "user_id" in answer, answers)
        answer_items = [(answer["id"], answer["user_id"]) for answer in answers]
        drop_dup_answers = drop_dup(answer_items)
        if tail <= len(drop_dup_answers):
            answer_ids = [item[0] for item in drop_dup_answers[offset:tail]]
    if len(answer_ids) == 0:
        answers = recall_answers(None, offset, size, filters, ANSWER_SORT_TYPE.RECOMMEND, {})
        answer_ids = [answer['id'] for answer in answers]
    return {"answer_ids": answer_ids}

@bind('doris/recommend/icon')
def fetch_icon(device_id, size):
    try:
        card_type = "icon"
        try:
             que = DeviceIconQueue.objects.get(device_id=device_id)
        except DeviceIconQueue.DoesNotExist:
             que = IconQueue.objects.last()
        if not que:
            return {"icon": []}
        que = list(filter(None, que.queue.split(',')))

        # adjust args.
        cursor = 0
        cursor = int(cursor) % len(que)
        size = min(size, len(que))
        data = list(islice(cycle(que), cursor, cursor + size))
        return {card_type: list(map(int, data))}
    except:
        logging_exception()
        return {"icon": []}

@bind('doris/recommend/homepage_polymer')
def fetch_polymer_ids(device_id, size):
    try:
        card_type = "polymer_ids"
        try:
            que = DeviceIconQueue.objects.get(device_id=device_id)
        except DeviceIconQueue.DoesNotExist:
            que = IconQueue.objects.last()
        if not que:
            return {"polymer_ids": []}
        que = list(filter(None, que.queue.split(',')))

        # adjust args.
        cursor = 0
        cursor = int(cursor) % len(que)
        size = min(size, len(que))
        data = list(islice(cycle(que), cursor, cursor + size))
        return {card_type: list(map(int, data))}
    except:
        logging_exception()
        return {"polymer_ids": []}

@bind('doris/recommend/feed')
def recommend_feed(device_id, card_type, city_id, size):
    try:
        return RecommendFeed.dispatch(device_id, card_type,
                                      city_id, size)
    except:
        logging_exception()
        return {card_type: [], "cpc_ids":[]}

class RecommendFeed:
    @classmethod
    def dispatch(cls, device_id, card_type, city_id, size):
        data = []
        cpc_ids = []
        time_begin = time.time()
        if card_type == CARD_TYPE.QA:
            data = cls.fetch_qa(device_id, card_type, size)
            logging.info("duan add test,fetch_qa cost:%f,device_id:%s" % ((time.time() - time_begin), str(device_id)))
        elif card_type == CARD_TYPE.ANSWER:
            data = cls.fetch_answer(device_id, card_type, size)
            data = list(map(int, data))
            logging.info("duan add test,fetch_answer cost:%f" % (time.time() - time_begin))
        elif card_type == CARD_TYPE.ARTICLE:
            data = cls.fetch_article(device_id, card_type, size)
            data = list(map(int, data))
        elif card_type == CARD_TYPE.QUESTION:
            data = cls.fetch_question(device_id, card_type, size)
            data = list(map(int, data))
        elif card_type == CARD_TYPE.DIARY:
            total = cls.fetch_diary(device_id, card_type, city_id, size)
            if total:
                data = total[0]
                cpc_ids = total[1]
            logging.info("duan add test,fetch_diary cost:%f,device_id:%s" % ((time.time() - time_begin),str(device_id)))
        elif card_type == CARD_TYPE.USERTOPIC:
            data = cls.fetch_user_topic(device_id,card_type,size)
            logging.info("duan add test,fetch_user_topic cost:%f,device_id:%s" % ((time.time() - time_begin), str(device_id)))
        elif card_type == CARD_TYPE.DOCTORTOPIC:
            data = cls.fetch_doctor_topic(device_id,card_type,size)
            data = list(map(int, data))
        elif card_type == CARD_TYPE.ENCYCLOPEDIA:
            data = cls.fetch_wiki(device_id,card_type,size)
        return {card_type: data, "cpc_ids":cpc_ids}

    @staticmethod
    def current_date():
        return datetime.datetime.now().strftime('%Y-%m-%d')

    @staticmethod
    def fetch_question(device_id, card_type, size):
        key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                      card_type=card_type, date=RecommendFeed.current_date())
        try:
            que = DeviceQuestionQueue.objects.get(device_id=device_id)
        except DeviceQuestionQueue.DoesNotExist:
            que = QuestionQueue.objects.last()

        que = list(filter(None, que.queue.split(',')))

        # adjust args.
        cursor = redis_client.get(key) or 0
        cursor = int(cursor) % len(que)
        size = min(size, len(que))

        redis_client.set(key, cursor + size, ex=24 * 60 * 60)
        return list(islice(cycle(que), cursor, cursor + size))

    @staticmethod
    def fetch_icon(device_id, card_type, size):
        key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                      card_type=card_type, date=RecommendFeed.current_date())
        try:
            que = DeviceIconQueue.objects.get(device_id=device_id)
        except DeviceIconQueue.DoesNotExist:
            que = IconQueue.objects.last()

        que = list(filter(None, que.queue.split(',')))

        # adjust args.
        cursor = redis_client.get(key) or 0
        cursor = int(cursor) % len(que)
        size = min(size, len(que))

        redis_client.set(key, cursor + size, ex=24 * 60 * 60)
        return list(islice(cycle(que), cursor, cursor + size))

    @staticmethod
    def fetch_wiki(device_id, card_type, size):
        try:
            key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                      card_type=card_type, date=RecommendFeed.current_date())

            que = WikiQueue.objects.last()
            if not que:
                return []
            # que = list(filter(None, que.queue.split(',')))
            que = json.loads(que.queue)
            # adjust args.
            cursor = redis_client.get(key) or 0
            cursor = int(cursor) % len(que)
            size = min(size, len(que))
            redis_client.set(key, cursor + size, ex=24 * 60 * 60)
            return list(islice(cycle(que), cursor, cursor + size))

        except:
            logging_exception()
            return []

    @staticmethod
    def fetch_answer(device_id, card_type, size):
        try:
            key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                          card_type=card_type, date=RecommendFeed.current_date())
            try:
                que = DeviceAnswerQueue.objects.get(device_id=device_id)
            except DeviceAnswerQueue.DoesNotExist:
                que = AnswerQueue.objects.last()
            if not que:
                return []
            que = list(filter(None, que.queue.split(',')))

            # adjust args.
            cursor = redis_client.get(key) or 0
            cursor = int(cursor) % len(que)
            size = min(size, len(que))

            redis_client.set(key, cursor + size, ex=24 * 60 * 60)
            return list(islice(cycle(que), cursor, cursor + size))
        except:
            logging_exception()
            return []

    @staticmethod
    def fetch_qa(device_id, card_type, size):
        try:
            def read_history(cid_list):
                read_qa_key = "TS:recommend_answer_set:device_id:" + str(device_id)
                old_qa_key = "TS:recommend_answer_set:device_id:{}:{}" \
                    .format(device_id, (datetime.date.today() - datetime.timedelta(days=14)).strftime("%Y-%m-%d"))
                today_qa_key = "TS:recommend_answer_set:device_id:{}:{}" \
                    .format(device_id, datetime.date.today().strftime("%Y-%m-%d"))
                redis_client.sadd(today_qa_key, *cid_list)
                redis_client.expire(today_qa_key, 14 * 24 * 60 * 60)
                redis_client.sadd(read_qa_key, *cid_list)
                if redis_client.exists(old_qa_key):
                    redis_client.sdiffstore(read_qa_key, read_qa_key, old_qa_key)
                    redis_client.delete(old_qa_key)

                redis_client.expire(read_qa_key, time=13 * 24 * 60 * 60)

            def no_filter_qa(device_id, size):
                if recommed_service_category_device_id_v2(device_id):
                    coldstart_qa_queue_key = "coldstart:light:clinic:beauty:qa:queue:grey"
                    device0_coldstart_qa_cursor = "coldstart:light:clinic:beauty:qa:queue:grey:cursor"
                else:
                    coldstart_qa_queue_key = "coldstart:light:clinic:beauty:qa:queue"
                    device0_coldstart_qa_cursor = "coldstart:light:clinic:beauty:qa:queue:cursor"

                coldstart_cursor = redis_client2.get(device0_coldstart_qa_cursor)
                if coldstart_cursor:
                    coldstart_cursor = int(coldstart_cursor)
                else:
                    coldstart_cursor = 0

                result = redis_client2.lrange(coldstart_qa_queue_key, coldstart_cursor, coldstart_cursor + size - 1)
                result = [int(qa_id) for qa_id in result]
                if coldstart_cursor + size > redis_client2.llen(coldstart_qa_queue_key) - size:
                    redis_client2.set(device0_coldstart_qa_cursor, 0)
                else:
                    redis_client2.set(device0_coldstart_qa_cursor, coldstart_cursor + size)
                return result


            if device_id != '0':
                read_qa_key = "TS:recommend_answer_set:device_id:" + str(device_id)
                read_list = []
                if redis_client.exists(read_qa_key):
                    read_list = [int(x) for x in list(redis_client.smembers(read_qa_key))]
                data = fetch_qa_by_user_portrait(device_id, read_list, size)
                if data:
                    read_history(data)
                if len(data) >= size:
                    return data
                else:
                    size = size - len(data)
                    logger.info("portrait_fetch_qa:supplement1:device_id:{0}:size:{1}".format(device_id, size))

                supplements_qa = no_filter_qa(device_id, size)
                read_history(supplements_qa)
                data.extend(supplements_qa)
                return data
            else:
                data = no_filter_qa(device_id, size)
                return data

        except:
            logging_exception()
            return []

    @staticmethod
    def fetch_article(device_id, card_type, size):
        key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                      card_type=card_type, date=RecommendFeed.current_date())
        try:
            que = DeviceArticleQueue.objects.get(device_id=device_id)
        except DeviceArticleQueue.DoesNotExist:
            que = ArticleQueue.objects.last()

        if not que:
            return []

        que = list(filter(None, que.queue.split(',')))

        # adjust args.
        cursor = redis_client.get(key) or 0
        cursor = int(cursor) % len(que)
        size = min(size, len(que))
        redis_client.set(key, cursor + size, ex=24 * 60 * 60)
        return list(islice(cycle(que), cursor, cursor + size))

    @staticmethod
    def fetch_user_topic(device_id, card_type, size):
        try:
            def read_history(cid_list):
                read_key = "TS:recommend_tractate_set:device_id:" + str(device_id)
                old_key = "TS:recommend_tractate_set:device_id:{}:{}" \
                    .format(device_id, (datetime.date.today() - datetime.timedelta(days=14)).strftime("%Y-%m-%d"))
                today_key = "TS:recommend_tractate_set:device_id:{}:{}" \
                    .format(device_id, datetime.date.today().strftime("%Y-%m-%d"))
                redis_client.sadd(today_key, *cid_list)
                redis_client.expire(today_key, 14 * 24 * 60 * 60)
                redis_client.sadd(read_key, *cid_list)
                if redis_client.exists(old_key):
                    redis_client.sdiffstore(read_key, read_key, old_key)
                    redis_client.delete(old_key)

                redis_client.expire(read_key, time=13 * 24 * 60 * 60)

            def no_filter_get_topic(device_id, size):
                if recommed_service_category_device_id(device_id):
                    coldstart_topic_queue_key = "coldstart:light:clinic:beauty:topic:queue:grey"
                    device0_coldstart_topic_cursor = "coldstart:light:clinic:beauty:topic:queue:grey:cursor"
                else:
                    coldstart_topic_queue_key = "coldstart:light:clinic:beauty:topic:queue"
                    device0_coldstart_topic_cursor = "coldstart:light:clinic:beauty:topic:queue:cursor"

                coldstart_cursor = redis_client2.get(device0_coldstart_topic_cursor)
                if coldstart_cursor:
                    coldstart_cursor = int(coldstart_cursor)
                else:
                    coldstart_cursor = 0

                result = redis_client2.lrange(coldstart_topic_queue_key, coldstart_cursor, coldstart_cursor + size - 1)
                result = [int(topic_id) for topic_id in result]

                if coldstart_cursor + size > redis_client2.llen(coldstart_topic_queue_key) - size:
                    redis_client2.set(device0_coldstart_topic_cursor, 0)
                else:
                    redis_client2.set(device0_coldstart_topic_cursor, coldstart_cursor + size)
                return result

            if device_id != '0':
                read_key = "TS:recommend_tractate_set:device_id:" + str(device_id)
                if redis_client.exists(read_key):
                    have_read_list = [int(i) for i in redis_client.smembers(read_key)]
                else:
                    have_read_list = list()
                topic_list = fetch_topic_by_user_portrait(device_id, have_read_list, size)
                if topic_list:
                    read_history(topic_list)
                if len(topic_list) >= size:
                    return topic_list
                else:
                    size = size - len(topic_list)
                    logger.info("portrait_fetch_topic:supplement1:device_id:{0}:size:{1}".format(device_id, size))

                supplements_topic = no_filter_get_topic(device_id, size)
                read_history(supplements_topic)
                topic_list.extend(supplements_topic)
                return topic_list
            else:
                data = no_filter_get_topic(device_id, size)
                return data
        except:
            logging_exception()
            return []

    @staticmethod
    def fetch_doctor_topic(device_id, card_type, size):
        try:
            key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                          card_type=card_type, date=RecommendFeed.current_date())
            try:
                que = DeviceDoctorTopicQueue.objects.get(device_id=device_id)
            except DeviceDoctorTopicQueue.DoesNotExist:
                que = DoctorTopicQueue.objects.last()
            if not que:
                return []
            que = list(filter(None, que.queue.split(',')))
            # adjust args.
            cursor = redis_client.get(key) or 0
            cursor = int(cursor) % len(que)
            size = min(size, len(que))
            redis_client.set(key, cursor + size, ex=24 * 60 * 60)
            return list(islice(cycle(que), cursor, cursor + size))
        except:
            logging_exception()
            return []


    @classmethod
    def fetch_diary(cls, device_id, card_type, city_id, size):
        try:
            def read_history(cid_list):
                read_key = "TS:recommend_diary_set:device_id:" + str(device_id)
                old_key = "TS:recommend_diary_set:device_id:{}:{}" \
                    .format(device_id, (datetime.date.today() - datetime.timedelta(days=14)).strftime("%Y-%m-%d"))
                today_key = "TS:recommend_diary_set:device_id:{}:{}" \
                    .format(device_id, datetime.date.today().strftime("%Y-%m-%d"))
                redis_client.sadd(today_key, *cid_list)
                redis_client.expire(today_key, 14 * 24 * 60 * 60)
                redis_client.sadd(read_key, *cid_list)
                if redis_client.exists(old_key):
                    redis_client.sdiffstore(read_key, read_key, old_key)
                    redis_client.delete(old_key)

                redis_client.expire(read_key, time=13 * 24 * 60 * 60)

            # 城市tag id
            x = City.objects.filter(id=city_id)
            city_tag_id = x[0].tag_id if x else -1
            if device_id != '0':
                # 已读
                read_key = "TS:recommend_diary_set:device_id:" + str(device_id)
                if redis_client.exists(read_key):
                    p = redis_client.smembers(read_key)
                    have_read_diary_list = list(map(int, p))
                else:
                    have_read_diary_list = list()

                portrait_list = list()
                cpc_list = list()

                # cpc召回
                tag_names = get_device_portrait_list(device_id, 5)
                if not tag_names:
                    light_clinic_beauty_key = "user:service_coldstart_tags3"
                    light_clinic_beauty = redis_client2.hgetall(light_clinic_beauty_key)
                    tag_names = [str(tag, 'utf-8') for tag in light_clinic_beauty]
                cpc_list = get_explore_tags_diary_id(tag_names, have_read_diary_list, city_tag_id, device_id, size=size, is_cpc=True)
                portrait_list = deepcopy(cpc_list)
                size = size - len(cpc_list)
                logging.info("gyz add diary cpc list: %s; device: %s " % (cpc_list, device_id))
                if size == 0:
                    read_history(cpc_list)
                    return cpc_list, cpc_list

                if size > 0:
                    # 召回
                    have_read_diary_list.extend(portrait_list)
                    diary_list = fetch_diary_by_user_portrait(device_id, city_tag_id, have_read_diary_list, size)
                    size = size - len(diary_list)
                    portrait_list.extend(diary_list)
                    logger.info(
                        "portrait_fetch_diary:device_id:{0}:portrait_list:{1}:cpc_list:{2}".format(device_id,
                                                                                                   portrait_list,
                                                                                                   cpc_list))
                    if size <= 0:
                        read_history(portrait_list)
                        return portrait_list, cpc_list
                    logger.info("portrait_fetch_diary:supplement1:device_id:{0}:size:{1}".format(device_id, size))
                    supplements_diary = cls.no_filter_get_diary(city_tag_id, device_id, size)
                    portrait_list.extend(supplements_diary)
                    read_history(portrait_list)
                    return portrait_list, cpc_list
            else:
                data = cls.no_filter_get_diary(city_tag_id, device_id, size)
                return data, []
        except:
            logging.error("catch exception,err_log:%s" % traceback.format_exc())
            logging_exception()
            return [], []

    @classmethod
    def no_filter_get_diary(cls, city_id, device_id, size):
        if recommed_service_category_device_id_v2(device_id):
            coldstart_diary_queue_key = "coldstart:light:clinic:beauty:diary:queue:grey"
        else:
            coldstart_diary_queue_key = "coldstart:light:clinic:beauty:diary:queue"
        coldstart_diary_queue = redis_client2.hget(coldstart_diary_queue_key, city_id)
        if coldstart_diary_queue:
            coldstart_diary_queue = json.loads(coldstart_diary_queue)
        else:
            coldstart_diary_queue = []

        coldstart_cursor = redis_client2.hget(coldstart_diary_queue_key, 'cursor_'+str(city_id))
        if coldstart_cursor:
            coldstart_cursor = json.loads(coldstart_cursor)
        else:
            coldstart_cursor = 0

        result = coldstart_diary_queue[coldstart_cursor: coldstart_cursor + size]
        if coldstart_cursor + size > len(coldstart_diary_queue) - size - 1:
            redis_client2.hset(coldstart_diary_queue_key, 'cursor_'+str(city_id), 0)
        else:
            redis_client2.hset(coldstart_diary_queue_key, 'cursor_'+str(city_id), coldstart_cursor + size)
        return result
