
# 新增一个用户画像对比接口
from itertools import chain, islice, cycle
import datetime
from collections import Counter

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 gm_rpcd.all import context
from libs.algorithms import drop_dup
from libs.cache import redis_client
from libs.error import logging_exception

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

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: []}


class RecommendFeed:
    @classmethod
    def dispatch(cls, device_id, card_type, city_id, size):
        data = []
        if card_type == CARD_TYPE.QA:
            data = cls.fetch_qa(device_id, card_type, size)
        elif card_type == CARD_TYPE.ANSWER:
            data = cls.fetch_answer(device_id, card_type, size)
            data = list(map(int, data))
        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:
            data = cls.fetch_diary(device_id, card_type, city_id, size)

        elif card_type == CARD_TYPE.USERTOPIC:
            data = cls.fetch_user_topic(device_id,card_type,size)
        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}

    @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 get_after_filter_qa():
                try:
                    return json.loads(gmkv.get(after_filter_key))
                except:
                    return []

            def write_after_filter_qa(cid_list):
                try:
                    if gmkv.exists(after_filter_key):
                        gmkv.set(after_filter_key, json.dumps(cid_list))
                    else:
                        gmkv.set(after_filter_key, json.dumps(cid_list),ex = 6*60*60)

                except:
                    logging_exception()
                    logger.error("catch exception,err_log:%s" % traceback.format_exc())

            def filter_qa(device_id,cid_list):
                try:
                    key = str(device_id) + "_dislike_qa"
                    if gmkv.exists(key):
                        dislike = gmkv.smembers(key)
                        if len(cid_list) > 0:
                            if type(cid_list[0]) == int or type(cid_list[0]) == str:
                                cid_list = [i for i in cid_list if str(i).encode('utf-8') not in dislike]
                            else:
                                cid_list = [i for i in cid_list if i not in dislike]
                        return cid_list
                    else:
                        return cid_list
                except:
                    return cid_list

            def read_history(cid_list):
                if redis_client.exists(today_qa_key):
                    redis_client.sadd(today_qa_key, *cid_list)
                else:
                    redis_client.sadd(today_qa_key, *cid_list)
                    redis_client.expire(today_qa_key, 15 * 24 * 60 * 60)
                if redis_client.exists(read_qa_key) and 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)

                redis_client.sadd(read_qa_key, *cid_list)

            def get_gmkv(redis_ip, redis_port, redis_db, redis_password=""):
                try:
                    if len(redis_password) == 0:
                        cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, socket_timeout=2)
                    else:
                        cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, password=redis_password,
                                              socket_timeout=2)
                    cli_ins.ping()

                    return cli_ins
                except:
                    return None

            search_qa_recommend_list = 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"))
            answer_queue_key = "qa_is_tail:" + str(device_id)
            after_filter_key = "device_qa_after_filter:device_id:" + str(device_id)
            gmkv = None
            for gm_kv_host_item in settings.GM_KV_HOSTS:
                gmkv = get_gmkv(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"],
                                         redis_db=gm_kv_host_item["db"],
                                         redis_password=gm_kv_host_item["password"])
                if gmkv:
                    break

            if device_id != '0':
                search_qa_recommend_key = "TS:search_recommend_answer_queue:device_id:" + str(device_id)
                if redis_client.exists(search_qa_recommend_key):
                    search_qa_recommend_dict = redis_client.hgetall(search_qa_recommend_key)
                    queue_list = json.loads(search_qa_recommend_dict[b'answer_queue'])
                    queue_list = filter_qa(device_id, queue_list)
                    if len(queue_list) == 0:
                        redis_client.delete(search_qa_recommend_key)
                    elif len(queue_list) == 1:
                        size = size - 1
                        search_qa_recommend_list = queue_list
                        redis_client.delete(search_qa_recommend_key)
                    else:
                        size = size - 1
                        search_qa_recommend_list.append(queue_list[0])
                        redis_client.hset(search_qa_recommend_key,"answer_queue",json.dumps(queue_list[1:]))

                if gmkv.exists(answer_queue_key):
                    if len(search_qa_recommend_list) > 0:
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                elif gmkv.exists(after_filter_key):
                    que = get_after_filter_qa()
                    que = filter_qa(device_id,que)
                    if len(que) == 0:
                        gmkv.set(answer_queue_key,"tail",ex = 6*60*60)
                        if len(search_qa_recommend_list) > 0:
                            search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                            read_history(search_qa_recommend_list)
                        return search_qa_recommend_list
                    elif len(que) <= size:
                        search_qa_recommend_list.extend(que)
                        gmkv.set(answer_queue_key, "tail", ex=6 * 60 * 60)
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                        return search_qa_recommend_list
                    else:
                        search_qa_recommend_list.extend(que[:size])
                        write_after_filter_qa(que[size:])
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                        return search_qa_recommend_list

                try:
                    que = DeviceQAQueue.objects.get(device_id=device_id)
                except DeviceQAQueue.DoesNotExist:
                    que = AnswerQueue.objects.last()
                if not que:
                    if len(search_qa_recommend_list) > 0:
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                qa = list(filter(None, que.queue.split(',')))
                if device_id != "0":
                    qa = filter_qa(device_id,qa)
                if len(qa) == 0:
                    if device_id != "0":
                        gmkv.set(answer_queue_key, "tail", ex=6 * 60 * 60)
                    if len(search_qa_recommend_list) > 0:
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                elif len(qa) <= size:
                    search_qa_recommend_list.extend(qa)
                    search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                    if device_id != "0":
                        gmkv.set(answer_queue_key, "tail", ex=6 * 60 * 60)
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                else:
                    search_qa_recommend_list.extend(qa[:size])
                    search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                    if device_id != "0":
                        write_after_filter_qa(qa[size:])
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list

            else:
                key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                              card_type=card_type, date=RecommendFeed.current_date())
                try:
                    que = DeviceQAQueue.objects.get(device_id=device_id)
                except DeviceQAQueue.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)
                data = list(islice(cycle(que), cursor, cursor + size))
                data = list(map(int, data))
                if cursor + 2 * size < len(que):
                    redis_client.set(key, cursor + size, ex=24 * 60 * 60)
                else:
                    try:
                        context.request_logger.app(reset_answer_queue=True)
                        cursor = 0
                        redis_client.set(key, cursor, ex=24 * 60 * 60)
                    except:
                        redis_client.set(key, cursor + size, ex=24 * 60 * 60)
                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:
            key = '{device_id}-{card_type}-{date}'.format(device_id=device_id, card_type=card_type,
                                                          date=RecommendFeed.current_date())

            if (device_id != '0') and size >= 2:

                search_topic_recommend_key = "TS:search_recommend_tractate_queue:device_id:" + str(device_id)
                search_topic_recommend_list = list()
                search_cursor_ts = 0
                if redis_client.exists(search_topic_recommend_key):
                    search_topic_recommend_dict = redis_client.hgetall(search_topic_recommend_key)
                    if b'cursor' in search_topic_recommend_dict:
                        search_cursor_ts = json.loads(search_topic_recommend_dict[b'cursor'])
                        if search_cursor_ts < 30:
                            search_topic_recommend_list = json.loads(search_topic_recommend_dict[b'tractate_queue'])
                            if search_cursor_ts < len(search_topic_recommend_list):
                                size = size - 2

            try:
                que = DeviceUserTopicQueue.objects.get(device_id=device_id)
            except DeviceUserTopicQueue.DoesNotExist:
                que = UserTopicQueue.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))
            data = list(islice(cycle(que), cursor, cursor + size))
            data = list(map(int, data))

            if cursor + 2 * size < len(que):
                redis_client.set(key, cursor + size, ex=24 * 60 * 60)
            else:
                try:
                    context.request_logger.app(reset_queue=True)
                    cursor = 0
                    redis_client.set(key, cursor, ex=24 * 60 * 60)
                except:
                    redis_client.set(key, cursor + size, ex=24 * 60 * 60)

            if device_id != '0' and size >= 2:
                if len(search_topic_recommend_list) > 0 and search_cursor_ts < len(search_topic_recommend_list):
                    queue = search_topic_recommend_list[search_cursor_ts:search_cursor_ts + 2]
                    queue.extend(data)
                    data = queue
                    new_search_cursor = search_cursor_ts + 2
                    redis_client.hset(search_topic_recommend_key, 'cursor', new_search_cursor)
                    redis_client.expire(search_topic_recommend_key, 30 * 24 * 60 * 60)
                read_topic_key = "TS:recommend_tractate_set:device_id:" + str(device_id)
                if len(data) > 0:
                    redis_client.sadd(read_topic_key, *data)
            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 get_gm_kv_ins(cls,redis_ip, redis_port, redis_db, redis_password=""):
        try:
            if len(redis_password) == 0:
                cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, socket_timeout=2)
            else:
                cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, password=redis_password,
                                      socket_timeout=2)
            cli_ins.ping()

            return cli_ins
        except:
            return None

    @classmethod
    def fetch_diary_queue_data(cls, city_id, device_id=None):
        local = list()
        nearby = list()
        nation = list()
        megacity = list()
        use_city_id = city_id
        try:
            gm_kv_ins = None
            for gm_kv_host_item in settings.GM_KV_HOSTS:
                gm_kv_ins = cls.get_gm_kv_ins(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"], redis_db=gm_kv_host_item["db"],redis_password=gm_kv_host_item["password"])
                if gm_kv_ins:
                    break

            specify_city_id_key = "diary_queue:city_id:" + use_city_id
            world_city_id_key = "diary_queue:city_id:world"
            if device_id is not None:
                specify_city_id_key = "device_diary_queue:device_id:" + device_id + ":city_id:" + use_city_id

            city_val_dict = gm_kv_ins.hgetall(specify_city_id_key)
            if len(city_val_dict) == 0:
                city_val_dict = gm_kv_ins.hgetall(world_city_id_key)
                use_city_id = "world"

            if b"native_queue" in city_val_dict and city_val_dict[b"native_queue"]:
                local = list(filter(None, city_val_dict[b"native_queue"].split(b",")))
            if b"nearby_queue" in city_val_dict and city_val_dict[b"nearby_queue"]:
                nearby = list(filter(None, city_val_dict[b"nearby_queue"].split(b",")))
            if b"nation_queue" in city_val_dict and city_val_dict[b"nation_queue"]:
                nation = list(filter(None, city_val_dict[b"nation_queue"].split(b",")))
            if b"megacity_queue" in city_val_dict and city_val_dict[b"megacity_queue"]:
                megacity = list(filter(None, city_val_dict[b"megacity_queue"].split(b",")))

            return (local, nearby, nation, megacity, use_city_id)

        except:
            logging_exception()
            logger.error("catch exception,err_log:%s" % traceback.format_exc())
            qs = DiaryQueue.objects.filter(city_id__in=[city_id, 'world'])
            # Assume that world queue must exist.
            if len(qs) == 1:
                obj = qs[0]
            else:
                obj = qs[0] if qs[0].city_id == city_id else qs[1]

            if obj.native_queue:
                local = list(filter(None, obj.native_queue.split(',')))
            if obj.nearby_queue:
                nearby = list(filter(None, obj.nearby_queue.split(',')))
            if obj.nation_queue:
                nation = list(filter(None, obj.nation_queue.split(',')))
            if obj.megacity_queue:
                megacity = list(filter(None, obj.megacity_queue.split(',')))

            use_city_id = obj.city_id if obj else use_city_id
            return (local, nearby, nation, megacity, use_city_id)

    @classmethod
    def fetch_device_diary_queue_data(cls, city_id, device_id):
        local = list()
        nearby = list()
        nation = list()
        megacity = list()
        use_city_id = city_id
        try:
            gm_kv_ins = None
            for gm_kv_host_item in settings.GM_KV_HOSTS:
                gm_kv_ins = cls.get_gm_kv_ins(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"], redis_db=gm_kv_host_item["db"],redis_password=gm_kv_host_item["password"])
                if gm_kv_ins:
                    break

            specify_city_id_key = "device_diary_queue:device_id:" + device_id + ":city_id:" + use_city_id
            city_val_dict = gm_kv_ins.hgetall(specify_city_id_key)

            if b"native_queue" in city_val_dict and city_val_dict[b"native_queue"]:
                local = list(filter(None, city_val_dict[b"native_queue"].split(b",")))

            if b"nearby_queue" in city_val_dict and city_val_dict[b"nearby_queue"]:
                nearby = list(filter(None, city_val_dict[b"nearby_queue"].split(b",")))
            if b"nation_queue" in city_val_dict and city_val_dict[b"nation_queue"]:
                nation = list(filter(None, city_val_dict[b"nation_queue"].split(b",")))
            if b"megacity_queue" in city_val_dict and city_val_dict[b"megacity_queue"]:
                megacity = list(filter(None, city_val_dict[b"megacity_queue"].split(b",")))

            return (local, nearby, nation, megacity, use_city_id)
        except:
            logging_exception()
            logger.error("catch exception,err_log:%s" % traceback.format_exc())
            obj = DeviceDiaryQueue.objects.filter(device_id=device_id, city_id=city_id).first()
            if obj and obj.native_queue:
                local = list(filter(None, obj.native_queue.split(',')))
            if obj and obj.nearby_queue:
                nearby = list(filter(None, obj.nearby_queue.split(',')))
            if obj and obj.nation_queue:
                nation = list(filter(None, obj.nation_queue.split(',')))
            if obj and obj.megacity_queue:
                megacity = list(filter(None, obj.megacity_queue.split(',')))

            use_city_id = obj.city_id if obj else use_city_id
            return (local, nearby, nation, megacity, use_city_id)

    @classmethod
    def fetch_diary(cls, device_id, card_type, city_id, size):
        try:
            def read_history(cid_list):
                if redis_client.exists(today_key):
                    redis_client.sadd(today_key, *cid_list)
                else:
                    redis_client.sadd(today_key, *cid_list)
                    redis_client.expire(today_key, 15 * 24 * 60 * 60)
                if redis_client.exists(read_key) and 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)

                redis_client.sadd(read_key, *cid_list)

            def dislike_cid_filter(device_id, cid_list):
                try:
                    key = str(device_id) + "_dislike_diary"
                    if gmkv.exists(key):
                        dislike = gmkv.smembers(key)
                        if len(cid_list) > 0:
                            if type(cid_list[0]) == int or type(cid_list[0]) == str:
                                cid_list = [i for i in cid_list if str(i).encode('utf-8') not in dislike]
                            else:
                                cid_list = [i for i in cid_list if i not in dislike]
                    return cid_list
                except:
                    return cid_list

            def fetch_after_filter_queue(device_id, city_id, name):
                local = list()
                try:
                    key = "device_diary_queue_after_filter:device_id:" + device_id + ":city_id:" + city_id + name
                    if gmkv.exists(key):
                        return json.loads(gmkv.get(key))
                    else:
                        return local
                except:

                    return local

            def write_after_filter_queue(device_id, city_id, cid_list, name):
                try:
                    key = "device_diary_queue_after_filter:device_id:" + device_id + ":city_id:" + city_id + name
                    if gmkv.exists(key):
                        gmkv.set(key, json.dumps(cid_list))
                    else:
                        gmkv.set(key, json.dumps(cid_list), ex=6 * 60 * 60)

                except:
                    logging_exception()
                    logger.error("catch exception,err_log:%s" % traceback.format_exc())

            def get_data(local, nearby, nation, megacity, cx, cy, cm, cz, x, y, z, m, size):
                nx = int(round(x * 1.0 / (x + y + z + m) * size))
                ny = int(round(y * 1.0 / (x + y + z + m) * size))
                nz = int(round(z * 1.0 / (x + y + z + m) * size))
                nm = int(round(m * 1.0 / (x + y + z + m) * size))
                nxyz = [nx, ny, nm, nz]
                xyz = [x, y, m, z]
                counter = Counter([nx, ny, nm, nz])
                if counter[0] == 2:
                    nxyz[nxyz.index(0)] += size - sum(nxyz)
                else:
                    nxyz[xyz.index(max(xyz))] += size - sum(nxyz)
                nx, ny, nm, nz = nxyz

                local_filter = dislike_cid_filter(device_id, cx)
                if len(local_filter) == 0:
                    local_filter = dislike_cid_filter(device_id, local)
                slocal = local_filter[:nx]
                have_x = local_filter[nx:]
                x_list = list(map(int, have_x))
                write_after_filter_queue(device_id, city_id, x_list, "native")

                ny += (nx - len(slocal))

                nearby_filter = dislike_cid_filter(device_id, cy)
                if len(nearby_filter) == 0:
                    nearby_filter = dislike_cid_filter(device_id, nearby)
                snearby = nearby_filter[:ny]
                have_y = nearby_filter[ny:]
                y_list = list(map(int, have_y))
                write_after_filter_queue(device_id, city_id, y_list, "nearby")

                nm += (ny - len(snearby))

                megacity_filter = dislike_cid_filter(device_id, cm)
                if len(megacity_filter) == 0:
                    megacity_filter = dislike_cid_filter(device_id, megacity)
                smegacity = megacity_filter[:nm]
                have_m = megacity_filter[nm:]
                m_list = list(map(int, have_m))
                write_after_filter_queue(device_id, city_id, m_list, "megacity")

                nz += (nm - len(smegacity))

                nation_filter = dislike_cid_filter(device_id, cz)
                if len(nation_filter) == 0:
                    nation_filter = dislike_cid_filter(device_id, nation)
                snation = nation_filter[:nz]
                have_z = snation[nz:]
                z_list = list(map(int, have_z))
                write_after_filter_queue(device_id, city_id, z_list, "nation")

                return chain(slocal, snearby, smegacity, snation)

            if device_id != '0':
                portrait_list = list()
                click_diary_size = 1
                search_diary_size = 4
                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"))
                user_portrait_diary_key = 'user_portrait_recommend_diary_queue:device_id:%s:%s' % \
                                          (device_id, datetime.datetime.now().strftime('%Y-%m-%d'))
                gmkv = None
                for gm_kv_host_item in settings.GM_KV_HOSTS:
                    gmkv = cls.get_gm_kv_ins(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"],
                                             redis_db=gm_kv_host_item["db"],
                                             redis_password=gm_kv_host_item["password"])
                    if gmkv:
                        break

                if redis_client.exists(user_portrait_diary_key):
                    user_portrait_diary_dict = redis_client.hgetall(user_portrait_diary_key)
                    user_portrait_cursor = str(user_portrait_diary_dict[b'cursor'], encoding='utf-8')
                    if user_portrait_cursor == '0':
                        if b'len_cursor' in user_portrait_diary_dict.keys():
                            user_portrait_diary_list = json.loads(user_portrait_diary_dict[b'diary_queue'])
                            filter_user_portrait_diary_list = dislike_cid_filter(device_id, user_portrait_diary_list)
                            if len(filter_user_portrait_diary_list) > size:
                                portrait_list = filter_user_portrait_diary_list[:size]
                                redis_client.hset(user_portrait_diary_key, 'diary_queue',
                                                  json.dumps(filter_user_portrait_diary_list[size:]))
                                portrait_list = list(map(int, portrait_list))
                                read_history(portrait_list)
                                return portrait_list
                            else:
                                size = size - len(filter_user_portrait_diary_list)
                                portrait_list = filter_user_portrait_diary_list
                                redis_client.delete(user_portrait_diary_key)

                search_diary_recommend_key = "TS:search_recommend_diary_queue:device_id:" + str(device_id)
                search_list = list()

                if redis_client.exists(search_diary_recommend_key) and size > 3:
                    search_diary_recommend_dict = redis_client.hgetall(search_diary_recommend_key)
                    search_diary_recommend_list = json.loads(search_diary_recommend_dict[b'diary_queue'])
                    search_diary_recommend_list = dislike_cid_filter(device_id, search_diary_recommend_list)
                    if len(search_diary_recommend_list) == 0:
                        redis_client.delete(search_diary_recommend_key)
                    elif len(search_diary_recommend_list) <= search_diary_size:
                        search_list = search_diary_recommend_list
                        size = size - len(search_diary_recommend_list)
                        redis_client.delete(search_diary_recommend_key)

                    else:
                        search_list = search_diary_recommend_list[:search_diary_size]
                        size = size - search_diary_size
                        redis_client.hset(search_diary_recommend_key, 'diary_queue',
                                          json.dumps(search_diary_recommend_list[search_diary_size:]))
                    if size <= 0:
                        portrait_list.extend(search_list)
                        portrait_list = list(map(int, portrait_list))
                        read_history(portrait_list)
                        return portrait_list

                diary_recommend_key = "TS:recommend_diary_queue:device_id:" + str(device_id)
                ts_recommend_list = list()
                if redis_client.exists(diary_recommend_key) and size > 0:
                    diary_recommend_dict = redis_client.hgetall(diary_recommend_key)
                    diary_recommend_list = json.loads(diary_recommend_dict[b'diary_queue'])
                    diary_recommend_list = dislike_cid_filter(device_id, diary_recommend_list)
                    if len(diary_recommend_list) == 0:
                        redis_client.delete(diary_recommend_key)

                    elif len(diary_recommend_list) <= click_diary_size:
                        ts_recommend_list = diary_recommend_list
                        redis_client.delete(diary_recommend_key)
                        size = size - len(ts_recommend_list)

                    else:
                        size = size - click_diary_size
                        ts_recommend_list = diary_recommend_list[:click_diary_size]
                        diary_recommend_list_json = json.dumps(diary_recommend_list[click_diary_size:])
                        redis_client.hset(diary_recommend_key, 'diary_queue', diary_recommend_list_json)
                    if size <= 0:
                        portrait_list.extend(search_list)
                        portrait_list.extend(ts_recommend_list)
                        portrait_list = list(map(int, portrait_list))
                        read_history(portrait_list)
                        return portrait_list

                if size > 0:
                    try:
                        (local, nearby, nation, megacity, city_id) = cls.fetch_device_diary_queue_data(city_id,
                                                                                                       device_id)
                        if len(local) == 0 and len(nearby) == 0 and len(nation) == 0 and len(megacity) == 0:
                            (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)

                    except:
                        logging_exception()
                        (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)
                    x, y, m, z = cls.get_city_scale(city_id)
                    cx = fetch_after_filter_queue(device_id, city_id, "native")
                    cy = fetch_after_filter_queue(device_id, city_id, "nearby")
                    cz = fetch_after_filter_queue(device_id, city_id, "nation")
                    cm = fetch_after_filter_queue(device_id, city_id, "megacity")
                    data = get_data(
                        local, nearby, nation, megacity,
                        cx, cy, cm, cz,
                        x, y, z, m, size)

                    portrait_list.extend(search_list)
                    portrait_list.extend(ts_recommend_list)
                    portrait_list.extend(data)
                    if len(portrait_list) == 0:
                        (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)
                        portrait_list = cls.get_queue(local, nearby, nation, megacity,
                                                      device_id, city_id, size, x, y, z, m)
                    portrait_list = list(map(int, portrait_list))
                    if len(portrait_list) != 0:
                        read_history(portrait_list)
                    return portrait_list
            else:

                try:
                    (local, nearby, nation, megacity, city_id) = cls.fetch_device_diary_queue_data(city_id, device_id)
                    if len(local) == 0 and len(nearby) == 0 and len(nation) == 0 and len(megacity) == 0:
                        (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)
                except:
                    logging_exception()
                    (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)

                key = '{device_id}-{city_id}-{date}'.format(device_id=device_id,
                                                            city_id=city_id, date=RecommendFeed.current_date())
                # strategy rule: when user refresh over 30 loadings, reset native nearby nation queue cursor.
                counter_key = key + '-counter_v1'
                counter = redis_client.incr(counter_key)
                if counter == 1:
                    redis_client.expire(counter_key, 24 * 60 * 60)
                cursor_key = key + '-cursor_v1'
                cursor = redis_client.get(cursor_key) or b'0-0-0-0'
                # if counter > 30:
                #     cursor = b'0-0-0-0'
                #     redis_client.delete(counter_key)
                cx, cy, cm, cz = map(int, cursor.split(b'-'))

                x, y, m, z = cls.get_city_scale(city_id)
                data, ncx, ncy, ncm, ncz = cls.get_scale_data(
                    local, nearby, nation, megacity,
                    cx, cy, cm, cz,
                    x, y, z, m, size
                )

                if ncx == cx and ncy == cy:  # native queue and nearby queue
                    logger.info("diary queue reach end,cx:%d,cy:%d,cm:%d,cz:%d", cx, cy, cm, cz)
                    # redis_client.delete(counter_key)
                    # data, ncx, ncy, ncm, ncz = cls.get_scale_data(
                    #     local, nearby, nation, megacity,
                    #     0, 0, 0, 0,
                    #     x, y, z, m, size
                    # )
                    ncx = ncy = ncm = ncz = 0

                val = '-'.join(map(str, [ncx, ncy, ncm, ncz]))
                redis_client.set(cursor_key, val, ex=24 * 60 * 60)
                data = list(map(int, data))

                return data
        except:
            logging_exception()
            return []

    @classmethod
    def get_queue(cls,local, nearby, nation, megacity, device_id,city_id,size,x,y,z,m):
        key = '{device_id}-{city_id}-{date}'.format(device_id=device_id,
                                                    city_id=city_id, date=RecommendFeed.current_date())
        counter_key = key + '-counter_v1'
        counter = redis_client.incr(counter_key)
        if counter == 1:
            redis_client.expire(counter_key, 24 * 60 * 60)
        cursor_key = key + '-cursor_v1'
        cursor = redis_client.get(cursor_key) or b'0-0-0-0'
        cx, cy, cm, cz = map(int, cursor.split(b'-'))

        def get_scale(local, nearby, nation, megacity, cx, cy, cm, cz, x, y, z, m, size):
            nx = int(round(x * 1.0 / (x + y + z + m) * size))
            ny = int(round(y * 1.0 / (x + y + z + m) * size))
            nz = int(round(z * 1.0 / (x + y + z + m) * size))
            nm = int(round(m * 1.0 / (x + y + z + m) * size))
            nxyz = [nx, ny, nm, nz]
            xyz = [x, y, m, z]
            counter = Counter([nx, ny, nm, nz])
            if counter[0] == 2:
                nxyz[nxyz.index(0)] += size - sum(nxyz)
            else:
                nxyz[xyz.index(max(xyz))] += size - sum(nxyz)
            nx, ny, nm, nz = nxyz

            slocal = local[cx:cx + nx]
            cx = min(cx + nx, len(local))
            ny += (nx - len(slocal))

            snearby = nearby[cy:cy + ny]
            cy = min(cy + ny, len(nearby))
            nm += (ny - len(snearby))

            smegacity = megacity[cm: cm + nm]
            cm = min(cm + nm, len(megacity))
            nz += (nm - len(smegacity))

            snation = nation[cz:cz + nz]
            cz = min(cz + nz, len(nation))
            return chain(slocal, snearby, smegacity, snation), cx, cy, cm, cz

        data, ncx, ncy, ncm, ncz = get_scale(
            local, nearby, nation, megacity,
            cx, cy, cm, cz,
            x, y, z, m, size)

        if ncx == cx and ncy == cy:  # native queue and nearby queue
            logger.info("diary queue reach end,cx:%d,cy:%d,cm:%d,cz:%d", cx, cy, cm, cz)

            ncx = ncy = ncm = ncz = 0

        val = '-'.join(map(str, [ncx, ncy, ncm, ncz]))
        redis_client.set(cursor_key, val, ex=24 * 60 * 60)
        return list(map(int, data))

    @staticmethod
    def get_city_scale(city_id):
        try:
            c = CityScale.objects.get(city_id=city_id)
            x, y, z, m = c.native, c.nearby, c.nation, c.megacity
        except CityScale.DoesNotExist:
            try:
                c = City.objects.get(id=city_id)
                if c.level in (CITY_LEVEL.SUPER, CITY_LEVEL.ONE):
                    x, y, m, z = 4, 3, 0, 3
                elif c.level == CITY_LEVEL.TWO:
                    x, y, m, z = 3, 3, 0, 3
                elif c.level == CITY_LEVEL.THREE:
                    x, y, m, z = 1, 4, 0, 5
                else:
                    x, y, m, z = 0, 0, 0, 10
            except City.DoesNotExist:
                x, y, m, z = 0, 0, 0, 10

        return x, y, m, z

    @staticmethod
    def get_scale_data(local, nearby, nation, megacity, cx, cy, cm, cz, x, y, z, m, size):
        """

        :param local:           local diary queue
        :param nearby:          nearby diary queue
        :param nation:          nation diary queue
        :param megacity:        megacity diary queue
        :param cx:              seen local diary offset
        :param cy:              seen nearby diary offset
        :param cz:              seen nation diary offset
        :param cm:              seen megacity diary offset
        :param x:               local diary scale factor
        :param y:               nearby diary scale factor
        :param z:               nation diary scale factor
        :param m:               megacity diary scale factor
        :param size:            nubmer of diary
        :return:
        """
        # 本地 临近 特大城市 全国 四个层级 都按照的是四舍五入取得方式
        # 针对出现的问题，本次相应的优化是：
        # 1、如果出现两个层级为零，且有剩余坑位时，则按照本地 临近 全国的优先级，先给优先级高且为零的层级一个坑位。
        # 2、如果所有层级都非零，且有剩余坑位时，则优先给权重占比大的层级一个坑位。
        # 3、如果只有一个层级为零，且有剩余坑位时，则优先填充权重占比大的层级一个坑位。
        nx = int(round(x * 1.0 / (x + y + z + m) * size))
        ny = int(round(y * 1.0 / (x + y + z + m) * size))
        nz = int(round(z * 1.0 / (x + y + z + m) * size))
        nm = int(round(m * 1.0 / (x + y + z + m) * size))
        nxyz = [nx, ny, nm, nz]
        xyz = [x, y, m, z]
        counter = Counter([nx, ny, nm, nz])
        if counter[0] == 2:
            nxyz[nxyz.index(0)] += size - sum(nxyz)
        else:
            nxyz[xyz.index(max(xyz))] += size - sum(nxyz)
        nx, ny, nm, nz = nxyz

        slocal = local[cx:cx + nx]
        cx = min(cx + nx, len(local))
        ny += (nx - len(slocal))

        snearby = nearby[cy:cy + ny]
        cy = min(cy + ny, len(nearby))
        nm += (ny - len(snearby))

        smegacity = megacity[cm: cm + nm]
        cm = min(cm + nm, len(megacity))
        nz += (nm - len(smegacity))

        snation = nation[cz:cz + nz]
        cz = min(cz + nz, len(nation))

        return chain(slocal, snearby, smegacity, snation), cx, cy, cm, cz


@bind('doris/recommend/feed_rerank')
def recommend_feed_rerank(device_id, card_type, city_id, size):
    try:
        return RecommendFeedRerank.dispatch(device_id, card_type,
                                      city_id, size)
    except:
        logging_exception()
        return {card_type: []}


class RecommendFeedRerank:
    @classmethod
    def dispatch(cls, device_id, card_type, city_id, size):
        data = []
        if card_type == CARD_TYPE.QA:
            data = cls.fetch_qa(device_id, card_type, size)
        elif card_type == CARD_TYPE.ANSWER:
            data = cls.fetch_answer(device_id, card_type, size)
            data = list(map(int, data))
        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:
            data = cls.fetch_diary(device_id, card_type, city_id, size)

        elif card_type == CARD_TYPE.USERTOPIC:
            data = cls.fetch_user_topic(device_id,card_type,size)
        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}

    @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 get_after_filter_qa():
                try:
                    return json.loads(gmkv.get(after_filter_key))
                except:
                    return []

            def write_after_filter_qa(cid_list):
                try:
                    if gmkv.exists(after_filter_key):
                        gmkv.set(after_filter_key, json.dumps(cid_list))
                    else:
                        gmkv.set(after_filter_key, json.dumps(cid_list),ex = 6*60*60)

                except:
                    logging_exception()
                    logger.error("catch exception,err_log:%s" % traceback.format_exc())

            def filter_qa(device_id,cid_list):
                try:
                    key = str(device_id) + "_dislike_qa"
                    if gmkv.exists(key):
                        dislike = gmkv.smembers(key)
                        if len(cid_list) > 0:
                            if type(cid_list[0]) == int or type(cid_list[0]) == str:
                                cid_list = [i for i in cid_list if str(i).encode('utf-8') not in dislike]
                            else:
                                cid_list = [i for i in cid_list if i not in dislike]
                        return cid_list
                    else:
                        return cid_list
                except:
                    return cid_list

            def read_history(cid_list):
                if redis_client.exists(today_qa_key):
                    redis_client.sadd(today_qa_key, *cid_list)
                else:
                    redis_client.sadd(today_qa_key, *cid_list)
                    redis_client.expire(today_qa_key, 15 * 24 * 60 * 60)
                if redis_client.exists(read_qa_key) and 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)

                redis_client.sadd(read_qa_key, *cid_list)

            def get_gmkv(redis_ip, redis_port, redis_db, redis_password=""):
                try:
                    if len(redis_password) == 0:
                        cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, socket_timeout=2)
                    else:
                        cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, password=redis_password,
                                              socket_timeout=2)
                    cli_ins.ping()

                    return cli_ins
                except:
                    return None

            search_qa_recommend_list = 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"))
            answer_queue_key = "qa_is_tail:" + str(device_id)
            after_filter_key = "device_qa_after_filter:device_id:" + str(device_id)
            gmkv = None
            for gm_kv_host_item in settings.GM_KV_HOSTS:
                gmkv = get_gmkv(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"],
                                         redis_db=gm_kv_host_item["db"],
                                         redis_password=gm_kv_host_item["password"])
                if gmkv:
                    break

            if device_id != '0':
                search_qa_recommend_key = "TS:search_recommend_answer_queue:device_id:" + str(device_id)
                if redis_client.exists(search_qa_recommend_key):
                    search_qa_recommend_dict = redis_client.hgetall(search_qa_recommend_key)
                    queue_list = json.loads(search_qa_recommend_dict[b'answer_queue'])
                    queue_list = filter_qa(device_id, queue_list)
                    if len(queue_list) == 0:
                        redis_client.delete(search_qa_recommend_key)
                    elif len(queue_list) == 1:
                        size = size - 1
                        search_qa_recommend_list = queue_list
                        redis_client.delete(search_qa_recommend_key)
                    else:
                        size = size - 1
                        search_qa_recommend_list.append(queue_list[0])
                        redis_client.hset(search_qa_recommend_key,"answer_queue",json.dumps(queue_list[1:]))

                if gmkv.exists(answer_queue_key):
                    if len(search_qa_recommend_list) > 0:
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                elif gmkv.exists(after_filter_key):
                    que = get_after_filter_qa()
                    que = filter_qa(device_id,que)
                    if len(que) == 0:
                        gmkv.set(answer_queue_key,"tail",ex = 6*60*60)
                        if len(search_qa_recommend_list) > 0:
                            search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                            read_history(search_qa_recommend_list)
                        return search_qa_recommend_list
                    elif len(que) <= size:
                        search_qa_recommend_list.extend(que)
                        gmkv.set(answer_queue_key, "tail", ex=6 * 60 * 60)
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                        return search_qa_recommend_list
                    else:
                        search_qa_recommend_list.extend(que[:size])
                        write_after_filter_qa(que[size:])
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                        return search_qa_recommend_list

                try:
                    que = DeviceQAQueue.objects.get(device_id=device_id)
                except DeviceQAQueue.DoesNotExist:
                    que = AnswerQueue.objects.last()
                if not que:
                    if len(search_qa_recommend_list) > 0:
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                qa = list(filter(None, que.queue.split(',')))
                if device_id != "0":
                    qa = filter_qa(device_id,qa)
                if len(qa) == 0:
                    if device_id != "0":
                        gmkv.set(answer_queue_key, "tail", ex=6 * 60 * 60)
                    if len(search_qa_recommend_list) > 0:
                        search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                elif len(qa) <= size:
                    search_qa_recommend_list.extend(qa)
                    search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                    if device_id != "0":
                        gmkv.set(answer_queue_key, "tail", ex=6 * 60 * 60)
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list
                else:
                    search_qa_recommend_list.extend(qa[:size])
                    search_qa_recommend_list = list(map(int, search_qa_recommend_list))
                    if device_id != "0":
                        write_after_filter_qa(qa[size:])
                        read_history(search_qa_recommend_list)
                    return search_qa_recommend_list

            else:
                key = '{device_id}-{card_type}-{date}'.format(device_id=device_id,
                                                              card_type=card_type, date=RecommendFeed.current_date())
                try:
                    que = DeviceQAQueue.objects.get(device_id=device_id)
                except DeviceQAQueue.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)
                data = list(islice(cycle(que), cursor, cursor + size))
                data = list(map(int, data))
                if cursor + 2 * size < len(que):
                    redis_client.set(key, cursor + size, ex=24 * 60 * 60)
                else:
                    try:
                        context.request_logger.app(reset_answer_queue=True)
                        cursor = 0
                        redis_client.set(key, cursor, ex=24 * 60 * 60)
                    except:
                        redis_client.set(key, cursor + size, ex=24 * 60 * 60)
                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:
            key = '{device_id}-{card_type}-{date}'.format(device_id=device_id, card_type=card_type,
                                                          date=RecommendFeed.current_date())

            if (device_id != '0') and size >= 2:

                search_topic_recommend_key = "TS:search_recommend_tractate_queue:device_id:" + str(device_id)
                search_topic_recommend_list = list()
                search_cursor_ts = 0
                if redis_client.exists(search_topic_recommend_key):
                    search_topic_recommend_dict = redis_client.hgetall(search_topic_recommend_key)
                    if b'cursor' in search_topic_recommend_dict:
                        search_cursor_ts = json.loads(search_topic_recommend_dict[b'cursor'])
                        if search_cursor_ts < 30:
                            search_topic_recommend_list = json.loads(search_topic_recommend_dict[b'tractate_queue'])
                            if search_cursor_ts < len(search_topic_recommend_list):
                                size = size - 2

            try:
                que = DeviceUserTopicQueue.objects.get(device_id=device_id)
            except DeviceUserTopicQueue.DoesNotExist:
                que = UserTopicQueue.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))
            data = list(islice(cycle(que), cursor, cursor + size))
            data = list(map(int, data))

            if cursor + 2 * size < len(que):
                redis_client.set(key, cursor + size, ex=24 * 60 * 60)
            else:
                try:
                    context.request_logger.app(reset_queue=True)
                    cursor = 0
                    redis_client.set(key, cursor, ex=24 * 60 * 60)
                except:
                    redis_client.set(key, cursor + size, ex=24 * 60 * 60)

            if device_id != '0' and size >= 2:
                if len(search_topic_recommend_list) > 0 and search_cursor_ts < len(search_topic_recommend_list):
                    queue = search_topic_recommend_list[search_cursor_ts:search_cursor_ts + 2]
                    queue.extend(data)
                    data = queue
                    new_search_cursor = search_cursor_ts + 2
                    redis_client.hset(search_topic_recommend_key, 'cursor', new_search_cursor)
                    redis_client.expire(search_topic_recommend_key, 30 * 24 * 60 * 60)
                read_topic_key = "TS:recommend_tractate_set:device_id:" + str(device_id)
                if len(data) > 0:
                    redis_client.sadd(read_topic_key, *data)
            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 get_gm_kv_ins(cls,redis_ip, redis_port, redis_db, redis_password=""):
        try:
            if len(redis_password) == 0:
                cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, socket_timeout=2)
            else:
                cli_ins = redis.Redis(host=redis_ip, port=redis_port, db=redis_db, password=redis_password,
                                      socket_timeout=2)
            cli_ins.ping()

            return cli_ins
        except:
            return None

    @classmethod
    def fetch_diary_queue_data(cls, city_id, device_id=None):
        local = list()
        nearby = list()
        nation = list()
        megacity = list()
        use_city_id = city_id
        try:
            gm_kv_ins = None
            for gm_kv_host_item in settings.GM_KV_HOSTS:
                gm_kv_ins = cls.get_gm_kv_ins(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"], redis_db=gm_kv_host_item["db"],redis_password=gm_kv_host_item["password"])
                if gm_kv_ins:
                    break

            specify_city_id_key = "diary_queue:city_id:" + use_city_id
            world_city_id_key = "diary_queue:city_id:world"
            if device_id is not None:
                specify_city_id_key = "device_diary_queue_rerank:device_id:" + device_id + ":city_id:" + use_city_id

            city_val_dict = gm_kv_ins.hgetall(specify_city_id_key)
            if len(city_val_dict) == 0:
                city_val_dict = gm_kv_ins.hgetall(world_city_id_key)
                use_city_id = "world"

            if b"native_queue" in city_val_dict and city_val_dict[b"native_queue"]:
                local = list(filter(None, city_val_dict[b"native_queue"].split(b",")))
            if b"nearby_queue" in city_val_dict and city_val_dict[b"nearby_queue"]:
                nearby = list(filter(None, city_val_dict[b"nearby_queue"].split(b",")))
            if b"nation_queue" in city_val_dict and city_val_dict[b"nation_queue"]:
                nation = list(filter(None, city_val_dict[b"nation_queue"].split(b",")))
            if b"megacity_queue" in city_val_dict and city_val_dict[b"megacity_queue"]:
                megacity = list(filter(None, city_val_dict[b"megacity_queue"].split(b",")))

            return (local, nearby, nation, megacity, use_city_id)

        except:
            logging_exception()
            logger.error("catch exception,err_log:%s" % traceback.format_exc())
            qs = DiaryQueue.objects.filter(city_id__in=[city_id, 'world'])
            # Assume that world queue must exist.
            if len(qs) == 1:
                obj = qs[0]
            else:
                obj = qs[0] if qs[0].city_id == city_id else qs[1]

            if obj.native_queue:
                local = list(filter(None, obj.native_queue.split(',')))
            if obj.nearby_queue:
                nearby = list(filter(None, obj.nearby_queue.split(',')))
            if obj.nation_queue:
                nation = list(filter(None, obj.nation_queue.split(',')))
            if obj.megacity_queue:
                megacity = list(filter(None, obj.megacity_queue.split(',')))

            use_city_id = obj.city_id if obj else use_city_id
            return (local, nearby, nation, megacity, use_city_id)

    @classmethod
    def fetch_device_diary_queue_data(cls, city_id, device_id):
        local = list()
        nearby = list()
        nation = list()
        megacity = list()
        use_city_id = city_id
        try:
            gm_kv_ins = None
            for gm_kv_host_item in settings.GM_KV_HOSTS:
                gm_kv_ins = cls.get_gm_kv_ins(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"], redis_db=gm_kv_host_item["db"],redis_password=gm_kv_host_item["password"])
                if gm_kv_ins:
                    break

            specify_city_id_key = "device_diary_queue_rerank:device_id:" + device_id + ":city_id:" + use_city_id
            city_val_dict = gm_kv_ins.hgetall(specify_city_id_key)

            if b"native_queue" in city_val_dict and city_val_dict[b"native_queue"]:
                local = list(filter(None, city_val_dict[b"native_queue"].split(b",")))
            if b"nearby_queue" in city_val_dict and city_val_dict[b"nearby_queue"]:
                nearby = list(filter(None, city_val_dict[b"nearby_queue"].split(b",")))
            if b"nation_queue" in city_val_dict and city_val_dict[b"nation_queue"]:
                nation = list(filter(None, city_val_dict[b"nation_queue"].split(b",")))
            if b"megacity_queue" in city_val_dict and city_val_dict[b"megacity_queue"]:
                megacity = list(filter(None, city_val_dict[b"megacity_queue"].split(b",")))

            return (local, nearby, nation, megacity, use_city_id)
        except:
            logging_exception()
            logger.error("catch exception,err_log:%s" % traceback.format_exc())
            obj = DeviceDiaryQueue.objects.filter(device_id=device_id, city_id=city_id).first()
            if obj and obj.native_queue:
                local = list(filter(None, obj.native_queue.split(',')))
            if obj and obj.nearby_queue:
                nearby = list(filter(None, obj.nearby_queue.split(',')))
            if obj and obj.nation_queue:
                nation = list(filter(None, obj.nation_queue.split(',')))
            if obj and obj.megacity_queue:
                megacity = list(filter(None, obj.megacity_queue.split(',')))

            use_city_id = obj.city_id if obj else use_city_id
            return (local, nearby, nation, megacity, use_city_id)

    @classmethod
    def fetch_diary(cls, device_id, card_type, city_id, size):
        try:
            def read_history(cid_list):
                if redis_client.exists(today_key):
                    redis_client.sadd(today_key, *cid_list)
                else:
                    redis_client.sadd(today_key, *cid_list)
                    redis_client.expire(today_key, 15 * 24 * 60 * 60)
                if redis_client.exists(read_key) and 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)

                redis_client.sadd(read_key, *cid_list)

            def dislike_cid_filter(device_id, cid_list):
                try:
                    key = str(device_id) + "_dislike_diary"
                    if gmkv.exists(key):
                        dislike = gmkv.smembers(key)
                        if len(cid_list) > 0:
                            if type(cid_list[0]) == int or type(cid_list[0]) == str:
                                cid_list = [i for i in cid_list if str(i).encode('utf-8') not in dislike]
                            else:
                                cid_list = [i for i in cid_list if i not in dislike]
                    return cid_list
                except:
                    return cid_list

            def fetch_after_filter_queue(device_id, city_id, name):
                local = list()
                try:
                    key = "device_diary_queue_after_filter:device_id:" + device_id + ":city_id:" + city_id + name
                    if gmkv.exists(key):
                        return json.loads(gmkv.get(key))
                    else:
                        return local
                except:

                    return local

            def write_after_filter_queue(device_id, city_id, cid_list, name):
                try:
                    key = "device_diary_queue_after_filter:device_id:" + device_id + ":city_id:" + city_id + name
                    if gmkv.exists(key):
                        gmkv.set(key, json.dumps(cid_list))
                    else:
                        gmkv.set(key, json.dumps(cid_list), ex=6 * 60 * 60)

                except:
                    logging_exception()
                    logger.error("catch exception,err_log:%s" % traceback.format_exc())

            def get_data(local, nearby, nation, megacity, cx, cy, cm, cz, x, y, z, m, size):
                nx = int(round(x * 1.0 / (x + y + z + m) * size))
                ny = int(round(y * 1.0 / (x + y + z + m) * size))
                nz = int(round(z * 1.0 / (x + y + z + m) * size))
                nm = int(round(m * 1.0 / (x + y + z + m) * size))
                nxyz = [nx, ny, nm, nz]
                xyz = [x, y, m, z]
                counter = Counter([nx, ny, nm, nz])
                if counter[0] == 2:
                    nxyz[nxyz.index(0)] += size - sum(nxyz)
                else:
                    nxyz[xyz.index(max(xyz))] += size - sum(nxyz)
                nx, ny, nm, nz = nxyz

                local_filter = dislike_cid_filter(device_id, cx)
                if len(local_filter) == 0:
                    local_filter = dislike_cid_filter(device_id, local)
                slocal = local_filter[:nx]
                have_x = local_filter[nx:]
                x_list = list(map(int, have_x))
                write_after_filter_queue(device_id, city_id, x_list, "native")

                ny += (nx - len(slocal))

                nearby_filter = dislike_cid_filter(device_id, cy)
                if len(nearby_filter) == 0:
                    nearby_filter = dislike_cid_filter(device_id, nearby)
                snearby = nearby_filter[:ny]
                have_y = nearby_filter[ny:]
                y_list = list(map(int, have_y))
                write_after_filter_queue(device_id, city_id, y_list, "nearby")

                nm += (ny - len(snearby))

                megacity_filter = dislike_cid_filter(device_id, cm)
                if len(megacity_filter) == 0:
                    megacity_filter = dislike_cid_filter(device_id, megacity)
                smegacity = megacity_filter[:nm]
                have_m = megacity_filter[nm:]
                m_list = list(map(int, have_m))
                write_after_filter_queue(device_id, city_id, m_list, "megacity")

                nz += (nm - len(smegacity))

                nation_filter = dislike_cid_filter(device_id, cz)
                if len(nation_filter) == 0:
                    nation_filter = dislike_cid_filter(device_id, nation)
                snation = nation_filter[:nz]
                have_z = snation[nz:]
                z_list = list(map(int, have_z))
                write_after_filter_queue(device_id, city_id, z_list, "nation")

                return chain(slocal, snearby, smegacity, snation)
            if device_id != '0':
                portrait_list = list()
                click_diary_size = 1
                search_diary_size = 4
                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"))
                user_portrait_diary_key = 'user_portrait_recommend_diary_queue:device_id:%s:%s' % \
                                          (device_id, datetime.datetime.now().strftime('%Y-%m-%d'))
                gmkv = None
                for gm_kv_host_item in settings.GM_KV_HOSTS:
                    gmkv = cls.get_gm_kv_ins(redis_ip=gm_kv_host_item["host"], redis_port=gm_kv_host_item["port"],
                                                  redis_db=gm_kv_host_item["db"],
                                                  redis_password=gm_kv_host_item["password"])
                    if gmkv:
                        break

                if redis_client.exists(user_portrait_diary_key):
                    user_portrait_diary_dict = redis_client.hgetall(user_portrait_diary_key)
                    user_portrait_cursor = str(user_portrait_diary_dict[b'cursor'], encoding='utf-8')
                    if user_portrait_cursor == '0':
                        if b'len_cursor' in user_portrait_diary_dict.keys():
                            user_portrait_diary_list = json.loads(user_portrait_diary_dict[b'diary_queue'])
                            filter_user_portrait_diary_list = dislike_cid_filter(device_id, user_portrait_diary_list)
                            if len(filter_user_portrait_diary_list) > size:
                                portrait_list = filter_user_portrait_diary_list[:size]
                                redis_client.hset(user_portrait_diary_key, 'diary_queue',
                                                  json.dumps(filter_user_portrait_diary_list[size:]))
                                portrait_list = list(map(int, portrait_list))
                                read_history(portrait_list)
                                return portrait_list
                            else:
                                size = size - len(filter_user_portrait_diary_list)
                                portrait_list = filter_user_portrait_diary_list
                                redis_client.delete(user_portrait_diary_key)

                search_diary_recommend_key = "TS:search_recommend_diary_queue:device_id:" + str(device_id)
                search_list = list()

                if redis_client.exists(search_diary_recommend_key) and size > 3:
                    search_diary_recommend_dict = redis_client.hgetall(search_diary_recommend_key)
                    search_diary_recommend_list = json.loads(search_diary_recommend_dict[b'diary_queue'])
                    search_diary_recommend_list = dislike_cid_filter(device_id, search_diary_recommend_list)
                    if len(search_diary_recommend_list) == 0:
                        redis_client.delete(search_diary_recommend_key)
                    elif len(search_diary_recommend_list) <= search_diary_size:
                        search_list = search_diary_recommend_list
                        size = size - len(search_diary_recommend_list)
                        redis_client.delete(search_diary_recommend_key)

                    else:
                        search_list = search_diary_recommend_list[:search_diary_size]
                        size = size - search_diary_size
                        redis_client.hset(search_diary_recommend_key, 'diary_queue',
                                          json.dumps(search_diary_recommend_list[search_diary_size:]))
                    if size <= 0:
                        portrait_list.extend(search_list)
                        portrait_list = list(map(int, portrait_list))
                        read_history(portrait_list)
                        return portrait_list

                diary_recommend_key = "TS:recommend_diary_queue:device_id:" + str(device_id)
                ts_recommend_list = list()
                if redis_client.exists(diary_recommend_key) and size > 0:
                    diary_recommend_dict = redis_client.hgetall(diary_recommend_key)
                    diary_recommend_list = json.loads(diary_recommend_dict[b'diary_queue'])
                    diary_recommend_list = dislike_cid_filter(device_id, diary_recommend_list)
                    if len(diary_recommend_list) == 0:
                        redis_client.delete(diary_recommend_key)

                    elif len(diary_recommend_list) <= click_diary_size:
                        ts_recommend_list = diary_recommend_list
                        redis_client.delete(diary_recommend_key)
                        size = size - len(ts_recommend_list)

                    else:
                        size = size - click_diary_size
                        ts_recommend_list = diary_recommend_list[:click_diary_size]
                        diary_recommend_list_json = json.dumps(diary_recommend_list[click_diary_size:])
                        redis_client.hset(diary_recommend_key, 'diary_queue', diary_recommend_list_json)
                    if size <= 0:
                        portrait_list.extend(search_list)
                        portrait_list.extend(ts_recommend_list)
                        portrait_list = list(map(int, portrait_list))
                        read_history(portrait_list)
                        return portrait_list

                if size > 0:
                    try:
                        (local, nearby, nation, megacity, city_id) = cls.fetch_device_diary_queue_data(city_id,
                                                                                                       device_id)
                        if len(local) == 0 and len(nearby) == 0 and len(nation) == 0 and len(megacity) == 0:
                            (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)

                    except:
                        logging_exception()
                        (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)
                    x, y, m, z = cls.get_city_scale(city_id)
                    cx = fetch_after_filter_queue(device_id, city_id, "native")
                    cy = fetch_after_filter_queue(device_id, city_id, "nearby")
                    cz = fetch_after_filter_queue(device_id, city_id, "nation")
                    cm = fetch_after_filter_queue(device_id, city_id, "megacity")
                    data = get_data(
                        local, nearby, nation, megacity,
                        cx, cy, cm, cz,
                        x, y, z, m, size)

                    portrait_list.extend(search_list)
                    portrait_list.extend(ts_recommend_list)
                    portrait_list.extend(data)
                    if len(portrait_list) == 0:
                        (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)
                        portrait_list = cls.get_queue(local, nearby, nation, megacity,
                                                      device_id, city_id, size, x, y, z, m)
                    portrait_list = list(map(int, portrait_list))
                    if len(portrait_list) != 0:
                        read_history(portrait_list)
                    return portrait_list
            else:

                try:
                    (local, nearby, nation, megacity, city_id) = cls.fetch_device_diary_queue_data(city_id, device_id)
                    if len(local) == 0 and len(nearby) == 0 and len(nation) == 0 and len(megacity) == 0:
                        (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)
                except:
                    logging_exception()
                    (local, nearby, nation, megacity, city_id) = cls.fetch_diary_queue_data(city_id)

                key = '{device_id}-{city_id}-{date}'.format(device_id=device_id,
                                                            city_id=city_id, date=RecommendFeed.current_date())
                # strategy rule: when user refresh over 30 loadings, reset native nearby nation queue cursor.
                counter_key = key + '-counter_v1'
                counter = redis_client.incr(counter_key)
                if counter == 1:
                    redis_client.expire(counter_key, 24 * 60 * 60)
                cursor_key = key + '-cursor_v1'
                cursor = redis_client.get(cursor_key) or b'0-0-0-0'
                # if counter > 30:
                #     cursor = b'0-0-0-0'
                #     redis_client.delete(counter_key)
                cx, cy, cm, cz = map(int, cursor.split(b'-'))

                x, y, m, z = cls.get_city_scale(city_id)
                data, ncx, ncy, ncm, ncz = cls.get_scale_data(
                    local, nearby, nation, megacity,
                    cx, cy, cm, cz,
                    x, y, z, m, size
                )

                if ncx == cx and ncy == cy:  # native queue and nearby queue
                    logger.info("diary queue reach end,cx:%d,cy:%d,cm:%d,cz:%d", cx, cy, cm, cz)
                    # redis_client.delete(counter_key)
                    # data, ncx, ncy, ncm, ncz = cls.get_scale_data(
                    #     local, nearby, nation, megacity,
                    #     0, 0, 0, 0,
                    #     x, y, z, m, size
                    # )
                    ncx = ncy = ncm = ncz = 0

                val = '-'.join(map(str, [ncx, ncy, ncm, ncz]))
                redis_client.set(cursor_key, val, ex=24 * 60 * 60)
                data = list(map(int, data))

                return data
        except:
            logging_exception()
            return []


    @classmethod
    def get_queue(cls,local, nearby, nation, megacity, device_id,city_id,size,x,y,z,m):
        key = '{device_id}-{city_id}-{date}'.format(device_id=device_id,
                                                    city_id=city_id, date=RecommendFeed.current_date())
        counter_key = key + '-counter_v1'
        counter = redis_client.incr(counter_key)
        if counter == 1:
            redis_client.expire(counter_key, 24 * 60 * 60)
        cursor_key = key + '-cursor_v1'
        cursor = redis_client.get(cursor_key) or b'0-0-0-0'
        cx, cy, cm, cz = map(int, cursor.split(b'-'))

        def get_scale(local, nearby, nation, megacity, cx, cy, cm, cz, x, y, z, m, size):
            nx = int(round(x * 1.0 / (x + y + z + m) * size))
            ny = int(round(y * 1.0 / (x + y + z + m) * size))
            nz = int(round(z * 1.0 / (x + y + z + m) * size))
            nm = int(round(m * 1.0 / (x + y + z + m) * size))
            nxyz = [nx, ny, nm, nz]
            xyz = [x, y, m, z]
            counter = Counter([nx, ny, nm, nz])
            if counter[0] == 2:
                nxyz[nxyz.index(0)] += size - sum(nxyz)
            else:
                nxyz[xyz.index(max(xyz))] += size - sum(nxyz)
            nx, ny, nm, nz = nxyz

            slocal = local[cx:cx + nx]
            cx = min(cx + nx, len(local))
            ny += (nx - len(slocal))

            snearby = nearby[cy:cy + ny]
            cy = min(cy + ny, len(nearby))
            nm += (ny - len(snearby))

            smegacity = megacity[cm: cm + nm]
            cm = min(cm + nm, len(megacity))
            nz += (nm - len(smegacity))

            snation = nation[cz:cz + nz]
            cz = min(cz + nz, len(nation))
            return chain(slocal, snearby, smegacity, snation), cx, cy, cm, cz

        data, ncx, ncy, ncm, ncz = get_scale(
            local, nearby, nation, megacity,
            cx, cy, cm, cz,
            x, y, z, m, size)

        if ncx == cx and ncy == cy:  # native queue and nearby queue
            logger.info("diary queue reach end,cx:%d,cy:%d,cm:%d,cz:%d", cx, cy, cm, cz)

            ncx = ncy = ncm = ncz = 0

        val = '-'.join(map(str, [ncx, ncy, ncm, ncz]))
        redis_client.set(cursor_key, val, ex=24 * 60 * 60)
        return list(map(int, data))

    @staticmethod
    def get_city_scale(city_id):
        try:
            c = CityScale.objects.get(city_id=city_id)
            x, y, z, m = c.native, c.nearby, c.nation, c.megacity
        except CityScale.DoesNotExist:
            try:
                c = City.objects.get(id=city_id)
                if c.level in (CITY_LEVEL.SUPER, CITY_LEVEL.ONE):
                    x, y, m, z = 4, 3, 0, 3
                elif c.level == CITY_LEVEL.TWO:
                    x, y, m, z = 3, 3, 0, 3
                elif c.level == CITY_LEVEL.THREE:
                    x, y, m, z = 1, 4, 0, 5
                else:
                    x, y, m, z = 0, 0, 0, 10
            except City.DoesNotExist:
                x, y, m, z = 0, 0, 0, 10

        return x, y, m, z

    @staticmethod
    def get_scale_data(local, nearby, nation, megacity, cx, cy, cm, cz, x, y, z, m, size):
        """

        :param local:           local diary queue
        :param nearby:          nearby diary queue
        :param nation:          nation diary queue
        :param megacity:        megacity diary queue
        :param cx:              seen local diary offset
        :param cy:              seen nearby diary offset
        :param cz:              seen nation diary offset
        :param cm:              seen megacity diary offset
        :param x:               local diary scale factor
        :param y:               nearby diary scale factor
        :param z:               nation diary scale factor
        :param m:               megacity diary scale factor
        :param size:            nubmer of diary
        :return:
        """
        # 本地 临近 特大城市 全国 四个层级 都按照的是四舍五入取得方式
        # 针对出现的问题，本次相应的优化是：
        # 1、如果出现两个层级为零，且有剩余坑位时，则按照本地 临近 全国的优先级，先给优先级高且为零的层级一个坑位。
        # 2、如果所有层级都非零，且有剩余坑位时，则优先给权重占比大的层级一个坑位。
        # 3、如果只有一个层级为零，且有剩余坑位时，则优先填充权重占比大的层级一个坑位。
        nx = int(round(x * 1.0 / (x + y + z + m) * size))
        ny = int(round(y * 1.0 / (x + y + z + m) * size))
        nz = int(round(z * 1.0 / (x + y + z + m) * size))
        nm = int(round(m * 1.0 / (x + y + z + m) * size))
        nxyz = [nx, ny, nm, nz]
        xyz = [x, y, m, z]
        counter = Counter([nx, ny, nm, nz])
        if counter[0] == 2:
            nxyz[nxyz.index(0)] += size - sum(nxyz)
        else:
            nxyz[xyz.index(max(xyz))] += size - sum(nxyz)
        nx, ny, nm, nz = nxyz

        slocal = local[cx:cx + nx]
        cx = min(cx + nx, len(local))
        ny += (nx - len(slocal))

        snearby = nearby[cy:cy + ny]
        cy = min(cy + ny, len(nearby))
        nm += (ny - len(snearby))

        smegacity = megacity[cm: cm + nm]
        cm = min(cm + nm, len(megacity))
        nz += (nm - len(smegacity))

        snation = nation[cz:cz + nz]
        cz = min(cz + nz, len(nation))

        return chain(slocal, snearby, smegacity, snation), cx, cy, cm, cz
