import { GLOBAL } from '@/static/Global.js';
import JsFn from '@/utils/js-fn.js';
import API from '@/api/index.js';
const blobPath = ['audio', 'doc', 'exam','image', 'item', 'notice', 'other', 'paper','syllabus', 'res', 'student', 'survey', 'temp', 'thum', 'video', 'vote']
const { BlobServiceClient, BlobClient } = require("@azure/storage-blob")
//获取文件后缀和类型
function getExAndType(fileName) {
    let ex = fileName.substring(fileName.lastIndexOf('.') + 1)
    let type = 'other'
    ex = ex.toUpperCase()
    for (let key in GLOBAL.CONTENT_TYPES) {
        if (GLOBAL.CONTENT_TYPES[key].indexOf(ex) != -1) {
            type = key
            break
        }
    }
    return {
        ex, type
    }
}

export default class BlobTool {
    /**
     * 初始化Blob,需要先调用授权API
     * @param {string} blobUrl blob地址
     * @param {string} container 容器名称
     * @param {string} sasString 授权 需要前面有 “ ? ”
     * @param {string} scope 学校(school)/个人(private) 计算空间大小
     * */
    constructor(blobUrl, container, sasString, scope) {
        this.initBlob(blobUrl, container, sasString, scope)
    }
    /**
     * 初始化Blob,需要先调用授权API
     * @param {string} blobUrl blob地址
     * @param {string} container 容器名称
     * @param {string} sasString 授权
     * @param {string} scope 学校(school)/个人(private) 计算空间大小
     * */
    initBlob(blobUrl, container, sasString, scope) {

        if (blobUrl && container && sasString, scope) {
            //初始化containerClient
            this.blobService = new BlobServiceClient(blobUrl + sasString)
            let containerClient = this.blobService.getContainerClient(container)
            if (containerClient) {
                this.containerClient = containerClient
                this.container = container
                this.blobUrl = blobUrl
                this.sasString = sasString
                if (scope == 'private') {
                    this.blobSpace = GLOBAL.PRIVATE_SPACE
                } else if (scope == 'school') {
                    this.blobSpace = GLOBAL.SCHOOL_SPACE
                }
            } else {
                throw new Error("参数错误，初始化失败")
            }
        } else {
            throw new Error("初始化参数不完整")
        }
    }
    /**
     * 获取容器信息 (授权失败,需要账号级别的授权)
     * @param {object} 
     */
    getProperties(options) {
        return new Promise((r, j) => {
            //const blockBlobClient = this.containerClient.getBlockBlobClient('res/基础操作范例_16x9.HTE') //blob获取成功
            this.containerClient.getProperties(options).then(
                res => {
                    console.log('获取信息成功')
                    r(res)
                },
                err => {
                    console.log('获取信息失败')
                    j(err)
                }
            )
        })
    }
    /**
     * 上传文件之前检查文件是否存在，如果存在返回文件大小
     * @params {urls} 需要上传文件地址 相对地址
     * @params {containerName} 容器名称，如果直接对象调用则必传；如果已经初始化BlobTool对象，则优先使用参数，没有则使用对象容器
     */
    static checkBlobs(urls, containerName) {
        return new Promise((r, j) => {
            if ((containerName || this.container) && urls.length) {
                API.blob.checkBlobs({
                    urls,
                    containerName: containerName || this.container
                }).then(
                    res => {
                        r(res.urlsSize)
                    },
                    err => {
                        console.log(err)
                    }
                )
            } else {
                throw new Error("参数错误")
            }
        })
    }

    /**
     * 上传文件之前检查文件夹的大小
     * @params {prefix} 需要上传文件地址 相对地址
     * @params {containerName} 容器名称，如果直接对象调用则必传；如果已经初始化BlobTool对象，则优先使用参数，没有则使用对象容器
     * 
     * @return size
     */
    static checkPrefixSize(prefix, containerName) {
        return new Promise((r, j) => {
            if (containerName && prefix) {
                API.blob.checkBlobs({
                    prefix,
                    containerName,
                    urls: []
                }).then(
                    res => {
                        r(res.prefixSize)
                    },
                    err => {
                        console.log(err)
                    }
                )
            } else {
                throw new Error("参数错误")
            }
        })
    }

    /**
     * 更新Blob空间数据 
     * @params {urls} checkBlobs方法返回的数据处理size后
     * @params {containerName} 容器名称，如果直接对象调用则必传；如果已经初始化BlobTool对象，则优先使用参数，没有则使用初始化对象的容器
     */
    static updateSize(optUrls, containerName) {
        return new Promise((r, j) => {
            if ((containerName || this.container) && optUrls.length) {
                API.blob.updateSize({
                    optUrls,
                    containerName: containerName || this.container
                }).then(
                    res => {
                        r(res.urlsSize)
                    },
                    err => {
                        console.log(err)
                    }
                )
            } else {
                throw new Error("参数错误")
            }
        })
    }

    /**
     * 计算上传前后size
     * @param {any}  before 上传之前的文件大小
     * @param {any}  after 上传之后的文件大小
     */
    calcSize(before, after) {
        if (before.length == after.length) {
            let res = []
            before.forEach((item, index) => {
                let afterBlob = after.find((afterItem, afterIndex) => {
                    return afterItem.url == item.url
                })
                if (after) {
                    res.push({
                        url: item.url,
                        size: afterBlob.size - item.size
                    })
                } else {
                    throw new Error(`未找到${item.url}上传之后的大小`)
                }
            })
            return res
        } else {
            throw new Error('beforeSize/afterSize上传前后数据不对应')
        }
    }

    /**
     * 上传文件方法,带回调上传进度
     * @param {any} file 文件对象
     * @param {any} path 文件夹路径 只需要文件夹名称 前后都不需要加‘/’
     * @param {any} option 官方可配置项
     * @param {any} checkSize 上传时是否检查容器空间 默认需要检查大小
     * @param {any} handleSize 是否需要更新size，建议多文件上传时在外部批量处理
     * @returns {object} {url, name,size,createTime,extension,type}
     */
    upload(file, path, option, checkSize = true, handleSize = true) {
        console.log(blobPath.includes(path))
        if (!blobPath.includes(path.split('/')[0])) {
            throw new Error('上传路径不合法，请检查上传路径：' + path)
        }
        return new Promise(async (r, j) => {
            //检查容器空间大小
            if (checkSize) {
                if (!this.totalSize) {
                    try {
                        let sizeRes = await BlobTool.getSizeByServe(this.container)
                        this.totalSize = sizeRes.size
                    } catch (e) {
                        this.totalSize = -1
                        j({
                            spaceError: '容器空间计算失败，无法上传文件'
                        })
                    }
                }
                if (this.totalSize == -1) {
                    j({
                        spaceError: 'Blob空间计算失败，无法上传文件'
                    })
                } else if (this.totalSize > this.blobSpace) {
                    j({
                        spaceError: 'Blob空间已满，无法上传'
                    })
                }
            }
            let blobSize = 0
            // 上传之前检查size
            if (handleSize) {
                let u = path + "/" + file.name
                try {
                    let checkRes = await BlobTool.checkBlobs([u], this.container)
                    blobSize = checkRes && checkRes.urlsSize ? checkRes.urlsSize[0].size : 0
                } catch (e) {
                    throw new Error(e)
                }
            }
            const blockBlobClient = this.containerClient.getBlockBlobClient(path + "/" + file.name)
            blockBlobClient.uploadBrowserData(file, option).then(
                res => {
                    let url = decodeURIComponent(res._response.request.url)
                    url = url.substring(0, url.lastIndexOf('?'))
                    let info = getExAndType(file.name)

                    let updateSize = res._response.request.body.size - blobSize

                    if (this.totalSize) {
                        this.totalSize += updateSize
                    }
                    // 上传成功后更新size
                    if (handleSize) {
                        BlobTool.updateSize([{
                            url: path + "/" + file.name,
                            size: updateSize
                        }], this.container)
                    }
                    r({
                        url: url,
                        blob: '/' + path + "/" + file.name,
                        name: file.name,
                        size: res._response.request.body.size,
                        createTime: res.lastModified.getTime(),
                        extension: info.ex,
                        type: info.type,
                        updateSize: updateSize
                    })
                },
                err => {
                    j(err)
                }
            )
        })
    }
    /**
     * 处理HTEX文件类型
     * @param {blobList} fileList 
     */
    handleHTEXFile(fileList) {
        let parseRes = []
        let names = []
        fileList.forEach((item, index) => {
            if (item.url.indexOf('/res/') > 0 && item.url.indexOf('.HTE') < 0) {
                let fileItem = {}
                let startIndex = JsFn.findChartIndex(item.blob, '/', 1)
                let endIndex = JsFn.findChartIndex(item.blob, '/', 2)
                let name = item.blob.substring(startIndex + 1, endIndex)
                let nameIndex = names.indexOf(name)
                if (nameIndex == -1) {
                    fileItem.url = this.blobUrl + '/' + this.container + '/res/' + name
                    fileItem.blob = `/res/${name}/index.json`
                    fileItem.name = name + '.HTEX'
                    fileItem.size = item.size
                    fileItem.createTime = item.createTime
                    fileItem.extension = 'HTEX'
                    fileItem.type = 'res'
                    names.push(name)
                    parseRes.push(fileItem)
                } else {
                    parseRes[nameIndex].size += item.size
                }
            } else {
                parseRes.push(item)
            }
        })
        return parseRes
    }

    /**
     * 列出所有（查询）
     * @param {Object} option ContainerListBlobsOptions
     * eg: option.prefix = 'res' 只查res文件夹下的blob
     * @returns {object} {blobList, continuationToken}
     */
    async listBlob(option, hendleHTEX = true) {
        return new Promise(async (r, j) => {
            let blobList = []
            if (this.containerClient) {
                let iter = this.containerClient.listBlobsFlat(option ? option : {});
                let blobItem = await iter.next();
                while (!blobItem.done) {
                    let blobName = blobItem.value.name
                    let info = getExAndType(blobItem.value.name)
                    blobList.push(
                        {
                            url: this.blobUrl + '/' + this.container + '/' + blobName,
                            blob: '/' + blobName,
                            // name: blobName.substring(JsFn.findChartIndex(blobName, '/', 0) + 1),
                            name: blobName.substring(blobName.lastIndexOf('/') + 1),
                            size: blobItem.value.properties.contentLength,
                            createTime: blobItem.value.properties.createdOn.getTime(),
                            extension: info.ex,
                            type: info.type
                        }
                    )
                    blobItem = await iter.next();
                }
                if (hendleHTEX) {
                    blobList = this.handleHTEXFile(blobList)
                }
                r({
                    blobList,
                    continuationToken: 'end'
                })
            } else {
                j('containerClient 错误')
            }
        })
    }
    /**
     * 分页列出（查询）
     * @param {Object} option ContainerListBlobsOptions
     * eg: option.prefix = 'res' 只查res文件夹下的blob
     * @param {Object} pageInfo 
     * eg: pageInfo.maxPageSize 当前请求条数
     * eg: pageInfo.continuationToken 首次请求不需要，后面需要（首次请求会返回，下次需要传入）
     * @returns {object} {blobList, continuationToken}
     */
    async listBlobByPage(option, pageInfo) {
        return new Promise(async (r, j) => {
            let page = {}
            let blobList = []
            if (pageInfo && JSON.stringify(pageInfo) != '{}') {
                page.maxPageSize = pageInfo.maxPageSize ? pageInfo.maxPageSize : 50
                if (pageInfo.continuationToken) {
                    page.continuationToken = pageInfo.continuationToken ? pageInfo.continuationToken : ''
                }
            }
            if (this.containerClient) {
                let iterator = this.containerClient.listBlobsFlat(option ? option : {}).byPage(page)
                let response = (await iterator.next()).value
                let prefixLen = response.prefix ? response.prefix.length + 1 : 0
                for (const blob of response.segment.blobItems) {
                    let info = getExAndType(blob.name)
                    blobList.push(
                        {
                            url: response.serviceEndpoint + response.containerName + '/' + blob.name,
                            blob: '/' + blob.name,
                            name: blob.name.substring(prefixLen),
                            size: blob.properties.contentLength,
                            createTime: blob.properties.lastModified.getTime(),
                            extension: info.ex,
                            type: info.type
                        }
                    )
                }
                let continuationToken = response.continuationToken ? response.continuationToken : 'end'
                r({
                    blobList,
                    continuationToken
                })
            } else {
                j('containerClient 错误')
            }
        })
    }
    /**
     * 删除Blob
     * @param {string} filePath 文件url + 容器 之后的路径
     */
    deleteBlob(filePath, size) {
        console.log(typeof size)
        console.log(typeof size == 'number')
        if (filePath && typeof size == 'number') {
            filePath = filePath.substring(1)
            return new Promise((r, j) => {
                this.containerClient.deleteBlob(filePath).then(
                    async res => {
                        let sizeInfo = await BlobTool.updateSize([{
                            url: filePath,
                            size: -size
                        }], this.container)
                        r(sizeInfo)
                    },
                    err => {
                        j(err)
                    }
                )

            })
        } else {
            throw new Error("filePath或size参数不完整")
        }

    }

    /**
     * 批量删除Blob 官方API授权失败(需要账号级别的授权,批量删除请访问后端API)
     * @param {string} files 文件url + 容器 之后的路径
     */
    deleteBlobs(files) {
        this.getProperties()
        let blobBatchClient = this.blobService.getBlobBatchClient()
        return new Promise((r, j) => {
            for (let i in files) {
                files[i] = files[i].substring(0, files[i].lastIndexOf('?'))
            }
            blobBatchClient.deleteBlobs(files, this.blobService.credential).then(
                res => {
                    console.log('批量删除成功')
                },
                err => {
                    console.log('批量删除失败')
                }
            )
        })
    }
    /**
     * 批量删除Blob 循环操作
     * @param {string} files 
     */
    deleteBlobBatch(files) {
        return new Promise((r, j) => {
            let promises = []
            for (let item of files) {
                let f = item.substring(1)
                promises.push(this.containerClient.deleteBlob(f))
            }
            Promise.all(promises).then(
                res => {
                    r(res)
                },
                err => {
                    j(err)
                }
            )
        })
    }
    /**
     * 复制单个Blob
     * @param {string} targetUrl 
     * @param {string} sourceUrl 
     * @param {string} sas 
     * 1、目标url（sourceUrl）不能出现中文或UTF-8码的字符， 如果有需要进行URI编码，否则会报错；
     * 2、目标url（sourceUrl）如果需要授权的容器，则url需要凭借授权。
     */
    copyBlob(targetUrl, sourceUrl, sas) {
		console.log(...arguments)
        return new Promise((r, j) => {
            let newBlob = this.containerClient.getBlobClient(targetUrl)
            let encodeUrl = encodeURI(sourceUrl)
            newBlob.beginCopyFromURL(encodeUrl + sas).then(
                res => {
                    console.log('复制成功返回数据', res)
                    r(200)
                },
                err => {
                    j(500)
                }
            )
        })
    }

    /**
     * 复制‘文件夹’
     * @param {string} targetFolder eg:'exam/评测id/paper/8b94c6b6-2572-41e5-89b9-a82fcf13891e/' 注意开头不加‘/’, 结尾需要加‘/’
     * @param {string} sourceFolder eg:'paper/JEFF組卷測試01'
     * @param {BlobTool} blobTool 非必传参数， 当目标文件和源文件不在同一个容器的时候，需要传源文件容器初始化的BlobTool
     * @param {boolean} handleSize 是否内部处理blobsize
     */
    copyFolder(targetFolder, sourceFolder, blobTool, handleSize = true) {
        return new Promise(async (r, j) => {
            let blobs = undefined
            let sasString = ''
            if (blobTool) {
                blobs = await blobTool.listBlob({
                    prefix: sourceFolder + '/'
                }, false)
                sasString = blobTool.sasString
            } else {
                blobs = await this.listBlob({
                    prefix: sourceFolder + '/'
                }, false)
                sasString = this.sasString
            }
            //上传之前检查文件夹大小
            let beforeSize = 0
            let cont = ''
            if (handleSize) {
                cont = this.container
                beforeSize = await BlobTool.checkPrefixSize(targetFolder.substring(0, targetFolder.length - 1), cont)
            }

            if (blobs && blobs.blobList) {
                let count = 0
                blobs.blobList.forEach(blobItem => {
                    let newUrl = targetFolder + blobItem.name
                    let newBlob = this.containerClient.getBlobClient(newUrl)
                    let resourceUrl = encodeURI(blobItem.url)
                    newBlob.beginCopyFromURL(resourceUrl + sasString).then().finally(async () => {
                        if (++count == (blobs.blobList.length)) {
                            if (handleSize) {
                                //复制之后更新大小
                                let afterSize = await BlobTool.checkPrefixSize(targetFolder, cont)
                                let updSize = afterSize - beforeSize
                                if (updSize !== 0) {
                                    BlobTool.updateSize([{
                                        url: targetFolder,
                                        size: updSize
                                    }], cont)
                                }
                            }
                            r('复制结束后')
                        }
                    })
                })
            }
        })
    }
    /**
     * 判断文件是否存在
     * @param {string} filePath 文件路径 正确 'res/基础操作范例_16x9.HTE ' 错误 '/res/基础操作范例_16x9.HTE'
     * @param {object} 
     * return true/false
     */
    exists(filePath, options) {
        const blockBlobClient = this.containerClient.getBlockBlobClient(filePath)
        return new Promise((r, j) => {
            blockBlobClient.exists(options).then(
                res => {
                    r(res)
                },
                err => {
                    j(err)
                }
            )
        })
    }
    /**
     * 计算容器的空间大小
     * return size
     */
    async getContainerSize() {
        return new Promise((r, j) => {
            this.listBlob({}, false).then(
                res => {
                    let totalSize = res.blobList.reduce((total, item) => {
                        return total + parseInt(item.size)
                    }, 0)
                    this.totalSize = totalSize
                    r(totalSize)
                },
                err => {
                    j(err)
                }
            )
        })
    }

    /**
     * 判断容器的空间是否已满
     * @param {string} scope 校本/个人 school / private
     * return true/false
     */
    async isContainerFull() {
        return new Promise(async (r, j) => {
            try {
                // let size = await this.getContainerSize()
                let sizeRes = await BlobTool.getSizeByServe(this.container)
                if (sizeRes)
                    if (sizeRes.size > this.blobSpace) {
                        r(true)
                    } else {
                        r(false)
                    }
            } catch (e) {
                j('容器空间判断失败！')
            }

        })
    }
    /**
     * 计算指定文件夹的空间大小,如果不传文件夹，则查询整个容器空间
     * @param {string} 
     * @param {object} 
     * return true/false
     */
    getSize(option) {
        return new Promise((r, j) => {
            this.listBlob(option, false).then(
                res => {
                    let sizeInfo = {
                        total: 0,   //所有
                        image: 0,   //内容模块图片
                        res: 0,     //内容模块教材
                        video: 0,   //内容模块视频
                        audio: 0,   //内容模块音频
                        doc: 0,     //内容模块文档
                        other: 0,   //内容模块其他文件
                        data: 0      //除了内容模块之外的文件
                    }
                    let length = res.blobList.reduce((total, item) => {
                        return total + parseInt(item.size)
                    }, 0)
                    sizeInfo.total = length
                    for (let item of res.blobList) {
                        let folder = item.blob.substring(1, JsFn.findChartIndex(item.blob, '/', 1))
                        switch (folder) {
                            case 'res':
                                sizeInfo.res += item.size
                                break
                            case 'image':
                                sizeInfo.image += item.size
                                break
                            case 'video':
                                sizeInfo.video += item.size
                                break
                            case 'audio':
                                sizeInfo.audio += item.size
                                break
                            case 'doc':
                                sizeInfo.doc += item.size
                                break
                            case 'other':
                                sizeInfo.other += item.size
                                break
                            default:
                                sizeInfo.data += item.size
                                break
                        }
                    }
                    r(sizeInfo)
                },
                err => {
                    j(err)
                }
            )
        })
    }
    /**
     * 从后端获取blob空间大小
     * @params {containerName} 容器名称
     * @params {cache} 是否从缓存读取
     */
    static getSizeByServe(containerName, cache = true) {
        return new Promise((r, j) => {
            if (containerName) {
                API.blob.getContainerSize({
                    cache,
                    containerName
                }).then(
                    res => {
                        let sizeInfo = {
                            total: 0,   //所有
                            image: 0,   //内容模块图片
                            res: 0,     //内容模块教材
                            video: 0,   //内容模块视频
                            audio: 0,   //内容模块音频
                            doc: 0,     //内容模块文档
                            other: 0,   //内容模块其他文件
                            data: 0      //除了内容模块之外的文件
                        }
                        sizeInfo.total = res.size
                        let defined = ['image', 'res', 'video', 'audio', 'doc', 'other']
                        for (let key in res.catalog) {
                            if (defined.indexOf(key) > -1) {
                                sizeInfo[key] = res.catalog[key]
                            } else {
                                sizeInfo.data += res.catalog[key]
                            }
                        }
                        r(sizeInfo)
                    },
                    err => {
                        console.log(err)
                    }
                )
            } else {
                throw new Error("containerName 不正确")
            }
        })
    }

}
