#! /usr/bin/env python3
# -*- coding:utf-8 -*-

import subprocess
import json
import math
import logging
import datetime
from django.core.paginator import Paginator
from apollo.settings import url_prefix
from domain.dataset import DataSet
from injector.models import MediaStorageModel
from perm.service.user_service import UserService
from apollo.settings import DEBUG
from utils.queue_producer import enqueue, query, query_many
from apollo.settings import DELETE_MEDIA_VIDEO
from domain.injector.value_object import StoragePlatformValue
import hashlib
from django.core.cache import cache
import requests
import re
from utils.url_utils import generate_signed_url, parse_secret_path, generate_signed_text, compose_path, \
    handle_signed_url
import time

logger = logging.getLogger(__file__)


class MediaStorage(DataSet):
    model = MediaStorageModel

    @classmethod
    def add(cls, data, uid):
        """
        上传文件信息，并关联媒体
        :param data: {"url": "视频url", "name": "文件名称", "media_id": "媒体id", "content_type": "内容类型", "pro_type": "版本", "definition": "清晰度", "episode_id": "分集id"}
        :param uid:
        :return:
        """
        last_md5 = cache.get("last_md5", None)
        # md5 = data["md5"]
        md5 = None
        if not last_md5 or last_md5 != md5:
            storage_info = cls.parse_data_hls(data, uid)
            cls.add_storage(storage_info, data)
            cache.set("last_md5", md5, 1800)
        # if m3u8_url:
        #     storage_info["url"] = m3u8_url
        #     storage_info["name"] = m3u8_name
        #     storage_info["type_code"] = StoragePlatformValue.M3U8
        #     cls.add_storage(storage_info, data)
        return

    @classmethod
    def update_hls(cls, storage_id):
        storage = MediaStorageModel.objects.filter(id=storage_id).first()
        return True, "success"
        # if storage:
        #     url = storage.url
        #  keys    md5 = storage.md5
        #     url_m3u8 = "{}/{}/{}".format("/".join(url.split("/")[:-1]), md5, "playlist.m3u8")
        #     storage.hls_url = url_m3u8
        #     storage.type_code = StoragePlatformValue.M3U8
        #     storage.save()
        #     return True, "success"
        # return False, "介质ID错误"

    @classmethod
    def replace(cls, data, uid):
        storage_info = cls.parse_data(data, uid)

        # 查找对应的分集下的介质 找到 同平台最新介质
        storage = cls.find_storage_by_episode_and_tag(data["episode_id"], data["type_code"])
        if storage:
            # 更新
            print("存在，更新")
            storage_info["content_type"] = data["content_type"]
            storage_info["definition"] = data["definition"]
            storage_info["pro_type"] = data["pro_type"]
            storage_info["update_time"] = datetime.datetime.now()
            cls.model.objects.filter(pk=storage.id).update(**storage_info)
            # 删除原视频
            cls.delete_video_only([storage.id])
        else:
            # 不存在
            print("不存在，添加")
            cls.add_storage(storage_info, data)

    @classmethod
    def edit(cls, storage_id, update_info):
        cls.model.objects.filter(pk=storage_id).update(**update_info)
        return

    @classmethod
    def delete(cls, storage_id):
        cls.model.objects.filter(pk=storage_id).update(is_delete=True)
        return

    @classmethod
    def deletes(cls, storage_ids):
        cls.model.objects.filter(id__in=storage_ids).update(is_delete=True)
        return

    @classmethod
    def add_storage(cls, storage_info, data):
        storage = cls.model.objects.create(**storage_info)
        if data["definition"] and str(data["definition"]) in ("8", "9"):
            definition = 10
        else:
            definition = data["definition"]
        cls.related_media(storage.id, data["media_id"], data["episode_id"], data["content_type"], definition,
                          data["pro_type"])
        return storage

    @classmethod
    def parse_data_hls(cls, data, uid):
        url = data["url"]
        url_m3u8 = ""
        if DEBUG:
            url = '{}/test{}'.format('http://192.168.1.141:80', url.split("test")[-1])
        else:
            url = '{}/matrix{}'.format(url_prefix, url.split("matrix")[-1])
        is_hls = data.get("is_hls", False)
        md5 = data["md5"]
        if is_hls:
            url_m3u8 = "{}/{}/{}".format("/".join(url.split("/")[:-1]), md5, "playlist.m3u8")
        storage_info = {}

        name = data["name"]
        if not name:
            name = url.split("/")[-1]

        storage_info.update({
            "name": name,
            "url": url,
            "md5": data["md5"],
            "create_user": uid,
            "v_codec": data.get("v_codec"),
            "v_profile": data.get("v_profile"),
            "width": data.get("width"),
            "height": data.get("height"),
            "v_bit_rate": data.get("v_bit_rate"),
            "sample_aspect_ratio": data.get("sample_aspect_ratio"),
            "display_aspect_ratio": data.get("display_aspect_ratio"),
            "seconds": data.get("seconds"),
            "a_codec": data.get("a_codec"),
            "a_channels": data.get("a_channels"),
            "a_bit_rate": data.get("a_bit_rate"),
            "a_sample_rate": data.get("a_sample_rate"),
            "bit_rate": data.get("bit_rate"),
            "size": data.get("size"),
            "dub": data.get("dub", "0"),
            "frame_rate": data.get("frame_rate", 0),
            "type_code": data.get("type_code", StoragePlatformValue.COMMON),
            "hls_url": url_m3u8

        })
        return storage_info

    @classmethod
    def parse_data(cls, data, uid):
        url = data["url"]
        if DEBUG:
            url = '{}/test{}'.format('http://192.168.1.233:8058', url.split("test")[-1])
        else:
            url = '{}/matrix{}'.format(url_prefix, url.split("matrix")[-1])

        storage_info = {}

        name = data["name"]
        if not name:
            name = url.split("/")[-1]

        storage_info.update({
            "name": name,
            "url": url,
            "md5": data["md5"],
            "create_user": uid,
            "v_codec": data.get("v_codec"),
            "v_profile": data.get("v_profile"),
            "width": data.get("width"),
            "height": data.get("height"),
            "v_bit_rate": data.get("v_bit_rate"),
            "sample_aspect_ratio": data.get("sample_aspect_ratio"),
            "display_aspect_ratio": data.get("display_aspect_ratio"),
            "seconds": data.get("seconds"),
            "a_codec": data.get("a_codec"),
            "a_channels": data.get("a_channels"),
            "a_bit_rate": data.get("a_bit_rate"),
            "a_sample_rate": data.get("a_sample_rate"),
            "bit_rate": data.get("bit_rate"),
            "size": data.get("size"),
            "dub": data.get("dub", "0"),
            "frame_rate": data.get("frame_rate", 0),
            "type_code": data.get("type_code", 99)

        })
        return storage_info

    @classmethod
    def find_storage_by_episode_and_tag(cls, episode_id, tag):
        return cls.model.objects.filter(episode_id=episode_id, type_code=tag, is_delete=0).order_by(
            "-create_time").first()

    @staticmethod
    def parse_file(media_url):
        order = ['/usr/bin/ffprobe', '-i', media_url, '-print_format', 'json', '-show_streams', '-show_format', '-v',
                 'quiet']
        media_info = subprocess.check_output(order)
        media_info = json.loads(media_info)
        storage_info = {}
        for i in media_info["streams"]:
            if i["codec_type"] == "video":
                storage_info["v_codec"] = i["codec_name"]
                storage_info["v_profile"] = i.get("profile", "")
                storage_info["width"] = int(i["width"])
                storage_info["height"] = int(i["height"])
                storage_info["v_bit_rate"] = i.get("bit_rate", None)
                storage_info["sample_aspect_ratio"] = i.get("sample_aspect_ratio", "")
                storage_info["display_aspect_ratio"] = i.get("display_aspect_ratio", "")
                duration = i.get("duration") or media_info["format"].get("duration", 0)
                storage_info["seconds"] = math.ceil(float(duration))
                storage_info["dub"] = i["disposition"]["dub"]

        for i in media_info["streams"]:
            if i["codec_type"] == "audio":
                # 可能有多个；只取第一个
                storage_info["a_codec"] = i["codec_name"]
                storage_info["a_channels"] = i["channels"]
                storage_info["a_bit_rate"] = i.get("bit_rate", None)
                storage_info["a_sample_rate"] = i["sample_rate"]
                break

        storage_info["bit_rate"] = media_info["format"]["bit_rate"]
        storage_info["size"] = media_info["format"]["size"]
        return storage_info

    @classmethod
    def _get_list(cls, query, page, page_size):
        pager = Paginator(query, page_size)
        obj_list = pager.get_page(page)
        data_list = []
        for i in obj_list:
            storage = cls.to_dict(i)
            if len(storage["sample_aspect_ratio"]) > 3:
                _left, _right = storage["sample_aspect_ratio"].split(":")
                left, right = int(round(int(_left) / int(_right), 1) * 10), 10
                storage["sample_aspect_ratio"] = "{}:{}".format(left, right)
            if len(storage["display_aspect_ratio"]) > 7:
                _left, _right = storage["display_aspect_ratio"].split(":")
                left, right = int(round(int(_left) / int(_right), 2) * 100), 100
                storage["display_aspect_ratio"] = "{}:{}".format(left, right)
            storage["media_id"] = i.media_id
            storage["episode_id"] = i.episode_id
            media = i.media
            media = {"name": media.name, "category": media.category} if media else {}
            storage["media"] = media
            episode = i.episode
            episode = {"serial_num": episode.serial_num, "begin": episode.begin, "end": episode.end} if episode else {}
            storage["episode"] = episode
            storage["update_time"] = storage.get("update_time")
            job_id = cache.get(cls.make_md5(storage.get("url")), "")
            storage["job_id"] = job_id
            if cls.is_exist(storage.get("md5")):
                storage["type_code_name"] = StoragePlatformValue.TYPE_CODE_DICT[StoragePlatformValue.HLS_PROCESS]
            else:
                storage["type_code_name"] = StoragePlatformValue.TYPE_CODE_DICT[i.type_code]
            storage["url"] = cls.change_local_url(i.url)
            data_list.append(storage)

        data = {
            "total_page": pager.num_pages,
            "data_list": data_list,
        }
        return data

    @classmethod
    def change_local_url(cls, url):
        r1 = re.sub("//[\d\.]+:\d{4}", r"//192.168.1.141", url)
        # r1 = url
        path = re.sub("\w+?://[\d\.]+:\d{2,4}", r"", url)
        # host = "http://sy-boss.sh-senyu.com"
        # port = "8000" if DEBUG else "80"
        host = "http://112.16.171.210"
        port = "8086"
        # r1 = f"{host}:{port}/videos/{generate_signed_url(path)}"
        r1 = url
        return r1

    @classmethod
    def get_list(cls, uid, media_id=0, name="", is_show_all=0, page=1, page_size=20, type_code=None):
        query_data = {"is_delete": False}
        if not is_show_all:
            if not UserService.is_admin(uid):
                query_data["create_user"] = uid
        else:
            query_data["is_related_media"] = True
        if media_id:
            query_data["media_id"] = media_id
        if name:
            query_data["name__icontains"] = name
        if type_code:
            query_data["type_code"] = type_code
        if query_data:
            query = cls.model.objects.filter(**query_data).order_by("-id").select_related("media", "episode")
        else:
            query = cls.model.objects.all().order_by("-id").select_related("media", "episode")

        return cls._get_list(query, page, page_size)

    @classmethod
    def search(cls, name, start_year, end_year, region_id, category, type_code, page=1, page_size=20, sort="desc",
               acodec=''):
        query_data = {"is_delete": False}
        if not (name or start_year or region_id or category or type_code):
            if sort == "desc":
                query = cls.model.objects.filter(**query_data).order_by("-id").all().select_related("media", "episode")
            else:
                query = cls.model.objects.filter(**query_data).select_related("media", "episode")
        else:
            query_data["is_related_media"] = True
            if name:
                query_data["media__name__icontains"] = name
            if region_id:
                query_data["media__regions__contains"] = {"id": region_id}
            if category:
                query_data["media__category"] = category
            if type_code:
                query_data["type_code"] = type_code
            if start_year and end_year:
                if start_year == end_year:
                    query_data.update({"media__year": int(start_year)})
                else:
                    query_data.update({
                        "media__year__gte": int(start_year),
                        "media__year__lte": int(end_year)
                    })
            if acodec:
                query_data.update({'a_codec': acodec})
            if sort == "desc":
                query = cls.model.objects.order_by("-id").filter(**query_data).select_related("media", "episode")
            else:
                query = cls.model.objects.filter(**query_data).select_related("media", "episode")
        return cls._get_list(query, page, page_size)

    def detail(self, storage_id):
        obj = self.model.objects.filter(pk=storage_id).first()
        media = obj.media
        episode = obj.episode
        info = self.to_dict(obj)
        info["media"] = self.to_dict(media)
        info["episode"] = self.to_dict(episode)
        return info

    @classmethod
    def related_media(cls, storage_id, media_id, episode_id, content_type, definition, pro_type):
        update_info = {
            "media_id": media_id,
            "content_type": content_type,
            "definition": definition,
            "pro_type": pro_type,
        }
        if episode_id:
            update_info.update({
                "is_related_media": True,
                "episode_id": episode_id
            })
        cls.model.objects.filter(pk=storage_id).update(**update_info)
        return

    @classmethod
    def bulk_related_media(cls, related_list):
        """批量关联;无法批量更新，采用循环更新;暂时未用"""
        for related in related_list:
            storage_id = related.pop("storage_id")
            related["is_related_media"] = True
            cls.model.objects.filter(pk=storage_id).update(**related)

        return

    @classmethod
    def bulk_delete_video(cls, storage_ids):
        """ 批量删除视频 """
        queryset = cls.model.objects.filter(pk__in=storage_ids)
        print("query--stream_data")
        for item in queryset:
            stream_data = {"pk": item.id, "url": item.url, "hls_url": item.hls_url}
            print("stream_data:{}".format(stream_data))
            logger.info("stream_data:{}".format(stream_data))
            enqueue(DELETE_MEDIA_VIDEO, stream_data)

    @classmethod
    def delete_video_only(cls, storage_ids):
        for storage_id in storage_ids:
            media_video = cls.model.objects.filter(pk=storage_id).first()
            stream_data = {"path": media_video.url}
            enqueue(DELETE_MEDIA_VIDEO, stream_data)

    @classmethod
    def query_storage_by_episode(cls, episode_id):
        queryset = cls.model.objects.filter(episode_id=episode_id)
        return [{"id": item.id, "name": item.name, "type_code": item.type_code} for item in queryset]

    @classmethod
    def query_delete_meta_video(cls):
        data = query_many(DELETE_MEDIA_VIDEO)
        print("data_:{}".format(data))
        return data

    @classmethod
    def make_md5(cls, o_str):
        md5 = hashlib.md5()
        string = o_str
        byte_string = string.encode()
        md5.update(byte_string)
        return md5.hexdigest()

    @classmethod
    def save_cache(cls, file_url, _value):
        return cache.set(MediaStorage.make_md5(file_url), _value, 3600 * 72)

    @classmethod
    def is_bad(cls, file_url):
        r = requests.head(file_url)
        return r.status_code == 404

    @classmethod
    def get_tag_list(cls):
        # TYPE_CODE_DICT = {99: "通用", 98: "通用-m3u8", 97: "通用-mp4", 21: "安徽电信影视", 16: "山西移动", 2: "湖北移动", 1: "银河"}
        data = [{"type_code": key, "type_code_name": val} for key, val in StoragePlatformValue.TYPE_CODE_DICT.items()]
        return data

    @classmethod
    def add_lock(cls, key, slat="cut"):
        _key = "{}_{}".format(slat, key)
        logger.warning("add_lock:{}".format(_key))
        res = False
        try:
            res = cache.add(_key, 1, 600)
        except Exception as e:
            logger.error("add_lock error :{}".format(e))
            cache.delete(_key)
        return res

    @classmethod
    def is_exist(cls, key, slat="cut"):
        _key = "{}_{}".format(slat, key)
        # logger.warning("is_exist:{}".format(_key))
        val = cache.get(_key)
        # logger.warning("val:{}".format(val))
        if val:
            logger.warning("True")
            return True
        return False

    @classmethod
    def release_lock(cls, key, slat="cut"):
        _key = "{}_{}".format(slat, key)
        logger.warning("release_lock:{}".format(_key))
        res = cache.delete(_key)
        return res > 0

    @classmethod
    def update_type_code_by_md5(cls, md5, storage_id, flag=None):
        params = {"md5": md5}
        if storage_id:
            params["pk"] = storage_id
        queryset = MediaStorageModel.objects.filter(**params)
        for storage in queryset:
            url = storage.url
            url_m3u8 = "{}/{}/{}".format("/".join(url.split("/")[:-1]), md5, "playlist.m3u8")
            if flag and flag == "note":
                url_m3u8 = url_m3u8.replace("/matrix/", "/nfs_storage/note/")
            if flag and flag == "test":
                url_m3u8 = url_m3u8.replace("/test/", "/nfs_storage/test/")
            storage.hls_url = url_m3u8
            new_code = StoragePlatformValue.TYPE_CODE_TO_CODE_UPDATE.get(storage.type_code, None)
            if new_code:
                storage.type_code = StoragePlatformValue.TYPE_CODE_TO_CODE_UPDATE[storage.type_code]
                storage.save()

    @classmethod
    def del_lock(cls, key, slat="cut"):
        _key = "{}_{}".format(slat, key)
        res = cache.delete(_key)
        return res > 0

    @classmethod
    def storage_is_online(cls, storage_id):
        # {'url': 'http://111.2.18.142:8081/matrix/2025/5/14/9634-13-52870284b5f3f4c37066ee5b31afcd5c.ts'}
        storage = MediaStorageModel.objects.values("url").filter(id=storage_id).first()
        if storage:
            url = storage.get("url")
            if url:
                try:
                    resp = requests.head(url, timeout=3)
                    if resp.status_code == 200:
                        return True
                except Exception as e:
                    print(e)
        return False

    @classmethod
    def storage_to_stream(cls, path, expires, signature):
        # 获得原始path
        origin_path = parse_secret_path(path)
        print("origin_path", origin_path)
        # 验证签名合法性
        com_path = compose_path(origin_path, expires)
        signature_new = generate_signed_text(com_path)
        print("signature_new", signature_new)
        if signature_new != signature:
            return 403, "签名错误"

        # 验证签名不过期
        if int(expires) < int(time.time()):
            return 403, "签名过期"
        # 文件是否存在
        # file_path = "{}".format(origin_path)
        # url = "{}{}".format("http://111.2.18.142:8081", origin_path)
        url = origin_path
        # 读取文件流
        return 200, url

    def parse_query_string(self, query_string):
        query_arg = str(query_string).split("?")[0]
        key_vals = query_arg.split("&")

    @classmethod
    def storage_to_verify(cls, original_uri):
        # 获得原始path
        arg_dict = handle_signed_url(original_uri)
        print("arg_dict", arg_dict)
        origin_path = arg_dict["origin_path"]
        expires = arg_dict["expires"]
        signature = arg_dict["signature"]
        # 验证签名合法性
        com_path = compose_path(origin_path, expires)
        signature_new = generate_signed_text(com_path)
        print("signature_new", signature_new)
        if signature_new != signature:
            return 403, "签名错误"

        # 验证签名不过期
        if int(expires) < int(time.time()):
            return 403, "签名过期"
        # 文件是否存在
        # file_path = "{}".format(origin_path)
        # url = "{}{}".format("http://111.2.18.142:8081", origin_path)
        url = origin_path
        # 读取文件流
        return 200, url
