# -*- coding: utf-8 -*-
import json
import hashlib
import datetime
import gevent
from gevent.pool import Pool
from django.conf import settings
from django.db.models import F, Max
from hera.cache.app_config import APPConfigCache
from mark.models.mark_log import UserMarkLog
from api.models import Notification
from statistic.models import Device
from rpc.cache import gadget_cache
from gm_types.push import AUTOMATED_PUSH
from distutils.version import LooseVersion
from gm_types.push import PUSH_INFO_TYPE
from mark.models import DeviceMiddleId, MarkGrayDevice, MarkMiddleDevice, MarkPushLog
from rpc.tool.log_tool import info_logger
from rpc.tool.protocol import gm_protocol


class MarkPush(object):
    """
    device表中的灰度用户
    """
    # FIRLST_MARK_PUSH_MSG = u'【运势测试】想了解今日运程如何？戳这里！快来测测吧'
    # PSUH_AGAIN_MSG = u'今日份【专属运势】请查收！了解这些宜忌事项，财运、桃花想不好都难'

    # update v 7.26.1 date 2020.05.09
    # wiki:  http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=36554535
    FIRLST_MARK_PUSH_MSG = u'我今天的肤质和运势怎么样？戳这里！快来测测吧'
    PSUH_AGAIN_MSG = u'【专属肤质&运势】请查收！了解肤质状况和宜忌事项～财运、桃花想不好都难'
    PUSH_TITLE = u'打卡提醒'

    def __init__(self, target_version='7.26', base=10, relation_num=[0, ]):
        self.target_version = target_version
        self.base = base
        self.relation_num = relation_num
        self.step = 200
        self.push_url = gm_protocol.get_group_detail(group_id=4)  # self.mark_activity_config().get('url', '')
        self.notice_list_url = gm_protocol.get_group_detail(group_id=4)  # gm_protocol.get_notice_list()

    def mark_activity_config(self, config_key='index_publish'):
        """
        打卡活动配置
        :param config_key:
        :return:
        """
        config = APPConfigCache(gadget_cache, config_key).get_config_from_cache()
        config = json.loads(config or "{}") or {}
        return config

    def update_mark_push_log_by_ids(self, ids):
        MarkPushLog.objects.filter(id__in=ids, is_effective=True).update(
            continuous_push_num=0,
            reset_num=F('reset_num') + 1,
        )

    def reset_continuous_push_num(self):
        obj = MarkPushLog.objects.last()
        last_id = obj.id if obj else 0

        p = Pool(5)
        jobs = []
        for i in range(1, last_id + 1, self.step):
            jobs.append(p.spawn(self.update_mark_push_log_by_ids, ids=[id for id in range(i, i + self.step)]))

        gevent.joinall(jobs)

    def get_last_device_id(self):
        last_obj = MarkGrayDevice.objects.using(settings.SLAVE_DB_NAME).last()
        last_id = last_obj.id if last_obj else 0
        return last_id

    def filter_marked_user(self, user_ids):
        """
        筛选打过卡的用户
        :param user_ids:
        :return:
        """
        user_ids = set(UserMarkLog.objects.using(settings.SLAVE_DB_NAME).filter(user_id__in=user_ids). \
                       values_list('user_id', flat=True))

        return user_ids

    def filter_push_state(self, user_ids):
        """
        获取未超连续推送次数的用户
        :return:
        """
        _user_ids = set(MarkPushLog.objects.using(settings.SLAVE_DB_NAME).filter(
            user_id__in=user_ids,
            is_effective=False,
        ).values_list('user_id', flat=True))

        return user_ids - _user_ids

    def filter_user_ids(self, user_ids):
        marked_use_ids = self.filter_marked_user(user_ids)
        user_ids = user_ids - marked_use_ids
        user_ids = self.filter_push_state(user_ids)

        return user_ids


    def get_user_ids_by_table_device_id(self, t_device_ids):
        user_ids = set(Device.objects.using(settings.SLAVE_DB_NAME).filter(
            id__in=t_device_ids
        ).values_list('user__id', flat=True))
        return user_ids

    def get_table_device_id(self, ids):
        ids = list(MarkGrayDevice.objects.using(settings.SLAVE_DB_NAME).
                   filter(id__in=ids).values_list("t_device_id", flat=True))
        return ids

    def get_user_ids_by_table_gray_device_ids(self, ids):
        # 获取命中灰度且版本符合要求的设备表id
        t_device_ids = self.get_table_device_id(ids)
        # 获取登录过灰度设备的所有用户id
        user_ids = self.get_user_ids_by_table_device_id(t_device_ids)
        user_ids = self.filter_user_ids(user_ids)
        return user_ids

    def get_user_ids(self):
        last_id = self.get_last_device_id()

        p = Pool(500)
        jobs = []
        for i in range(1, last_id + 1, self.step):
            jobs.append(p.spawn(self.get_user_ids_by_table_gray_device_ids, ids=[id for id in range(i, i + self.step)]))

        gevent.joinall(jobs)

        user_ids = set()
        for job in jobs:
            user_ids |= job.value

        return user_ids

    def save_notication(self, user_ids, push_msg, push_url, push_title=''):
        Notification.objects.bulk_create([
            Notification(
                title=push_title,
                user_id=user_id,
                content=push_msg,
                url=push_url,
                is_viewed=False,
            ) for user_id in user_ids
        ])

    def do_mark_push(self, user_ids):
        from mark.tasks import do_push_task, update_push_log
        extra = {
            'type': PUSH_INFO_TYPE.GM_PROTOCOL,
            'push_url': self.notice_list_url,
            'pushUrl': self.notice_list_url,
        }

        push_param = {
            "push_type": AUTOMATED_PUSH.ACTIVITY,
            # "push_type": AUTOMATED_PUSH.FIRST_MARK,
            "alert": self.FIRLST_MARK_PUSH_MSG,
            "extra": extra,
        }
        notication = {
            "push_msg": self.FIRLST_MARK_PUSH_MSG,
            "push_url": self.push_url,
            "push_title": self.PUSH_TITLE,
        }

        p = Pool(5)
        step = 128 * 50
        jobs = []

        for i in range(0, len(user_ids), step):
            ids = user_ids[i: i + step]
            push_param['user_ids'] = ids
            notication['user_ids'] = ids

            jobs.append(p.spawn(self.save_notication, **notication))
            do_push_task.delay(**push_param)
            update_push_log.delay(ids)

        gevent.joinall(jobs)

    def mark_push(self):
        # 获取活动在线状态
        is_online = self.mark_activity_config().get("is_online")

        if not is_online:
            # 重置用户连续打卡次数
            self.reset_continuous_push_num()
            info_logger.info("打卡活动不在线：{date_time}".format(date_time=datetime.datetime.now()))
            return

        info_logger.info("打卡活动开始获取user_ids：{date_time}".format(date_time=datetime.datetime.now()))

        # 通过灰度设备id 获取登录过灰度设备的所有用户，且未打过卡，且推送次数小于5，
        user_ids = self.get_user_ids()

        info_logger.info("打卡活动开始发送push：{date_time}".format(date_time=datetime.datetime.now()))

        self.do_mark_push([user_id for user_id in user_ids if user_id])

    def get_today_zero_time(self):
        now = datetime.datetime.now()
        zero_time = now - datetime.timedelta(
            hours=now.hour,
            minutes=now.minute,
            seconds=now.second,
            microseconds=now.microsecond,
        )
        return zero_time

    def get_date_limit(self, n):
        zero_time = self.get_today_zero_time()
        date_limit = zero_time - datetime.timedelta(days=n)
        return date_limit

    def get_user_last_mark_log(self, date_limit):
        # 每个用户最后的打卡记录id
        ids = list(UserMarkLog.objects.using(settings.SLAVE_DB_NAME).filter(mark_date__gt=date_limit).
                   values('user_id').annotate(last_id=Max('id')).values_list('last_id', flat=True))

        user_last_mark_log = UserMarkLog.objects.only('id', 'user_id', 'create_time').filter(id__in=ids)

        return user_last_mark_log

    def get_mark_again_user_ids(self, user_last_mark_log):
        """
        如果打卡时间小于今天零点，且大于5天前零点，则发送通知
        :param user_last_mark_log:
        :return:
        """
        mark_date_max = self.get_today_zero_time()

        user_ids = list()
        for mark_log in user_last_mark_log:
            if mark_log.create_time < mark_date_max:
                user_ids.append(mark_log.user_id)

        return user_ids

    def send_mark_again_push(self, user_ids):
        from mark.tasks import do_push_task
        extra = {
            'type': PUSH_INFO_TYPE.GM_PROTOCOL,
            'push_url': self.notice_list_url,
            'pushUrl': self.notice_list_url,
        }
        alert = self.PSUH_AGAIN_MSG

        push_param = {
            # "push_type": AUTOMATED_PUSH.MARK_AGIAN,
            "push_type": AUTOMATED_PUSH.ACTIVITY,
            "alert": alert,
            "extra": extra,
            "first_push": False,
        }

        notication = {
            "push_msg": alert,
            "push_url": self.push_url,
            "push_title": self.PUSH_TITLE,
        }

        p = Pool(5)
        step = 128
        jobs = []
        for i in range(0, len(user_ids), step):
            ids = user_ids[i: i + step]
            push_param['user_ids'] = ids
            notication['user_ids'] = ids

            jobs.append(p.spawn(self.save_notication, **notication))
            do_push_task.delay(**push_param)

        gevent.joinall(jobs)

    def mark_push_again(self):
        # 获取活动在线状态
        is_online = self.mark_activity_config().get("is_online")
        if not is_online:
            info_logger.info("打卡活动不在线：{date_time}".format(date_time=datetime.datetime.now()))
            return

        # 获取最近6天，每个用户的最后一条打卡记录，如果打卡时间小于今天零点，则发送再次参与打卡的通知
        user_last_mark_log = self.get_user_last_mark_log(self.get_date_limit(5))

        user_ids = self.get_mark_again_user_ids(user_last_mark_log)

        self.send_mark_again_push(user_ids)


class GrayDevicePool(object):

    def __init__(self, target_version='7.26', base=10, relation_num=[0, ]):
        self.step = 10000
        self.target_version = target_version
        self.base = base
        self.relation_num = relation_num

    def get_device_middle_id(self):
        obj = DeviceMiddleId.objects.using(settings.SLAVE_DB_NAME).first()
        middle_id = obj.middle_id if obj and obj.middle_id else 0
        return middle_id

    def update_device_middle_id(self, middle_id):
        obj = DeviceMiddleId.objects.first()
        if obj:
            obj.middle_id = middle_id
            obj.save()
        else:
            DeviceMiddleId.objects.create(middle_id=middle_id)

    def get_device_last_id(self):
        obj = Device.objects.using(settings.SLAVE_DB_NAME).last()
        last_id = obj.id if obj else 0
        return last_id

    def get_devices(self, ids, value_keys=['id', 'device_id', 'version']):
        devices = Device.objects.using(settings.SLAVE_DB_NAME).filter(id__in=ids).values(*value_keys)
        return devices

    def hit_gray(self, s):
        if s in settings.LAUNCH_GRAY_RULE:
            return True
        else:
            if int(hashlib.sha1(s).hexdigest(), 16) % (self.base + 1) in self.relation_num:
                return True
        return False

    def filter_devices(self, devices, verify_gray=True):
        ids = []
        low_version_ids = []
        for device in devices:
            version = device['version']
            if not version:
                continue

            if verify_gray and not self.hit_gray(device['device_id']):
                continue

            if LooseVersion(version) >= LooseVersion(self.target_version):
                ids.append(device['id'])
            else:
                low_version_ids.append(device['id'])

        return ids, low_version_ids

    def save_ids(self, model, ids):
        save_list = []
        for id in ids:
            save_list.append(model(t_device_id=id))

        step = 5000
        for i in range(0, len(ids), step):
            model.objects.bulk_create(save_list[i: i + step])

    def deal_devices_by_ids(self, table_device_ids):
        devices = self.get_devices(table_device_ids)
        device_table_ids, low_version_ids = self.filter_devices(devices)
        self.save_ids(MarkGrayDevice, device_table_ids)
        self.save_ids(MarkMiddleDevice, low_version_ids)

    def deal_devices(self):
        start_id = self.get_device_middle_id()
        end_id = self.get_device_last_id()

        p = Pool(5)
        jobs = []
        for i in range(start_id, end_id + 1, self.step):
            jobs.append(p.spawn(self.deal_devices_by_ids, table_device_ids=[id for id in range(i, i + self.step)]))
        gevent.joinall(jobs)

        self.update_device_middle_id(end_id)

    def get_middle_device_last_id(self):
        obj = MarkMiddleDevice.objects.using(settings.SLAVE_DB_NAME).last()
        if obj:
            last_id = obj.id
        else:
            last_id = 0
        return last_id

    def get_device_id_from_middle_devices(self, ids):
        return list(MarkMiddleDevice.objects.using(settings.SLAVE_DB_NAME).
                    filter(id__in=ids).values_list('t_device_id', flat=True))

    def delete_middle_devices(self, device_table_ids):
        if device_table_ids:
            MarkMiddleDevice.objects.filter(t_device_id__in=device_table_ids).delete()

    def deal_low_version_devices_by_ids(self, ids):
        ids = self.get_device_id_from_middle_devices(ids)
        devices = self.get_devices(ids, value_keys=['id', 'version'])
        device_table_ids, _ = self.filter_devices(devices, verify_gray=False)
        self.delete_middle_devices(device_table_ids)
        self.save_ids(MarkGrayDevice, device_table_ids)

    def deal_low_version_gray_devices(self):
        last_id = self.get_middle_device_last_id()

        jobs = []
        p = Pool(5)
        for i in range(0, last_id + 1, self.step):
            jobs.append(p.spawn(self.deal_low_version_devices_by_ids, ids=[id for id in range(i, i + self.step)]))
        gevent.joinall(jobs)
