import os
import queue
from datetime import datetime, timedelta

import psutil
from api import XiGuaLiveApi
import json
import threading
from bilibili import *

# 默认设置
config = {
    # 录像的主播名
    "l_u": "永恒de草薙",
    "b_u": "自己的B站账号",
    "b_p": "自己的B站密码",
    # 标题及预留时间位置
    "t_t": "【永恒de草薙直播录播】直播于 {}",
    # 标签
    "tag": ["永恒de草薙", "三国", "三国战记", "直播录像", "录播", "怀旧", "街机"],
    # 描述
    "des": "西瓜直播 https://live.ixigua.com/userlive/97621754276 \n自动投递\n原主播:永恒de草薙\n直播时间:晚上6点多到凌晨4点左右",
    # 来源, 空则为自制
    "src": "",
    # Log条数
    "l_c": 5,
    # 错误Log条数
    "elc": 10,
    # 每一chunk大小
    "c_s": 16 * 1024,
    # 每一块视频大小
    "p_s": 2141000000,
    # 忽略的大小
    "i_s": 2048000,
    "max": 75,
    "exp": 1,
    "dow": "echo 'clean'",
    # 仅下载
    "dlO": True,
    # 下播延迟投稿
    "dly": 30,
    # 短的时间的格式
    "sdf": "%Y%m%d",
    "enc": "ffmpeg -i {f} -c:v copy -c:a copy -f mp4 {t} -y"
}
doCleanTime = datetime.fromtimestamp(0)
loginTime = datetime.fromtimestamp(0)
_clean_flag = None
delay = datetime.fromtimestamp(0)
b = Bilibili()

network = [{
    "currentTime": datetime.now(),
    "out": {
        "currentByte": psutil.net_io_counters().bytes_sent,
    },
    "in": {
        "currentByte": psutil.net_io_counters().bytes_recv,
    }
}, {
    "currentTime": datetime.now(),
    "out": {
        "currentByte": psutil.net_io_counters().bytes_sent,
    },
    "in": {
        "currentByte": psutil.net_io_counters().bytes_recv,
    }
}]


def reloadConfig():
    global config
    if os.path.exists('config.json'):
        _config_fp = open("config.json", "r", encoding="utf8")
        _config = json.load(_config_fp)
        config.update(_config)
        _config_fp.close()


def resetDelay():
    global delay
    delay = datetime.now() + timedelta(minutes=int(config['dly']))


def doDelay():
    global delay
    if -60 < getTimeDelta(datetime.now(), delay) < 60:
        delay = datetime.fromtimestamp(0)
        return True
    return False


def updateNetwork():
    global network
    network.append({
        "currentTime": datetime.now(),
        "out": {
            "currentByte": psutil.net_io_counters().bytes_sent,
        },
        "in": {
            "currentByte": psutil.net_io_counters().bytes_recv,
        }
    })
    network = network[-3:]


def getTimeDelta(a, b):
    return (a - b).total_seconds()


def _doClean(_force=False):
    global doCleanTime, _clean_flag
    _disk = psutil.disk_usage(".")
    if _disk.percent > config["max"] or getTimeDelta(datetime.now(), doCleanTime) > config["exp"] * 86400 or _force:
        _clean_flag = True
        doCleanTime = datetime.now()
        appendOperation("执行配置的清理命令")
        os.system(config["dow"])
        appendOperation("执行配置的清理命令完毕")
        doCleanTime = datetime.now()
    _clean_flag = False


def doClean(_force=False):
    if _clean_flag:
        return
    p = threading.Thread(target=_doClean, args=(_force,))
    p.setDaemon(True)
    p.start()


def getCurrentStatus():
    _disk = psutil.disk_usage(".")
    _mem = psutil.virtual_memory()
    _net = psutil.net_io_counters()
    _delta = getTimeDelta(network[-1]["currentTime"], network[-2]["currentTime"])
    if 60 > _delta > 1:
        _inSpeed = (network[-1]["in"]["currentByte"] - network[-2]["in"]["currentByte"]) / _delta
        _outSpeed = (network[-1]["out"]["currentByte"] - network[-2]["out"]["currentByte"]) / _delta
    else:
        _outSpeed = (network[-1]["in"]["currentByte"] - network[-2]["in"]["currentByte"])
        _inSpeed = (network[-1]["out"]["currentByte"] - network[-2]["out"]["currentByte"])
    updateNetwork()
    return {
        "memTotal": parseSize(_mem.total),
        "memUsed": parseSize(_mem.used),
        "memUsage": _mem.percent,
        "diskTotal": parseSize(_disk.total),
        "diskUsed": parseSize(_disk.used),
        "diskUsage": _disk.percent,
        "cpu": psutil.cpu_percent(),
        "outSpeed": parseSize(_outSpeed),
        "inSpeed": parseSize(_inSpeed),
        "doCleanTime": datetime.strftime(doCleanTime, dt_format),
        "fileExpire": config["exp"],
    }


dt_format = "%Y/%m/%d %H:%M:%S"
reloadConfig()
broadcaster = ""
streamUrl = ""

forceNotDownload = False
forceNotBroadcasting = False
forceNotUpload = False
forceNotEncode = False
if config["dlO"] is True:
    forceNotUpload = True
    forceNotEncode = True
forceStartEncodeThread = False
forceStartUploadThread = False

uploadQueue = queue.Queue()
encodeQueue = queue.Queue()

uploadStatus = []
downloadStatus = []
encodeStatus = []
errors = []
operations = []


def appendOperation(obj):
    global operations
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        operations.append(obj)
    else:
        operations.append({
            "datetime": datetime.strftime(datetime.now(), dt_format),
            "message": str(obj)
        })
    operations = operations[-config["elc"]:]


def parseSize(size):
    K = size / 1024.0
    if K > 1000:
        M = K / 1024.0
        if M > 1000:
            return "{:.2f}GB".format(M / 1024.0)
        else:
            return "{:.2f}MB".format(M)
    else:
        return "{:.2f}KB".format(K)


def appendUploadStatus(obj):
    global uploadStatus
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        uploadStatus.append(obj)
    else:
        uploadStatus.append({
            "datetime": datetime.strftime(datetime.now(), dt_format),
            "message": str(obj)
        })
    uploadStatus = uploadStatus[-config["l_c"]:]


def modifyLastUploadStatus(obj):
    global uploadStatus
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        uploadStatus[-1] = obj
    else:
        uploadStatus[-1]["message"] = str(obj)
        uploadStatus[-1]["datetime"] = datetime.strftime(datetime.now(), dt_format)


def appendEncodeStatus(obj):
    global encodeStatus
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        encodeStatus.append(obj)
    else:
        encodeStatus.append({
            "datetime": datetime.strftime(datetime.now(), dt_format),
            "message": str(obj)
        })
    encodeStatus = encodeStatus[-config["l_c"]:]


def modifyLastEncodeStatus(obj):
    global encodeStatus
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        encodeStatus[-1] = obj
    else:
        encodeStatus[-1]["message"] = str(obj)
        encodeStatus[-1]["datetime"] = datetime.strftime(datetime.now(), dt_format)


def appendDownloadStatus(obj):
    global downloadStatus
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        downloadStatus.append(obj)
    else:
        downloadStatus.append({
            "datetime": datetime.strftime(datetime.now(), dt_format),
            "message": str(obj)
        })
    downloadStatus = downloadStatus[-config["l_c"]:]


def modifyLastDownloadStatus(obj):
    global downloadStatus
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        downloadStatus[-1] = obj
    else:
        downloadStatus[-1]["message"] = str(obj)
        downloadStatus[-1]["datetime"] = datetime.strftime(datetime.now(), dt_format)


def appendError(obj):
    global errors
    if isinstance(obj, dict):
        if "datetime" not in obj:
            obj["datetime"] = datetime.strftime(datetime.now(), dt_format)
        errors.append(obj)
    else:
        errors.append({
            "datetime": datetime.strftime(datetime.now(), dt_format),
            "message": str(obj)
        })
    errors = errors[-config["elc"]:]


def loginBilibili(force=False):
    if config["dlO"] is False or forceNotUpload is False:
        global loginTime
        global b
        if getTimeDelta(datetime.now(), loginTime) < 86400 * 10 and not force:
            return False
        if os.path.exists('cookie'):
            try:
                with open('cookie', 'r', encoding='utf8') as f:
                    _cookie = f.read(4096)
                    b = Bilibili(_cookie)
                    loginTime = datetime.now()
                    appendOperation("Cookie 登录")
                    return True
            except Exception as e:
                appendError(e)
        appendOperation("Cookie 登录失败")
        return False
    else:
        appendOperation("设置了不上传,所以不会登陆")


class downloader(XiGuaLiveApi):
    playlist = None

    def updRoomInfo(self, force=False):
        global broadcaster
        _prev_status = self.isLive
        doClean()
        _result = super(downloader, self).updRoomInfo(force)
        if _prev_status != self.isLive and not self.isLive:
            resetDelay()
        broadcaster = self.broadcaster
        if _result:
            if self.isLive:
                self.updPlayList()
            else:
                self.playlist = False
        return _result

    def updPlayList(self):
        global streamUrl
        if self.isLive and "stream_url" in self._rawRoomInfo:
            self.playlist = self._rawRoomInfo["stream_url"]["flv_pull_url"]
            if type(self.playlist) is dict:
                for _ in self.playlist.values():
                    self.playlist = _
                    break
            self.playlist = self.playlist.replace("_uhd", "").replace("_sd", "").replace("_ld", "")
            streamUrl = self.playlist
        else:
            streamUrl = None
            self.playlist = None


api = downloader(config["l_u"])


def refreshDownloader():
    global api
    api = downloader(config["l_u"])


def uploadVideo(name):
    if not os.path.exists(name):
        appendError("Upload File Not Exist {}".format(name))
        return
    loginBilibili()
    doClean()
    if forceNotUpload is False:
        b.preUpload(VideoPart(name, os.path.basename(name)))
    else:
        appendUploadStatus("设置了不上传,所以[{}]不会上传了".format(name))
    if not forceNotEncode:
        os.remove(name)


def publishVideo(date):
    if forceNotUpload is False:
        b.finishUpload(config["t_t"].format(date), 17, config["tag"], config["des"],
                       source=config["src"], no_reprint=0)
        b.clear()
    else:
        appendUploadStatus("设置了不上传,所以[{}]的录播不会投了".format(date))


def encodeVideo(name):
    if forceNotEncode:
        appendEncodeStatus("设置了不编码,所以[{}]不会编码".format(name))
        return False
    if not os.path.exists(name):
        appendEncodeStatus("文件[{}]不存在".format(name))
        return False
    if os.path.getsize(name) < 8 * 1024 * 1024:
        appendEncodeStatus("Encoded File >{}< is too small, will ignore it".format(name))
        return False
    appendEncodeStatus("Encoding >{}< Start".format(name))
    _new_name = os.path.splitext(name)[0] + ".mp4"
    _code = os.system(config["enc"].format(f=name, t=_new_name))
    if _code != 0:
        Common.appendError("Encode {} with Non-Zero Return.".format(name))
        return False
    Common.modifyLastEncodeStatus("Encode >{}< Finished".format(name))
    uploadQueue.put(_new_name)


def collectInfomation():
    return {
        "download": downloadStatus,
        "encode": encodeStatus,
        "encodeQueueSize": encodeQueue.qsize(),
        "upload": uploadStatus,
        "uploadQueueSize": uploadQueue.qsize(),
        "error": errors,
        "operation": operations,
        "broadcast": {
            "broadcaster": broadcaster.__str__(),
            "isBroadcasting": api.isLive,
            "streamUrl": streamUrl,
            "updateTime": api.updateAt.strftime(dt_format),
            "delayTime": delay.strftime(dt_format)
        },
        "config": {
            "forceNotBroadcasting": forceNotBroadcasting,
            "forceNotDownload": forceNotDownload,
            "forceNotUpload": forceNotUpload,
            "forceNotEncode": forceNotEncode,
            "downloadOnly": config['dlO'],
        },
    }