package com.fs.course.service.impl;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.fs.course.service.IHuaweiVodService;
import com.huaweicloud.sdk.core.auth.BasicCredentials;
import com.huaweicloud.sdk.core.auth.ICredential;
import com.huaweicloud.sdk.core.exception.ServiceResponseException;
import com.huaweicloud.sdk.vod.v1.VodClient;
import com.huaweicloud.sdk.vod.v1.model.*;
import com.huaweicloud.sdk.vod.v1.region.VodRegion;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.stereotype.Service;
import ws.schild.jave.Encoder;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;

import java.io.*;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.TimeZone;

@Service
public class HuaweiVodServiceImpl implements IHuaweiVodService {
    // 区域
    public static final String REGION_NORTH4 = "cn-north-4";
    public static final String REGION_NORTH1 = "cn-north-1";
    public static final String REGION_EAST2 = "cn-east-2";

    // ak/sk
    private static final String AK = "K2UTJGIN7UTZJR2XMXYG";
    private static final String SK = "sbyeNJLbcYmH6copxeFP9pAoksM4NIT9Zw4x0SRX";


    private static final String FIXED_VIDEO_PATH = "C:\\fs\\mergeVideo\\first.mp4";

    @Override
    public String uploadAndMergeVideo(File uploadedFile) throws IOException, InterruptedException {
        // 临时文件路径
        String mergedVideoPath = System.getProperty("java.io.tmpdir") + "merged_" + uploadedFile.getName();

        // 合并视频
        mergeVideos(FIXED_VIDEO_PATH, uploadedFile.getPath(), mergedVideoPath);

        // 上传到华为云VOD
        String assetId = uploadPartFile(new File(mergedVideoPath));

        return assetId;
    }

    private void mergeVideos(String fixedVideoPath, String uploadedVideoPath, String outputPath) throws IOException, InterruptedException {
        // 创建合并列表文件
        File listFile = new File(System.getProperty("java.io.tmpdir") + "/merge_list.txt");
        try (PrintWriter writer = new PrintWriter(listFile)) {
            writer.println("file '" + fixedVideoPath.replace("\\", "/") + "'");
            writer.println("file '" + uploadedVideoPath.replace("\\", "/") + "'");
        }
        String command = String.format("ffmpeg -f concat -safe 0 -i %s -c copy %s", listFile.getPath(), outputPath);

        // 打印调试信息
        System.out.println("Executing command: " + command);

        // 执行命令
        Process process = Runtime.getRuntime().exec(command);

        // 等待命令执行完成
        int exitCode = process.waitFor();

        if (exitCode != 0) {
            // 读取并打印错误输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String line;
            StringBuilder errorMsg = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                errorMsg.append(line).append(System.lineSeparator());
            }
            throw new IOException("FFmpeg command failed with exit code " + exitCode + ":\n" + errorMsg);
        }

    }

    @Override
    public BaseInfo getBaseInfo(String assetId) {
        // 1.初始化鉴权并获取vodClient
        VodClient vodClient = this.createVodClient();
        BaseInfo baseInfo = getAssetDetail(vodClient, assetId);
        return baseInfo;
    }

    // 设置缓冲区大小，每次读取文件分段的大小，根据情况自行设定
    public static final int bufferSize = 1024 * 1024; // 1MB

    /**
     * 分段上传
     * @param file 上传文件的本地路径
     */
    @Override
    public String uploadPartFile(File file) {
        // 校验一下文件路径和文件
//        File file = validFile(filePath);
        try {
            String fileName = file.getName();

            // 此处仅以MP4文件示例，其他格式可参考官网说明
            String fileType = "MP4";
            String fileContentType = "video/mp4";

            // FileInfo fileInfo = new FileInfo(filePath, file);
            // 1.初始化鉴权并获取vodClient
            VodClient vodClient = this.createVodClient();
            System.out.println("开始创建媒资:" + file.getName());
            // 2.创建点播媒资
            CreateAssetByFileUploadReq reqbody = this.buildFileUploadReq(fileName, fileType, null, null);
            CreateAssetByFileUploadResponse assetResponse = this.createAssetByFileUpload(vodClient, reqbody);
            // 3.获取初始化上传任务授权
            ShowAssetTempAuthorityResponse initAuthResponse = this.initPartUploadAuthority(vodClient, assetResponse, fileContentType);
            // 4.初始化上传任务
            String uploadId = this.initPartUpload(initAuthResponse.getSignStr(), fileContentType);

            // 文件分段计数
            int partNumber = 1;
            // 缓冲区
            byte[] fileByte = new byte[bufferSize];
            // 记录读取的长度
            int readLength = 0;

            // 7.读取文件内容， 循环5-6步上传所有分段
            FileInputStream fis = new FileInputStream(file);
            // MD5
            MessageDigest md = MessageDigest.getInstance("MD5");
            while ((readLength = fis.read(fileByte)) > 0) {
                // 读取的长度小于缓冲区长度，复制有效长度内数据，用于最后一段
                if(readLength < bufferSize) {
                    fileByte = Arrays.copyOf(fileByte, readLength);
                }
                // 先md5,再base64 encode
                byte[] digest = md.digest(fileByte);
                String contentMd5 = new String(Base64.encodeBase64(digest));
                System.out.println("该文件第"+(partNumber)+"段MD5为： " + contentMd5);

                // 5.获取上传分段的授权
                ShowAssetTempAuthorityResponse partUploadAuthorityResponse = this.getPartUploadAuthority(vodClient, fileContentType, assetResponse, contentMd5, uploadId, partNumber);

                // 6.上传分段
                this.uploadPartFile(partUploadAuthorityResponse.getSignStr(), fileByte, contentMd5);

                // 段号自增
                partNumber++;
            }

            fis.close();

            // 8.获取已上传分段的授权
            ShowAssetTempAuthorityResponse listPartUploadAuthorityResponse = this.listUploadedPartAuthority(vodClient, assetResponse, uploadId);
            // 9.获取已上传的分段
            String partInfo = this.listUploadedPart(listPartUploadAuthorityResponse.getSignStr());
            // 10.获取合并段授权
            ShowAssetTempAuthorityResponse mergePartUploadAuthorityResponse = this.mergeUploadedPartAuthority(vodClient, assetResponse, uploadId);
            // 11.合并上传分段
            this.mergeUploadedPart(mergePartUploadAuthorityResponse.getSignStr(), partInfo);
            // 12.确认媒资上传
            this.confirmUploaded(vodClient, assetResponse);
            System.out.println("创建媒资结束 assetId:" + assetResponse.getAssetId());
            return assetResponse.getAssetId();
        } catch (ServiceResponseException e) {
            e.printStackTrace();
            System.out.println(String.valueOf(e.getHttpStatusCode()));
            System.out.println(e.getRequestId());
            System.out.println(e.getErrorCode());
            System.out.println(e.getErrorMsg());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 校验文件路径和文件
     * @param filePath
     */
    private File validFile(String filePath){
        if (StringUtils.isEmpty(filePath)) {
            throw new RuntimeException("输入的文件路径为空！");
        }
        File file = new File(filePath);
        if (!file.exists()) {
            throw new RuntimeException("文件不存在！");
        } else if (file.isDirectory()) {
            try {
                throw new RuntimeException(file.getCanonicalPath() + "这是一个目录！");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return file;
    }


    /**
     * 1.构建鉴权
     * @return
     */
    public VodClient createVodClient(){
        ICredential auth = new BasicCredentials()
                .withAk(AK)
                .withSk(SK)
                ;
        return VodClient.newBuilder()
                .withCredential(auth)
                .withRegion(VodRegion.valueOf(REGION_NORTH4))
                .build();
    }

    /**
     * 2.创建媒资
     * @param client
     * @param reqbody
     * @return
     */
    public CreateAssetByFileUploadResponse createAssetByFileUpload(VodClient client, CreateAssetByFileUploadReq reqbody){
        System.out.println("createAssetByFileUpload start");
        CreateAssetByFileUploadRequest request = new CreateAssetByFileUploadRequest();
        // 设置 X-Sdk-Date参数，ak/sk认证必传
        request.setXSdkDate(getXSdkDate());
        // 设置上传参数
        request.withBody(reqbody);

        // 调用创建媒资
        CreateAssetByFileUploadResponse response = client.createAssetByFileUpload(request);
        System.out.println("createAssetByFileUpload end; createAssetResponse:" + response.toString());
        return response;
    }

    /**
     * 构建创建媒资请求参数
     * @param fileName
     * @param videoName
     * @param title
     * @return
     */
    public CreateAssetByFileUploadReq buildFileUploadReq(String fileName, String videoType, String videoName, String title){
        CreateAssetByFileUploadReq req = new CreateAssetByFileUploadReq();
        req.withVideoName(StringUtils.isNotEmpty(videoName) ? videoName : fileName);
        req.withTitle(StringUtils.isNotEmpty(title) ? title : fileName);
        // 设置媒资类型
        req.withVideoType(videoType);
        return req;
    }

    /**
     * 取 X-Sdk-Date 参数，当前UTC时间， 时间格式 yyyyHHddTHHmmssZ 例如： 20240312T092514Z
     * @return
     */
    public String getXSdkDate(){
        TimeZone zone = TimeZone.getTimeZone("UTC");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
        return LocalDateTime.now(zone.toZoneId()).format(formatter);
    }

    /**
     * 3.获取初始化上传任务授权
     * @param client
     * @param assetResponse
     */
    public ShowAssetTempAuthorityResponse initPartUploadAuthority(VodClient client, CreateAssetByFileUploadResponse assetResponse, String fileContentType){
        System.out.println("获取初始化上传任务授权 initPartUploadAuthority start");
        ShowAssetTempAuthorityRequest request = new ShowAssetTempAuthorityRequest();
        request.setXSdkDate(getXSdkDate());
        // 设置参数
        request.withHttpVerb("POST");
        request.withBucket(assetResponse.getTarget().getBucket());
        request.withObjectKey(assetResponse.getTarget().getObject());
        request.withContentType(fileContentType);
        // 发送初始化请求
        ShowAssetTempAuthorityResponse response = client.showAssetTempAuthority(request);
        System.out.println("获取初始化上传任务授权 initPartUploadAuthority end; response: " + response.toString());
        return response;
    }

    /**
     * 4.初始化分段上传
     * @param signStr
     * @param contentType
     * @return
     */
    public String initPartUpload(String signStr, String contentType) throws DocumentException {
        System.out.println("初始化分段上传 initPartUpload start");
        HttpResponse response = HttpRequest.post(signStr).header("Content-type",contentType).execute();
        System.out.println(response.body());
        Document document = DocumentHelper.parseText(response.body());
        Element root = document.getRootElement();
        Element u = root.element("UploadId");
        System.out.println("初始化分段上传 initPartUpload end; UploadId:" + u.getText());
        return u.getText();
    }

    /**
     * 5.获取分段上传授权
     * @param client
     * @param fileContentType
     * @param assetResponse
     * @param contentMd5
     * @param uploadId
     * @param partNumber
     * @return
     */
    public ShowAssetTempAuthorityResponse getPartUploadAuthority(VodClient client, String fileContentType, CreateAssetByFileUploadResponse assetResponse, String contentMd5, String uploadId, int partNumber) {
        System.out.println("获取分段上传授权 getPartUploadAuthority start; partNumber: " + partNumber);
        ShowAssetTempAuthorityRequest request = new ShowAssetTempAuthorityRequest();
        request.setXSdkDate(getXSdkDate());
        request.withHttpVerb("PUT");
        request.withBucket(assetResponse.getTarget().getBucket());
        request.withObjectKey(assetResponse.getTarget().getObject());
        request.withContentType(fileContentType);
        // contentMd5有特殊字符例如'+','=','/'等等，需要转义，此处因sdk会自动转义，所以没有转义contentMd5
        // java 可以这么写转义 URLEncoder.encode(contentMd5, "UTF-8");
        request.withContentMd5(contentMd5);
        request.withUploadId(uploadId);
        request.withPartNumber(partNumber);
        ShowAssetTempAuthorityResponse response = client.showAssetTempAuthority(request);
        System.out.println("获取分段上传授权 getPartUploadAuthority end; partNumber: " + partNumber + "; response" + response.toString());
        return response;
    }

    /**
     * 6.上传分段
     * @param signStr
     * @param fileByte
     * @param contentMd5
     */
    public void uploadPartFile(String signStr, byte[] fileByte, String contentMd5) {
        System.out.println("上传分段 uploadPartFile start");
        HttpResponse response = HttpRequest.put(signStr)
                // 此处contentMd5不需要转义
                .header("Content-MD5", contentMd5)
                .header("Content-Type", "application/octet-stream")
                .body(fileByte).execute();
        System.out.println(response.toString());
        if (response.getStatus() != 200) {
            throw new RuntimeException("上传分段 uploadPartFile end; 上传失败！");
        }
        System.out.println("上传分段 uploadPartFile end");
    }

    /**
     * 8.获取列举已上传段的授权
     * @param client
     * @param assetResponse
     * @param uploadId
     * @return
     */
    public ShowAssetTempAuthorityResponse listUploadedPartAuthority(VodClient client, CreateAssetByFileUploadResponse assetResponse, String uploadId){
        System.out.println("获取列举已上传段的授权 listUploadedPartAuthority start");
        ShowAssetTempAuthorityRequest request = new ShowAssetTempAuthorityRequest();
        request.setXSdkDate(getXSdkDate());
        request.withHttpVerb("GET");
        request.withBucket(assetResponse.getTarget().getBucket());
        request.withObjectKey(assetResponse.getTarget().getObject());
        request.withUploadId(uploadId);
        ShowAssetTempAuthorityResponse response = client.showAssetTempAuthority(request);
        System.out.println("获取列举已上传段的授权 listUploadedPartAuthority end; response: " + response.toString());
        return response;
    }

    /**
     * 9.查询已上传的分段
     * @param signStr
     * @return
     */
    public String listUploadedPart(String signStr) throws DocumentException {
        System.out.println("查询已上传的分段 listUploadedPart start");
        // 列举分段
        HttpResponse response = HttpRequest.get(signStr).execute();
        System.out.println("listUploadedPartResponse:" + response.body());

        Document responseDocument = DocumentHelper.parseText(response.body());
        Element rootResponse = responseDocument.getRootElement();
        List<Element> elementsResponse = rootResponse.elements("Part");
        // 组装参数
        Element mergerRoot = DocumentHelper.createElement("CompleteMultipartUpload");
        for(Element e : elementsResponse) {
            Element te = DocumentHelper.createElement("Part");
            te.add(e.element("PartNumber").createCopy());
            te.add(e.element("ETag").createCopy());
            mergerRoot.add(te);
        }
        System.out.println(mergerRoot.asXML());
        System.out.println("查询已上传的分段 listUploadedPart end");
        return mergerRoot.asXML();
    }

    /**
     * 10.获取合并段授权
     * @param client
     * @param assetResponse
     * @param uploadId
     * @return
     */
    public ShowAssetTempAuthorityResponse mergeUploadedPartAuthority(VodClient client, CreateAssetByFileUploadResponse assetResponse, String uploadId) {
        System.out.println("获取合并段授权 mergeUploadedPartAuthority start");
        ShowAssetTempAuthorityRequest request = new ShowAssetTempAuthorityRequest();
        request.setXSdkDate(getXSdkDate());
        request.withHttpVerb("POST");
        request.withBucket(assetResponse.getTarget().getBucket());
        request.withObjectKey(assetResponse.getTarget().getObject());
        request.withUploadId(uploadId);
        ShowAssetTempAuthorityResponse response = client.showAssetTempAuthority(request);
        System.out.println("获取合并段授权 mergeUploadedPartAuthority end; response: " + response.toString());
        return response;
    }

    /**
     * 11.合并分段
     */
    public void mergeUploadedPart(String signStr, String partInfo) {
        System.out.println("合并分段 mergeUploadedPart start");
        // 请求消息头中增加“Content-Type”，值设置为“application/xml”。
        HttpResponse response = HttpRequest.post(signStr)
                .header("Content-Type","application/xml")
                .body(partInfo)
                .execute();
        System.out.println(response.toString());
        if (response.getStatus() != 200) {
            throw new RuntimeException("合并分段 mergeUploadedPart end; 合并段失败!");
        }
        System.out.println("合并分段 mergeUploadedPart end");
    }

    /**
     * 12.确认上传完成
     */
    public void confirmUploaded(VodClient client, CreateAssetByFileUploadResponse assetResponse) {
        System.out.println("确认上传完成 confirmUploaded start");
        ConfirmAssetUploadRequest request = new ConfirmAssetUploadRequest();
        request.setXSdkDate(getXSdkDate());
        ConfirmAssetUploadReq body = new ConfirmAssetUploadReq();
        body.withStatus(ConfirmAssetUploadReq.StatusEnum.fromValue("CREATED"));
        body.withAssetId(assetResponse.getAssetId());
        request.withBody(body);
        ConfirmAssetUploadResponse response = client.confirmAssetUpload(request);
        System.out.println("上传完成，assetId:" + response.toString());
    }


    public BaseInfo getAssetDetail(VodClient client,String assetId){
        // 获取视频资产详情
        ShowAssetDetailRequest detailRequest = new ShowAssetDetailRequest().withAssetId(assetId);
        detailRequest.withXSdkDate(getXSdkDate());
        ShowAssetDetailResponse detailResponse = client.showAssetDetail(detailRequest);

        // 检查播放信息并返回URL
        if (detailResponse != null && detailResponse.getBaseInfo() != null) {
            return detailResponse.getBaseInfo();
        } else {
            throw new RuntimeException("获取视频详情失败，视频详情为空");
        }
    }



}
