# coding=utf-8

import datetime
import hashlib
import json
import pili
import random
import requests
import time
import os
import ffmpy
from urllib.parse import urljoin

from celery import shared_task
# from celery_once import QueueOnce
from django.conf import settings
from django.db.models import Q
from django.utils import timezone

from gm_upload import (
    set_video_watermark,
    fetch_picture_and_save_to_qiniu,
    video_clipping,
    upload_file,
    video_delete
)
from gm_upload.utils.qiniu_tool import QiniuTool

from gm_types.gaia import VIDEO_CODE_STATUS, QINIU_VIDEO_HANDLE_STATUS_CODE
from gm_types.push import PUSH_INFO_TYPE, AUTOMATED_PUSH
from gm_protocol import GmProtocol

from live.managers import LiveMsgManager
from live.managers import create_stream_replay_danmu
from live.models import LiveStream, LiveChannel
from social.models import SocialInfo
from talos.cache.base import community_video_handle_cache
from talos.cache.gaia import (
    sleep_action_fans_cache,
    sleep_noaction_fans_cache,
)
from talos.cache.live import (
    live_msg_cache,
    live_other_kv_cache,
    live_fake_vote_cache,
)
from talos.libs.datetime_utils import get_timestamp_or_none
from talos.models.topic.video import Video, VideoLibrary
from talos.rpc import logging_exception
from talos.services import UserService
from utils.UploadVideoPicture import UploadVideoPicture
from utils.rpc import get_rpc_invoker
from utils.push import push_task_to_user_multi

ts_now_as_score = lambda: get_timestamp_or_none(timezone.now())


@shared_task
def save_replay_url():
    mac = pili.Mac(settings.QINIU_ACCESS_KEY, settings.QINIU_SECRET_KEY)
    client = pili.Client(mac)
    hub = client.hub(settings.QINIU_HUB_NAME)

    streams = LiveStream.objects.filter(save_replay_url='', status=0)
    for stream in streams:
        timedetla = datetime.datetime.now() - stream.updated_time
        if timedetla.seconds < settings.LIVE_TIME_OUT:
            continue
        try:
            s = hub.get(stream.stream_key)
            url = s.save_as(0, ts_now_as_score())
            stream.save_replay_url = url['fname']
            stream.save(update_fields=['save_replay_url'])
            stream.create_topic()
            stream.clear_redis_info()
            create_stream_replay_danmu(stream.id)
        except:
            logging_exception()


@shared_task
def check_live_status():
    mac = pili.Mac(settings.QINIU_ACCESS_KEY, settings.QINIU_SECRET_KEY)
    client = pili.Client(mac)
    hub = client.hub(settings.QINIU_HUB_NAME)

    streams = LiveStream.objects.filter(status=True)
    for stream in streams:
        try:
            # qiniu
            s = hub.get(stream.stream_key.encode())
            s.status()
        except pili.errors.APIError:
            stream.set_status(False)
        except:
            logging_exception()

    ss = hub.list(liveonly=True)
    for i in ss["items"]:
        key = i.key
        stream = LiveStream.objects.filter(stream_key=key, status=False).first()
        if stream:
            stream.set_status(True)


@shared_task
def upload_video_picture_to_qiniu(id):
    video = Video.objects.get(id=id)
    video_pic = video.get_video_info()['video_pic']
    video_pic = fetch_picture_and_save_to_qiniu(video_pic)
    video.video_pic = video_pic
    video.save(update_fields=['video_pic'])


@shared_task
def upload_video_picture_to_qiniu_v2(id):
    """
    upload_video_picture_to_qiniu传入的是一个图片，upload_video_picture_to_qiniu_v2传入的是视频
    :param id:
    :return:
    """
    video = Video.objects.get(id=id)
    video_url = video.video_url
    video.video_pic = UploadVideoPicture(video_url)
    video.save(update_fields=['video_pic'])


@shared_task
def set_water_mark_to_video(id):
    video = Video.objects.get(id=id)
    video.persistent_status = VIDEO_CODE_STATUS.WAITING
    url = video.video_url
    if not isinstance(url, bytes):
        url = url.encode('utf-8')
    pid = set_video_watermark(
        video.video_url,
        hashlib.md5(url).hexdigest() + '.mp4',
        water_mark_url=settings.WATER_MARK_URL_FOR_VIDEO
    )
    video.persistentId = pid
    video.save(update_fields=['persistentId', 'persistent_status'])


@shared_task
def check_water_mark_video_is_finish():
    videos = Video.objects.filter(persistent_status=VIDEO_CODE_STATUS.WAITING)
    for video in videos:
        try:
            if video.persistentId:
                result = json.loads(requests.get('http://api.qiniu.com/status/get/prefop?id=' +
                                                 video.persistentId).text)
                # 状态码0成功，1等待处理，2正在处理，3处理失败，4通知提交失败。
                code = result['code']
                if code not in (0, 3):
                    continue
                elif code == 3:
                    video.persistent_status = VIDEO_CODE_STATUS.FAIL
                    video.save(update_fields=['persistent_status'])
                else:
                    if result['items'][0]['code'] == 0:
                        water_url = result['items'][0]['key']

                        # 刷新cdn缓存
                        QiniuTool.refresh_qiniu_resource_cache([urljoin(settings.VIDEO_HOST, water_url)])

                        video.water_url = water_url
                        video.persistent_status = VIDEO_CODE_STATUS.SUCCESS
                        video.save(update_fields=['persistent_status', 'water_url'])
                    else:
                        video.persistent_status = result['items'][0]['code']
                        video.save(update_fields=['persistent_status'])

        except:
            logging_exception()


@shared_task
def set_stream_to_redis():
    channels = LiveChannel.objects.all()
    for channel in channels:
        stream = channel.now_stream
        if stream:
            live_msg_cache.set('channel' + str(stream.channel.id), json.dumps(stream.data_for_redis()))


@shared_task
def add_test_redis_msg(channel_id, count, msg=None):
    if not msg:
        msg = {
            'id': 0,  # 用来占位
            'text': 'hellow',
            'name': '1111111',
            'type': '2',
            'user_id': 0
        }
    stream = LiveChannel.objects.get(id=channel_id).now_stream
    for i in range(0, count):
        key = LiveMsgManager.get_live_redis_key(stream.id)
        msg['text'] = str(i)
        live_msg_cache.rpush(key, json.dumps(msg))


@shared_task
def add_test_redis_like_msg(channel_id, count):
    msg = {
        'id': 0,  # 用来占位
        'text': 'live_like',
        'name': 'live_like',
        'type': '3',
        'user_id': 0
    }
    add_test_redis_msg(channel_id, count, msg)
    stream = LiveChannel.objects.get(id=channel_id).now_stream
    for i in range(0, count):
        key = LiveMsgManager.get_live_like_redis_key(stream.id)
        timestamp = int(time.time()) - count + i
        w_str = 'live_like' + str(i)
        live_msg_cache.zadd(key, timestamp, w_str)


@shared_task
def calc_sleep_user_for_prepare(stream_id):
    fake_fan_manager = FakeFansManager(stream_id)
    k = random.uniform(0.3, 0.5)
    fake_fan_manager.init_random_num(k)

    max_num = int(1129 * k)
    t1_max_num = int(max_num / 3)
    t2_max_num = t1_max_num * 2

    action_user = fake_fan_manager.get_sleep_action_fans()
    no_action_user = fake_fan_manager.get_sleep_noaction_fans()
    random.shuffle(action_user)
    random.shuffle(no_action_user)

    # init all data
    sleep_user = action_user[0:t1_max_num] + no_action_user[0:t2_max_num]
    random.shuffle(sleep_user)

    fake_fan_manager.init_live_enter_person_num()
    fake_fan_manager.set_live_action_user(action_user[t1_max_num:t1_max_num + 5])
    fake_fan_manager.set_live_mix_action_user(sleep_user)

    calc_add_sleep_user_fans.apply_async(
        args=(stream_id,),
        countdown=2 * 60
    )


@shared_task
def calc_add_sleep_user_fans(stream_id):
    fake_fan_manager = FakeFansManager(stream_id)
    stream = LiveStream.objects.get(id=stream_id)
    if stream.live_status_stream:
        # 每隔2min 添加人数
        num = fake_fan_manager.get_live_enter_person_num()
        k = fake_fan_manager.get_random_num()
        add_num = int(num * k)
        fan_ids = fake_fan_manager.get_live_mix_action_user(add_num)

        calc_add_sleep_user_fans.apply_async(
            args=(stream_id,),
            countdown=2 * 60
        )

        add_sleep_user_fans.apply_async(
            args=(stream_id, fan_ids),
            countdown=5 * 60
        )
    else:
        # 结束
        fan_ids = fake_fan_manager.get_live_action_user(5)
        add_sleep_user_fans(stream_id, fan_ids)


@shared_task
def add_sleep_user_fans(stream_id, ids):
    stream = LiveStream.objects.get(id=stream_id)
    user = UserService.get_user_by_person_id(person_id=stream.channel.person_id)
    social = SocialInfo(user.id)
    for id in ids:
        social.add_fans(id)


class FakeFansManager(object):
    def __init__(self, stream_id):
        self.sleep_action_fans_cache = sleep_action_fans_cache
        self.live_other_kv_cache = live_other_kv_cache
        self.sleep_noaction_fans_cache = sleep_noaction_fans_cache
        self.stream_id = stream_id

    @staticmethod
    def _get_live_add_person_random_key(stream_id):
        return str(stream_id) + ':k'

    @staticmethod
    def _get_live_enter_person_key(stream_id):
        return str(stream_id) + ':person_num'

    @staticmethod
    def add_live_enter_person_num_for_sleep_user(stream_id):
        live_other_kv_cache.incr(FakeFansManager._get_live_enter_person_key(stream_id), 1)

    @staticmethod
    def _get_live_sleep_user_key(stream_id):
        return str(stream_id) + ':noaction'

    @staticmethod
    def _get_live_action_user_key(stream_id):
        return str(stream_id) + ':action'

    def init_live_enter_person_num(self):
        self.live_other_kv_cache.setex(self._get_live_enter_person_key(self.stream_id), 12 * 3600, 0)

    def get_live_enter_person_num(self):
        num = int(self.live_other_kv_cache.get(self._get_live_enter_person_key(self.stream_id)))
        self.init_live_enter_person_num()
        return num

    def set_live_action_user(self, action_user):
        self.live_other_kv_cache.lpush(
            self._get_live_action_user_key(
                self.stream_id), *action_user)

    def get_live_action_user(self, num):
        users = []
        for i in range(0, num):
            users.append(self.live_other_kv_cache.lpop(
                self._get_live_action_user_key(self.stream_id)))
        return users

    def set_live_mix_action_user(self, noaction_user):
        self.live_other_kv_cache.lpush(
            self._get_live_sleep_user_key(self.stream_id), *noaction_user)

    def get_live_mix_action_user(self, num):
        users = []
        for i in range(0, num):
            users.append(self.live_other_kv_cache.lpop(
                self._get_live_sleep_user_key(
                    self.stream_id)))
        return users

    def init_random_num(self, k):
        self.live_other_kv_cache.setex(
            self._get_live_add_person_random_key(self.stream_id), 12 * 3600, k)

    def get_random_num(self):
        return float(self.live_other_kv_cache.get(
            self._get_live_add_person_random_key(self.stream_id)))

    def set_all_sleep_user(self, action_user, sleep_list):
        self.sleep_action_fans_cache.set('sleep_user', json.dumps(action_user))
        self.sleep_noaction_fans_cache.set('sleep_user', json.dumps(sleep_list))

    def get_sleep_action_fans(self):
        return json.loads(self.sleep_action_fans_cache.get('sleep_user'))

    def get_sleep_noaction_fans(self):
        return json.loads(self.sleep_noaction_fans_cache.get('sleep_user'))


def add_fake_vote_num(stream_id):
    stream_id = str(stream_id)
    num = live_fake_vote_cache.get(stream_id)
    if num is None:
        num = 0
    live_fake_vote_cache.setex(stream_id, 60 * 10, int(num) + int(random.randint(30, 50)))


@shared_task
def add_fake_vote_num_list():
    now = datetime.datetime.now() - datetime.timedelta(days=1)

    streams = LiveStream.objects.filter(status=True, created_time__gte=now)
    for ss in streams:
        add_fake_vote_num(ss.id)


@shared_task
def get_qiniu_persistent_ids(source_id, video_type, url_list):
    """
    获取 视频资源处理，七牛返回的 persistent_id
    :param source_id: 数据来源id
    :param video_type: 数据来源类型
    :param url_list: 待处理的视频列表
    :return:
    """
    def cleaned_video_url(video_url):
        return video_url.replace(settings.VIDEO_HOST, '')

    def get_new_video_name(url):
        if not isinstance(url, bytes):
            url = url.encode('utf-8')
        return hashlib.md5(url).hexdigest() + '.mp4'

    for _video_url in url_list:
        video_url = cleaned_video_url(_video_url)
        new_name = get_new_video_name(video_url)

        pid = set_video_watermark(
            video_url,
            new_name,
            water_mark_url=settings.WATER_MARK_URL_FOR_VIDEO
        )
        video_obj, status = VideoLibrary.objects.get_or_create(**{
            "source_id": source_id,
            "video_type": video_type,
            "raw_video_url": video_url,
        })
        video_obj.persistent_id = pid
        video_obj.persistent_status = VIDEO_CODE_STATUS.WAITING
        video_obj.save(update_fields=['persistent_id', 'persistent_status'])

    v = "{_id}:{_type}".format(_id=source_id, _type=video_type)
    community_video_handle_cache.sadd("video_handle_cache", v)  # 把要处理的字段缓存，用于异步处理


@shared_task
def check_community_video_water_mark_url_is_finish():
    videos = VideoLibrary.objects.filter(persistent_status=VIDEO_CODE_STATUS.WAITING)
    for video in videos:
        try:
            if video.persistent_id:
                result = json.loads(requests.get('http://api.qiniu.com/status/get/prefop?id=' +
                                                 video.persistent_id).text)
                code = result['code']
                if code != QINIU_VIDEO_HANDLE_STATUS_CODE.SUCCESS:
                    continue
                else:
                    if result['items'][0]['code'] == QINIU_VIDEO_HANDLE_STATUS_CODE.SUCCESS:
                        water_video_url = result['items'][0]['key']
                        # 刷新cdn缓存
                        QiniuTool.refresh_qiniu_resource_cache([urljoin(settings.VIDEO_HOST, water_video_url)])

                        video.water_video_url = water_video_url
                        video.persistent_status = VIDEO_CODE_STATUS.SUCCESS
                        video.save(update_fields=['persistent_status', 'water_video_url'])
                    else:
                        continue
        except:
            logging_exception()


@shared_task
def video_fsm_runner():
    """
    状态机
    考虑封装成类，根据状态给对应worker添加任务
    :return:
    """

    # 截取视频前两秒
    clipping_id_list = list(Video.objects.filter(
        persistent_clip_status=VIDEO_CODE_STATUS.NOSTART
    ).values_list("id", flat=True))
    for video_id in clipping_id_list:
        set_clipping_to_video.delay(video_id)

    # 检查七牛云截取状态
    check_id_list = list(Video.objects.filter(
        persistent_clip_status=VIDEO_CODE_STATUS.WAITING
    ).values_list("id", flat=True))
    for video_id in check_id_list:
        check_video_clipping_is_finish.delay(video_id)

    # 将七牛云截取成功的 mp4 转换为 webP 动图并上传
    set_id_list = list(Video.objects.filter(
        persistent_clip_status=VIDEO_CODE_STATUS.OPERATING_LOCAL
    ).values_list("id", flat=True))
    for video_id in set_id_list:
        set_video_webp_pic.delay(video_id)


@shared_task
def set_clipping_to_video(video_id):
    video = Video.objects.get(id=video_id)

    if video.persistent_clip_status != VIDEO_CODE_STATUS.NOSTART:
        return

    hash_key = hashlib.md5(str(time.time()).encode("utf8")).hexdigest()
    pid = video_clipping(
        video.video_url,
        new_filename='{}_clip.mp4'.format(hash_key),
        video_type='mp4',
        water_mark_url=None  # settings.WATER_MARK_URL_FOR_VIDEO
    )

    video.persistent_clip_status = VIDEO_CODE_STATUS.WAITING
    video.persistent_clip_id = pid
    video.save(update_fields=['persistent_clip_id', 'persistent_clip_status'])


@shared_task
def check_video_clipping_is_finish(video_id):
    try:
        video = Video.objects.get(id=video_id)

        if video.persistent_clip_status != VIDEO_CODE_STATUS.WAITING:
            return

        if video.persistent_clip_id:
            result = json.loads(requests.get(
                settings.QINIU_VIDEO_INQUIRE_HOST + video.persistent_clip_id
            ).text)
            # 状态码0成功，1等待处理，2正在处理，3处理失败，4通知提交失败。
            code = result['code']
            if code not in (QINIU_VIDEO_HANDLE_STATUS_CODE.SUCCESS,
                            QINIU_VIDEO_HANDLE_STATUS_CODE.PROCESSING_FAIL):
                return
            elif code == QINIU_VIDEO_HANDLE_STATUS_CODE.PROCESSING_FAIL:
                video.persistent_clip_status = VIDEO_CODE_STATUS.FAIL
                video.save(update_fields=['persistent_clip_status'])
            else:
                if result['items'][0]['code'] == QINIU_VIDEO_HANDLE_STATUS_CODE.SUCCESS:
                    mp4_key = result['items'][0]['key']

                    # 刷新cdn缓存
                    QiniuTool.refresh_qiniu_resource_cache([
                        urljoin(settings.VIDEO_HOST, mp4_key)
                    ])

                    video.intercept_video_url = mp4_key
                    video.persistent_clip_status = VIDEO_CODE_STATUS.OPERATING_LOCAL
                    video.save(update_fields=['persistent_clip_status',
                                              'intercept_video_url'])

                else:
                    video.persistent_status = result['items'][0]['code']
                    video.save(update_fields=['persistent_clip_status'])

    except:
        logging_exception()


@shared_task
def set_video_webp_pic(video_id):
    video = Video.objects.get(id=video_id)
    if video.persistent_clip_status != VIDEO_CODE_STATUS.OPERATING_LOCAL:
        return

    mp4_key = video.intercept_video_url
    hash_key = hashlib.md5(str(time.time()).encode("utf8")).hexdigest()
    input_path = os.path.join(settings.VIDEO_CONVERT_PATH, "{}.mp4".format(hash_key))
    output_path = os.path.join(settings.VIDEO_CONVERT_PATH, '{}.webp'.format(hash_key))

    try:
        clipping_video_url = settings.VIDEO_HOST + mp4_key
        res = requests.get(clipping_video_url)

        # 文件存储到本地
        with open(input_path, 'wb') as f:
            f.write(res.content)

        # 使用底层 FFmpeg 库进行转码
        ff = ffmpy.FFmpeg(
            inputs={input_path: None},
            outputs={output_path: settings.VIDEO_FF_ARGS}
        )
        ff.run()

        # 上传webP图片
        video.webp_url = upload_file(output_path)
        video.persistent_clip_status = VIDEO_CODE_STATUS.SUCCESS
        video.save(update_fields=['webp_url', 'persistent_clip_status'])

        # 删除中间数据　
        video_delete(mp4_key)

        # 刷新缓存
        QiniuTool.refresh_qiniu_resource_cache(
            [urljoin(settings.VIDEO_HOST, mp4_key)]
        )

    except:
        logging_exception()
        video.persistent_clip_status = VIDEO_CODE_STATUS.FAIL
        video.save(update_fields=['persistent_clip_status'])

    finally:
        if os.path.exists(input_path):
            os.remove(input_path)
        if os.path.exists(output_path):
            os.remove(output_path)


@shared_task
def push_follow_user(user_id, channel_id, user_nick_name=""):

    push_msg = "你关注的{nick_name}正在直播，速来围观>>>".format(nick_name=user_nick_name)
    try:
        user_ids = get_rpc_invoker()['api/user/get_all_fans'](user_id=user_id).unwrap()
    except Exception:
        logging_exception()
        return

    push_url = GmProtocol().get_live_list(channel_id)
    p_type = AUTOMATED_PUSH.FOLLOWED_USER_POSTED_LIVE
    kwargs = {
        "user_ids": user_ids,
        "platform": ['android', 'iPhone'],
        "alert": push_msg,
        "extra": {
            'type': PUSH_INFO_TYPE.GM_PROTOCOL,
            'msgType': 4,
            'pushUrl': push_url,
            'push_url': push_url,
        },
        "push_type": p_type,
    }
    push_task_to_user_multi(**kwargs)
