# coding=utf-8
from __future__ import unicode_literals, print_function, absolute_import

import time
import datetime
import logging
import traceback
import django.db.models
from django.conf import settings
from libs.es import ESPerform
import elasticsearch
import elasticsearch.helpers
import sys
import copy

from trans2es.models import doctor, itemwiki, collectwiki, brandwiki, productwiki, tag, wordresemble
from trans2es.utils.doctor_transfer import DoctorTransfer
from trans2es.utils.hospital_transfer import HospitalTransfer
from trans2es.utils.itemwiki_transfer import ItemWikiTransfer
from trans2es.utils.collectwiki_transfer import CollectWikiTransfer
from trans2es.utils.brandwiki_transfer import BrandWikiTransfer
from trans2es.utils.productwiki_transfer import ProduceWikiTransfer
from trans2es.utils.tag_transfer import TagTransfer
from trans2es.utils.wordresemble import WordResemble
from libs.es import ESPerform
from libs.tools import tzlc, getMd5Digest
from trans2es.commons.words_utils import QueryWordAttr

from gm_types.gaia import SERVICE_ITEM_PRICE_TYPE, DOCTOR_TYPE
from gm_types.gaia import (
    DOCTOR_PROCESS_STATUS, DOCTOR_TYPE, PHONE_HINTS, TOPIC_TYPE, TAG_TYPE, DisplayTag,
    SERVICE_FLAG)

__es = None


def get_elasticsearch_instance():
    global __es
    if __es is None:
        __es = ESPerform.get_cli()
    return __es


def get_es_list_by_type(es_type):
    return [get_elasticsearch_instance()]


class TypeInfo(object):
    def __init__(
            self,
            name,
            type,
            model,
            query_deferred,
            get_data_func,
            bulk_insert_chunk_size,
            round_insert_chunk_size,
            round_insert_period,
            batch_get_data_func=None,  # receive a list of pks, not instance
            logic_database_id=None,
    ):
        self.name = name
        self.type = type
        self.model = model
        self.query_deferred = query_deferred
        self.get_data_func = get_data_func
        self.batch_get_data_func = batch_get_data_func
        self.pk_blacklist = ()
        self.bulk_insert_chunk_size = bulk_insert_chunk_size
        self.round_insert_chunk_size = round_insert_chunk_size
        self.round_insert_period = round_insert_period
        self.logic_database_id = logic_database_id

    @property
    def query(self):
        return self.query_deferred()

    @property
    def queryset(self):
        return django.db.models.QuerySet(model=self.model, query=self.query)

    @property
    def pk_blacklist(self):
        return self.__pk_blacklist

    @pk_blacklist.setter
    def pk_blacklist(self, value):
        self.__pk_blacklist = frozenset(value)

    def bulk_get_data(self, instance_iterable):
        data_list = []
        if self.batch_get_data_func:
            _pk_list = [getattr(instance, 'pk', None) for instance in instance_iterable]
            not_found_pk_list = []
            blacklisted_pk_list = []
            pk_list = []
            for pk in _pk_list:
                if pk is None:
                    not_found_pk_list.append(pk)
                elif pk in self.__pk_blacklist:
                    blacklisted_pk_list.append(pk)
                else:
                    pk_list.append(pk)
            if not_found_pk_list:
                logging.exception('those pks not found for name={}, doc_type={}, pk_list={}'.format(
                    self.name,
                    self.type,
                    str(not_found_pk_list),
                ))
            if blacklisted_pk_list:
                logging.info('those pks are in blacklist for name={}, doc_type={}, pk_list={}'.format(
                    self.name,
                    self.type,
                    str(blacklisted_pk_list),
                ))
            try:
                data_list = self.batch_get_data_func(pk_list)
            except Exception:
                traceback.print_exc()
                logging.exception('bulk_get_data for name={}, doc_type={}, pk_list={}'.format(
                    self.name,
                    self.type,
                    str(pk_list),
                ))
        else:
            for instance in instance_iterable:
                pk = getattr(instance, 'pk', None)
                try:
                    if pk is None:
                        raise Exception('pk not found')
                    if pk in self.__pk_blacklist:
                        logging.info('bulk_get_data for name={}, doc_type={}, pk={}: ignore blacklisted pk'.format(
                            self.name,
                            self.type,
                            pk,
                        ))
                        continue
                    data = self.get_data_func(instance)
                    (item_dict, suggest_list) = data

                    if item_dict:
                        if item_dict["tips_name_type"] == 4:
                            instance.name = instance.keyword

                        resemble_list = WordResemble.get_word_resemble_list(str(instance.name))

                        for suggest_item in suggest_list:
                            suggest_dict = copy.deepcopy(item_dict)
                            suggest_dict["suggest_type"] = suggest_item["suggest_type"]
                            suggest_dict["offline_score"] = suggest_item["word_weight"] + suggest_dict["order_weight"]
                            suggest_dict["id"] = str(suggest_dict["id"]) + "_" + str(suggest_item["cur_index"])
                            suggest_dict["suggest"] = {
                                "input": suggest_item["input"],
                                "weight": int(suggest_dict["offline_score"]),
                                "contexts": {
                                    "is_online": suggest_dict["is_online"]
                                }
                            }
                            data_list.append(suggest_dict)
                            if item_dict["tips_name_type"] != 4:
                                for resemble_item in resemble_list:
                                    resemble_dict = copy.deepcopy(suggest_dict)
                                    resemble_dict["id"] = suggest_dict["id"] + "_" + getMd5Digest(resemble_item)
                                    resemble_dict["ori_name"] = resemble_item
                                    resemble_dict["results_num"] = QueryWordAttr.get_query_results_num(resemble_item)
                                    resemble_dict["offline_score"] = 0
                                    resemble_dict["suggest"]["weight"] = 0
                                    data_list.append(resemble_dict)
                            else:
                                pass
                except Exception:
                    traceback.print_exc()
                    logging.exception('bulk_get_data for name={}, doc_type={}, pk={}'.format(
                        self.name,
                        self.type,
                        pk,
                    ))
        return data_list

    def elasticsearch_bulk_insert_data(self, sub_index_name, data_list, es=None):

        # assert (es is not None)
        # index = ESPerform.get_official_index_name(sub_index_name=sub_index_name,index_flag="write")
        # bulk_actions = []
        # for data in data_list:
        #     bulk_actions.append({
        #         '_op_type': 'index',
        #         '_index': index,
        #         '_type': "_doc",
        #         '_id': data['id'],
        #         '_source': data,
        #     })
        #
        # es_result = None
        # if bulk_actions:
        #     for t in es:
        #         try:
        #             es_result = elasticsearch.helpers.bulk(client=t, actions=bulk_actions)
        #         except Exception as e:
        #             traceback.print_exc()
        #             es_result = 'error'

        return ESPerform.es_helpers_bulk(es, data_list, sub_index_name, True)

    def elasticsearch_bulk_insert(self, sub_index_name, instance_iterable, es=None):
        data_list = self.bulk_get_data(instance_iterable)
        return self.elasticsearch_bulk_insert_data(
            sub_index_name=sub_index_name,
            data_list=data_list,
            es=es,
        )

    def insert_table_by_pk_list(self, sub_index_name, pk_list, es=None, use_batch_query_set=False, es_type=None):
        if use_batch_query_set:
            qs = self.queryset
        else:
            qs = self.model.objects.all()

        if es_type == "doctor_tips":
            instance_list = qs.filter(pk__in=pk_list, doctor_type=DOCTOR_TYPE.DOCTOR)
        elif es_type == "wordrel_tips":
            instance_list = qs.filter(pk__in=pk_list, category__in=[13, 12, 11, 9, 1])
        elif es_type == "tag_tips":
            instance_list = qs.filter(pk__in=pk_list, tag_type__in=[TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM,
                                                                    TAG_TYPE.ITEM_WIKI])
        else:
            instance_list = qs.filter(pk__in=pk_list)

        data_list = self.bulk_get_data(instance_list)
        self.elasticsearch_bulk_insert_data(
            sub_index_name=sub_index_name,
            data_list=data_list,
            es=es,
        )

    def insert_table_chunk(self, sub_index_name, table_chunk, es=None):

        start_clock = time.clock()
        start_time = time.time()

        instance_list = list(table_chunk)

        stage_1_time = time.time()

        data_list = self.bulk_get_data(instance_list)

        stage_2_time = time.time()

        es_result = ESPerform.es_helpers_bulk(
            es_cli=es,
            data_list=data_list,
            sub_index_name=sub_index_name,
            auto_create_index=True
        )

        stage_3_time = time.time()
        end_clock = time.clock()

        return ('{datetime} {index_prefix} {type_name:10s} {pk_start:>15s} {pk_stop:>15s} {count:5d} '
                '{stage_1_duration:6.3f} {stage_2_duration:6.3f} {stage_3_duration:6.3f} {clock_duration:6.3f} '
                '{response}').format(
            datetime=datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f'),
            index_prefix=sub_index_name,
            type_name=self.name,
            pk_start=repr(table_chunk.get_pk_start()),
            pk_stop=repr(table_chunk.get_pk_stop()),
            count=len(instance_list),
            stage_1_duration=stage_1_time - start_time,
            stage_2_duration=stage_2_time - stage_1_time,
            stage_3_duration=stage_3_time - stage_2_time,
            clock_duration=end_clock - start_clock,
            response=es_result,
        )


_get_type_info_map_result = None


def get_type_info_map():
    global _get_type_info_map_result
    if _get_type_info_map_result:
        return _get_type_info_map_result

    type_info_list = [
        TypeInfo(
            name='suggest',
            type='doctor_tips',  # doctor
            model=doctor.Doctor,
            query_deferred=lambda: doctor.Doctor.objects.all().filter(doctor_type=DOCTOR_TYPE.DOCTOR).query,
            get_data_func=DoctorTransfer.get_doctor_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='hospital_tips',  # hospital
            model=doctor.Hospital,
            # query_deferred=lambda: doctor.Doctor.objects.all().filter(doctor_type=DOCTOR_TYPE.OFFICER).query,
            query_deferred=lambda: doctor.Hospital.objects.all().query,
            get_data_func=HospitalTransfer.get_hospital_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='itemwiki_tips',  # itemwiki
            model=itemwiki.ItemWiki,
            query_deferred=lambda: itemwiki.ItemWiki.objects.all().query,
            get_data_func=ItemWikiTransfer.get_itemwiki_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='collectwiki_tips',  # collectwiki
            model=collectwiki.CollectWiki,
            query_deferred=lambda: collectwiki.CollectWiki.objects.all().query,
            get_data_func=CollectWikiTransfer.get_collectwiki_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='brandwiki_tips',  # brandwiki
            model=brandwiki.BrandWiki,
            query_deferred=lambda: brandwiki.BrandWiki.objects.all().query,
            get_data_func=BrandWikiTransfer.get_brandwiki_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='productwiki_tips',  # productwiki
            model=productwiki.ProductWiki,
            query_deferred=lambda: productwiki.ProductWiki.objects.all().query,
            get_data_func=ProduceWikiTransfer.get_productwiki_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='tag_tips',  # tag
            model=tag.Tag,
            query_deferred=lambda: tag.Tag.objects.all().filter(
                tag_type__in=[TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI]).query,
            get_data_func=TagTransfer.get_tag_suggest_data_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='wordrel_tips',  # tag
            model=wordresemble.WordRel,
            query_deferred=lambda: wordresemble.WordRel.objects.filter(
                category__in=[13, 12, 11, 9, 1]).query,
            get_data_func=WordResemble.get_resemble_list,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='doctor_tips',  # doctor
        #     model=doctor.Doctor,
        #     query_deferred=lambda: doctor.Doctor.objects.all().filter(doctor_type=DOCTOR_TYPE.DOCTOR).query,
        #     get_data_func=DoctorTransfer.get_doctor_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='hospital_tips',  # hospital
        #     model=doctor.Hospital,
        #     # query_deferred=lambda: doctor.Doctor.objects.all().filter(doctor_type=DOCTOR_TYPE.OFFICER).query,
        #     query_deferred=lambda: doctor.Hospital.objects.all().query,
        #     get_data_func=HospitalTransfer.get_hospital_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='itemwiki_tips',  # itemwiki
        #     model=itemwiki.ItemWiki,
        #     query_deferred=lambda: itemwiki.ItemWiki.objects.all().query,
        #     get_data_func=ItemWikiTransfer.get_itemwiki_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='collectwiki_tips',  # collectwiki
        #     model=collectwiki.CollectWiki,
        #     query_deferred=lambda: collectwiki.CollectWiki.objects.all().query,
        #     get_data_func=CollectWikiTransfer.get_collectwiki_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='brandwiki_tips',  # brandwiki
        #     model=brandwiki.BrandWiki,
        #     query_deferred=lambda: brandwiki.BrandWiki.objects.all().query,
        #     get_data_func=BrandWikiTransfer.get_brandwiki_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='productwiki_tips',  # productwiki
        #     model=productwiki.ProductWiki,
        #     query_deferred=lambda: productwiki.ProductWiki.objects.all().query,
        #     get_data_func=ProduceWikiTransfer.get_productwiki_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='tag_tips',  # tag
        #     model=tag.Tag,
        #     query_deferred=lambda: tag.Tag.objects.all().filter(
        #         tag_type__in=[TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI]).query,
        #     get_data_func=TagTransfer.get_tag_suggest_data_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # ),
        # TypeInfo(
        #     name='suggest-v1',
        #     type='wordrel_tips',  # tag
        #     model=wordresemble.WordRel,
        #     query_deferred=lambda: wordresemble.WordRel.objects.filter(
        #         category__in=[13, 12, 11, 9, 1]).query,
        #     get_data_func=WordResemble.get_resemble_list,
        #     bulk_insert_chunk_size=100,
        #     round_insert_chunk_size=5,
        #     round_insert_period=2,
        # )
    ]

    type_info_map = {
        type_info.type: type_info
        for type_info in type_info_list
    }

    _get_type_info_map_result = type_info_map
    return type_info_map


def tips_attr_sync_to_redis_type_info_map():
    global _get_type_info_map_result
    if _get_type_info_map_result:
        return _get_type_info_map_result

    type_info_list = [
        TypeInfo(
            name='suggest',
            type='doctor_results_num',  # doctor结果数
            model=doctor.Doctor,
            query_deferred=lambda: doctor.Doctor.objects.all().query,
            get_data_func=DoctorTransfer.get_doctor_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='tag_results_num',  # tag结果数
            model=tag.Tag,
            query_deferred=lambda: tag.Tag.objects.all().filter(
                tag_type__in=[TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI]).query,
            get_data_func=TagTransfer.get_tag_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='itemwiki_results_num',  # itemwiki
            model=itemwiki.ItemWiki,
            query_deferred=lambda: itemwiki.ItemWiki.objects.all().query,
            get_data_func=ItemWikiTransfer.get_wiki_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='collectwiki_results_num',  # collectwiki
            model=collectwiki.CollectWiki,
            query_deferred=lambda: collectwiki.CollectWiki.objects.all().query,
            get_data_func=ItemWikiTransfer.get_wiki_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='brandwiki_results_num',  # brandwiki
            model=brandwiki.BrandWiki,
            query_deferred=lambda: brandwiki.BrandWiki.objects.all().query,
            get_data_func=ItemWikiTransfer.get_wiki_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='productwiki_results_num',  # productwiki
            model=productwiki.ProductWiki,
            query_deferred=lambda: productwiki.ProductWiki.objects.all().query,
            get_data_func=ItemWikiTransfer.get_wiki_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='hospital_latlng',  # hospital_pos_info
            model=doctor.Hospital,
            query_deferred=lambda: doctor.Hospital.objects.all().query,
            get_data_func=HospitalTransfer.get_hospital_lat_lng_info_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='wordrel_results_num',  # api_wordrel
            model=wordresemble.WordRel,
            query_deferred=lambda: wordresemble.WordRel.objects.filter(
                category__in=[13, 12, 11, 9, 1]).query,
            get_data_func=WordResemble.get_all_data_name_mapping_results_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        ),
        TypeInfo(
            name='suggest',
            type='wordresemble_results_num',  # api_wordrelresemble
            model=wordresemble.WordRel,
            query_deferred=lambda: wordresemble.WordRel.objects.filter(
                category__in=[13, 12, 11, 9, 1]).query,
            get_data_func=WordResemble.set_data_to_redis,
            bulk_insert_chunk_size=100,
            round_insert_chunk_size=5,
            round_insert_period=2,
        )
    ]

    type_info_map = {
        type_info.type: type_info
        for type_info in type_info_list
    }

    _get_type_info_map_result = type_info_map
    return type_info_map
