# coding:utf-8
import datetime
import json

from math import exp
import math
from django.db.models import Max, Avg
from django.conf import settings

from api.models.service import Service
from rpc.cache import service_config_cache, rankcache
from statistic.models import StatisticServiceSmartRankV2, StatisticServiceSmartRankV3, StatisticServiceSmartRankV4
from api.models.smart_rank import ManualIndex, SmartRank, SmartRankFactor, SmartRankCompare
from django.db import connections
from math import sqrt
from rpc.all import get_rpc_remote_invoker
from rpc.tool.log_tool import smart_rank_logger

from api.models import City
from search.utils.service import search_service_smart_rank
from injection.data_sync.tasks import write_to_es
import traceback

class WeightCache(object):
    cache = service_config_cache

    __slots__ = ('cache_name', 'consult', 'ctr', 'order',  'refund',  'diary', 'evaluate', "income")

    def __init__(self):
        self.cache_name = 'index_cache'
        self.consult = 10
        self.ctr = 20
        self.order = 20
        self.refund = -5
        self.income = 30
        self.diary = 20
        self.evaluate = 5

    def get(self):
        value = self.cache.get(self.cache_name)

        if not value:
            value = {
                'consult': self.consult,
                'ctr': self.ctr,
                'order': self.order,
                'refund': self.refund,
                'diary': self.diary,
                'evaluate': self.evaluate,
                "income": self.income
            }
            return value

        data = json.loads(value)
        return dict(filter(lambda x:x[0] in self.__slots__, data.items()))

    def set(self, value):
        assert isinstance(value, dict)
        _d = json.dumps(value)
        self.cache.set(self.cache_name, _d)

        return _d


weight_cache = WeightCache()


def calc_city_pv_ctr():
    data = rankcache.get("calc_city_pv_ctr")
    if data:
        return json.loads(data)
    slave_db = settings.SLAVE_DB_NAME
    latest = StatisticServiceSmartRankV3.objects.using(slave_db).aggregate(Max('stat_date'))['stat_date__max']
    citys = City.objects.all()
    city_top5_map = {}
    for city in citys:
        services = Service.objects.using(settings.SLAVE_DB_NAME).filter(doctor__hospital__city=city).values_list('id', flat=True)
        query = StatisticServiceSmartRankV3.objects.using(settings.SLAVE_DB_NAME).filter(service_id__in=services, stat_date=latest).filter(service_detail_view_count_30__gt=0)
        count = query.count()
        if count <= 1:
            continue
        pos_top5 = int(math.ceil(count*0.2))
        city_top5 = query.order_by("-service_detail_view_count_30")[pos_top5]
        city_top5_map[city.id] =city_top5.service_detail_view_count_30
    rankcache.setex("calc_city_pv_ctr",86400, json.dumps(city_top5_map))
    return city_top5_map



class GetQuerySet(object):
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            slave_db = settings.SLAVE_DB_NAME
            orig = super(GetQuerySet, cls)
            cls._instance = orig.__new__(cls, *args, **kw)

            latest = StatisticServiceSmartRankV2.objects.using(slave_db).aggregate(Max('stat_date'))['stat_date__max']
            sql = """
                select v2.service_id,v2.ctr_value,v2.org_income_value,v2.org_deal_value,v1.consult_value,
                       v2.service_detail_view_count_30, v2.org_income_value_60, v2.org_income_value_90, v2.org_income_value_180,
                       v2.org_deal_value_60, v2.org_deal_value_90, v2.org_deal_value_180, 
                       api_hospital.city_id
                  from statistic_service_smart_rank_v2 v2 
                  left join statistic_service_smart_rank v1 on v2.service_id=v1.service_id 
                  left join api_service on v2.service_id=api_service.id
                  left join api_doctor on api_service.doctor_id = api_doctor.id
                  left join api_hospital on api_doctor.hospital_id=api_hospital.id
                  where v1.stat_date='{}' and v2.stat_date='{}'
            """.format(latest,latest)
            print(sql)
            cursor = connections[slave_db].cursor()
            cursor.execute(sql)
            items = cursor.fetchall()
            statistic_dict = {}
            for item in items:
                service_id = item[0]
                ctr_value = item[1]
                org_income_value = item[2]
                org_deal_value = item[3]
                consult_value = item[4]
                service_detail_view_count_30 = item[5]
                org_income_value_60 = item[6]
                org_income_value_90 = item[7]
                org_income_value_180 = item[8]
                org_deal_value_60 = item[9]
                org_deal_value_90 = item[10]
                org_deal_value_180 = item[11]
                city_id = item[12]
                statistic_dict[int(service_id)] = {
                    "ctr_value":ctr_value,
                    "org_income_value":org_income_value,
                    "org_deal_value":org_deal_value,
                    "consult_value":consult_value,
                    "service_detail_view_count_30": service_detail_view_count_30,
                    "org_income_value_60": org_income_value_60,
                    "org_income_value_90": org_income_value_90,
                    "org_income_value_180": org_income_value_180,
                    "org_deal_value_60": org_deal_value_60,
                    "org_deal_value_90": org_deal_value_90,
                    "org_deal_value_180": org_deal_value_180,
                    "city_id":city_id
                }

            #statistic = StatisticServiceSmartRankV2.objects.using(slave_db).filter(stat_date=latest)
            m = ManualIndex.objects.using(slave_db).all()
            day = datetime.date.today() + datetime.timedelta(-7)
            rank_group_by_city = SmartRank.objects.using(slave_db).filter(
                new_smart_rank__gt=0, service__start_time__lt=day
            ).values('service__doctor__hospital__city_id').annotate(avg=Avg('new_smart_rank'))

            cls._instance.__dict__['statistic_dict'] = statistic_dict
            cls._instance.__dict__["city_pv_map"] = calc_city_pv_ctr()


            cls._instance.__dict__['manual_dict'] = {
                _m.service_id: _m for _m in m
            }

            cls._instance.__dict__['city_rank'] = {
                rank['service__doctor__hospital__city_id']: rank['avg'] for rank in rank_group_by_city
            }

        return cls._instance

    def __init__(self):
        pass



class SmartRankCalculate(object):

    stat_date = datetime.date.today()

    def __init__(self, service, from_cache=True):
        self.service = service
        self.from_cache = from_cache

    def get_detail_value(self):
        queryset = GetQuerySet()
        try:
            index = queryset.statistic_dict[self.service.id]
            index['ctr_value'] = self.get_ctr_index(index['ctr_value'])
            index['consult_value'] = self.get_consult_index(index['consult_value'])
            return index
        except KeyError:
            return None

    def get_value(self):
        detail_value = self.get_detail_value()
        print(self.service.id, detail_value)
        #如果没有数据  该美购smartrank为0
        if detail_value is None:
            return 0

        service_detail_view_count_30 = detail_value['service_detail_view_count_30']
        city_id = detail_value['city_id']
        queryset = GetQuerySet()
        city_pv_map = queryset.city_pv_map
        min_pv = city_pv_map.get(city_id,0)
        if service_detail_view_count_30 < min_pv:
            ctr_value = 0.01
        else:
            ctr_value = detail_value['ctr_value']


        org_value_30 = self._get_org_value(detail_value['org_income_value'],detail_value['org_deal_value'])
        org_value_60 = self._get_org_value(detail_value["org_income_value_60"], detail_value["org_deal_value_60"])
        org_value_90 =  self._get_org_value(detail_value["org_income_value_90"], detail_value["org_deal_value_90"])
        org_value_180 = self._get_org_value(detail_value["org_income_value_180"], detail_value["org_deal_value_180"])
        org_value = 0.5*org_value_30 + 0.2*org_value_60 + 0.2*org_value_90 + 0.1*org_value_180

        consult_value = detail_value['consult_value']
        consult_value = sqrt(consult_value)
        ctr_value = sqrt(ctr_value)
        value = org_value * ctr_value * consult_value
        print(value)
        return value

    def _get_org_value(self,org_income_value, org_deal_value):
        org_value = org_income_value * 3 + org_deal_value
        org_value = org_value if org_value >= 0.01 else 0.01
        return org_value



    def _get_service_info(self):
        try:
            value = float(self.service.operation_effect_rating) + float(self.service.doctor_attitude_rating) + float(
                self.service.hospital_env_rating)
            value /= 3
            if value == 5:
                return 100, self._get_extra_value(self.service.start_time)
            if value <= 4.5:
                return 0, self._get_extra_value(self.service.start_time)

            return 100 * (value - 4.5) / 0.5, self._get_extra_value(self.service.start_time)
        except Service.DoesNotExist:
            return 0, 0

    @staticmethod
    def get_diary_index(count):
        if count > 7:
            return 100

        if count < 1:
            return 0

        return 100 * (count - 1) / 8.0

    def _get_manual_parameter(self):
        try:
            if self.from_cache:
                queryset = GetQuerySet()
                manul = queryset.manual_dict[self.service.id]
                index = manul.index
            else:
                manul = ManualIndex.objects.get(service=self.service)
                index = manul.index

        except:
            index = 0

        return index

    def _get_extra_value(self, start_time):
        if not start_time:
            return 0

        now = datetime.datetime.now()
        days = (now - start_time).days
        if days > 7:
            return 0

        else:
            queryset = GetQuerySet()
            return queryset.city_rank.get(self.service.doctor.hospital.city_id, 0)

    @staticmethod
    def get_ctr_index(ctr_value):
        if ctr_value < 0.001:
            value = 0.001
        elif ctr_value >= 0.09:
            value =  0.09
        else:
            value = ctr_value
        return 1000*value

    @staticmethod
    def get_consult_index(consult_value):
        if consult_value < 0.001:
            value = 0.001
        elif consult_value >0.09:
            value = 0.09
        else:
            value = consult_value
        return 1000*value


def service_rerank_v4(service_ids):
    slave_db = settings.SLAVE_DB_NAME
    latest = StatisticServiceSmartRankV4.objects.using(slave_db).aggregate(Max('stat_date'))['stat_date__max']

    rpc_invoker = get_rpc_remote_invoker()
    cpc_map = rpc_invoker['artemis/cpc/promote/status_batch'](
        service_ids=service_ids
    ).unwrap()


    city_pv_map = calc_city_pv_ctr()

    services = Service.objects.using(slave_db).filter(id__in=service_ids)
    city_map = {}
    for service in services:
        city = service.city
        if city is not None:
            city_map[service.id] = city.id

    # here
    ranks = StatisticServiceSmartRankV4.objects.using(slave_db).filter(service_id__in=service_ids,stat_date=latest)
    score_map = {}
    smart_rank_factor = {}
    for rank in ranks:
        # city_id = city_map.get(rank.service_id)
        ctr_value = rank.get_ctr_value()
        # if city_id is not None:
        #     min_pv = city_pv_map.get(city_id)
        #     if rank.service_detail_view_count_30 < min_pv:
        #         ctr_value = 0.01

        consult_value = rank.get_consult_value()

        discount_value = rank.get_discount_value()
        cpt_value = rank.get_cpt_value()
        # consult_value, ctr_value = sqrt(consult_value), sqrt(ctr_value)

        click_data = cpc_map.get(str(rank.service_id),None)
        click_price = 0
        if click_data is not  None:
            is_promote = click_data.get("is_promote", False)
            if is_promote :
                click_price = click_data.get("click_price")

        org_value = discount_value + cpt_value*0.5 + click_price

        score = consult_value * ctr_value * org_value
        score_map[rank.service_id] = score
        smart_rank_factor[rank.service_id] = [discount_value, cpt_value, click_price, consult_value, ctr_value]
        print("SOURCE_V4### {}: {} {} {} {} {} {} {}".format(rank.service_id, consult_value, ctr_value, discount_value, cpt_value, click_price/4.0, org_value,score))

    create_list = []
    update_list = []

    smart_rank_dict = {
        rank.service_id: rank for rank in SmartRank.objects.using(slave_db).filter(service_id__in=service_ids)
    }

    for service_id, score in score_map.items():
        if smart_rank_dict.get(service_id):
            obj = smart_rank_dict[service_id]
            if obj.smart_rank_v4 != score:
                smart_rank_logger.info("[SMART_RANK] update service_id(%s) from (%s) -> (%s)" % (service_id, obj.smart_rank_v4, score))
                obj.smart_rank_v4 = score
                obj.update_time = datetime.datetime.now()
                update_list.append(obj)
        else:
            smart_rank_logger.info('[SMART_RANK] add service_id(%s) with score(%s)' % (service_id, score))
            obj = SmartRank(service_id=service_id, smart_rank_v4=score)
            create_list.append(obj)

    for service_id, factors in smart_rank_factor.items():
        discount_value, cpt_value, click_price, consult_value, ctr_value = factors
        defaults = {
            'discount_value_v4': discount_value,
            'cpt_value': cpt_value,
            'click_price': click_price,
            'consult_value': consult_value,
            'ctr_value': ctr_value
        }

        SmartRankFactor.objects.update_or_create(service_id=service_id, defaults=defaults)

    SmartRank.objects.bulk_create(create_list, batch_size=1000)
    SmartRank.objects.bulk_update(update_list, update_fields=['smart_rank_v4', 'update_time'], batch_size=1000)


def service_rerank(service_ids):
    slave_db = settings.SLAVE_DB_NAME
    latest = StatisticServiceSmartRankV3.objects.using(slave_db).aggregate(Max('stat_date'))['stat_date__max']

    rpc_invoker = get_rpc_remote_invoker()
    cpc_map = rpc_invoker['artemis/cpc/promote/status_batch'](
        service_ids=service_ids
    ).unwrap()

    smart_rank_logger.info('[SMART_RANK] cpc_map(%s)' % (str(cpc_map)))
    city_pv_map = calc_city_pv_ctr()

    services = Service.objects.using(slave_db).filter(id__in=service_ids)
    city_map = {}
    for service in services:
        city = service.city
        if city is not None:
            city_map[service.id] = city.id


    ranks = StatisticServiceSmartRankV3.objects.using(slave_db).filter(service_id__in=service_ids,stat_date=latest)
    score_map = {}
    smart_rank_factor = {}
    for rank in ranks:
        # city_id = city_map.get(rank.service_id)
        ctr_value = rank.get_ctr_value()
        # if city_id is not None:
        #     min_pv = city_pv_map.get(city_id)
        #     if rank.service_detail_view_count_30 < min_pv:
        #         ctr_value = 0.01

        consult_value = rank.get_consult_value()

        discount_value = rank.get_discount_value()
        cpt_value = rank.get_cpt_value()
        # consult_value, ctr_value = sqrt(consult_value), sqrt(ctr_value)

        click_data = cpc_map.get(str(rank.service_id),None)
        click_price = 0
        if click_data is not  None:
            is_promote = click_data.get("is_promote", False)
            if is_promote :
                click_price = click_data.get("click_price")

        org_value = discount_value + cpt_value*0.5 + click_price

        score = consult_value * ctr_value * org_value
        score_map[rank.service_id] = score
        smart_rank_factor[rank.service_id] = [discount_value, cpt_value, click_price, consult_value, ctr_value]
        smart_rank_logger.info(
            "[SMART_RANK_FACTOR] update service_id:%s,discount_value:%s,cpt_value:%s,click_price:%s,consult_value:%s,ctr_value:%s,score:%s" %
            (str(rank.service_id), str(discount_value), str(cpt_value),str(click_price),str(consult_value),str(ctr_value),str(score)))
        print("SOURCE### {}: {} {} {} {} {} {} {}".format(rank.service_id, consult_value, ctr_value, discount_value, cpt_value, click_price/4.0, org_value,score))

    create_list = []
    update_list = []

    # smart_rank_dict = {
    #     rank.service_id: rank for rank in SmartRank.objects.using(slave_db).filter(service_id__in=service_ids)
    # }
    smart_rank_dict = {
        rank.service_id: rank for rank in SmartRank.objects.filter(service_id__in=service_ids)
    }

    for service_id, score in score_map.items():
        if smart_rank_dict.get(service_id):
            obj = smart_rank_dict[service_id]
            if obj.new_smart_rank != score:
                smart_rank_logger.info("[SMART_RANK] update service_id(%s) from (%s) -> (%s)" % (service_id, obj.new_smart_rank, score))
                obj.new_smart_rank = score
                obj.update_time = datetime.datetime.now()
                update_list.append(obj)
        else:
            smart_rank_logger.info('[SMART_RANK] add service_id(%s) with score(%s)' % (service_id, score))
            obj = SmartRank(service_id=service_id, new_smart_rank=score)
            create_list.append(obj)

    for service_id, factors in smart_rank_factor.items():
        discount_value, cpt_value, click_price, consult_value, ctr_value = factors
        defaults = {
            'discount_value': discount_value,
            'cpt_value': cpt_value,
            'click_price': click_price,
            'consult_value': consult_value,
            'ctr_value': ctr_value
        }

        SmartRankFactor.objects.update_or_create(service_id=service_id, defaults=defaults)

    SmartRank.objects.bulk_create(create_list, batch_size=1000)
    SmartRank.objects.bulk_update(update_list, update_fields=['new_smart_rank', 'update_time'], batch_size=1000)

    try:
        service_rerank_v4(service_ids)
    except Exception as e:
        print("[Service Rerank V4] error")

    #todo tapir会出现binlog监听失败的情况，这里针对这种情况加了一层保护
    try:
        write_to_es(es_type="service",pk_list=service_ids,configuration=None)
    except:
        smart_rank_logger.error("[SMART_RANK_EXCEPTION] catch exception,err_msg:%s" % traceback.format_exc())


def get_smart_rank_compare_data(service_ids):
    print("smart_rank_compare service_ids: {0}".format(str(service_ids)))

    sids = set(service_ids)

    es_result = search_service_smart_rank(service_ids)

    es_dict = {}

    for single_hit in es_result['hits']['hits']:
        if '_source' in single_hit:
            service_id = single_hit['_source'].get('id', None)
            is_online = single_hit['_source'].get('is_online', False)

            smart_rank = single_hit['_source'].get('smart_rank', 0)
            smart_rank2 = single_hit['_source'].get('smart_rank2', 0)

            if service_id:
                if is_online:
                    es_dict[str(service_id)] = {"smart_rank": smart_rank, "smart_rank2": smart_rank2}
                else:
                    sids.remove(service_id)

    mysql_data = SmartRank.objects.filter(service_id__in=sids)

    res = []

    for service in mysql_data:
        if service.service_id > 0:
            es_data = es_dict.get(str(service.service_id), {})
            es_smart_rank = es_data.get("smart_rank", 0)
            es_new_smart_rank = es_data.get("smart_rank2", 0)

            d = dict(
                service_id=service.service_id,
                mysql_smart_rank=service.smart_rank,
                mysql_new_smart_rank=service.new_smart_rank,
                es_smart_rank=es_smart_rank,
                es_new_smart_rank=es_new_smart_rank,
                sr_delta=service.smart_rank - es_smart_rank,
                srn_delta=service.new_smart_rank - es_new_smart_rank,
            )

            res.append(d)

    return res


def smart_rank_compare(service_ids, check_time):
    obj_dict = get_smart_rank_compare_data(service_ids)

    create_list = []
    update_list = []

    diff_sids = [od.get("service_id") for od in obj_dict if od.get("sr_delta") != 0 or od.get("srn_delta") != 0]

    for od in obj_dict:
        service_id = od.get("service_id")

        obj = SmartRankCompare.objects.filter(service_id=service_id).first()
        if obj:
            obj.check_time = check_time
            obj.mysql_smart_rank = od["mysql_smart_rank"]
            obj.mysql_new_smart_rank = od["mysql_new_smart_rank"]
            obj.es_smart_rank = od["es_smart_rank"]
            obj.es_new_smart_rank = od["es_new_smart_rank"]
            obj.sr_delta = od["sr_delta"]
            obj.srn_delta = od["srn_delta"]

            update_list.append(obj)
        else:
            obj = SmartRankCompare(
                service_id=service_id,
                check_time=check_time,
                mysql_smart_rank=od["mysql_smart_rank"],
                mysql_new_smart_rank=od["mysql_new_smart_rank"],
                es_smart_rank=od["es_smart_rank"],
                es_new_smart_rank=od["es_new_smart_rank"],
                sr_delta=od["sr_delta"],
                srn_delta=od["srn_delta"]
            )

            create_list.append(obj)

    SmartRankCompare.objects.bulk_create(create_list, batch_size=1000)
    SmartRankCompare.objects.bulk_update(
        update_list,
        update_fields=['check_time', 'mysql_smart_rank', 'mysql_new_smart_rank',
                       'es_smart_rank', 'es_new_smart_rank', 'sr_delta', 'srn_delta'],
        batch_size=1000)

    print("smart_rank_compare finish!! diff_sids:{}".format(str(diff_sids)))
    return diff_sids
