package cn.reghao.tnb.oss.sdk;

import cn.reghao.jutil.jdk.http.UploadParam;
import cn.reghao.jutil.jdk.http.WebRequest;
import cn.reghao.jutil.jdk.http.WebResponse;
import cn.reghao.jutil.jdk.io.FileSplitter;
import cn.reghao.jutil.jdk.web.result.WebResult;
import cn.reghao.jutil.jdk.security.DigestUtil;
import cn.reghao.jutil.jdk.serializer.JsonConverter;
import cn.reghao.jutil.tool.http.DefaultWebRequest;
import cn.reghao.tnb.oss.api.rest.UploadFilePart;
import cn.reghao.tnb.oss.api.rest.UploadFileRet;
import cn.reghao.tnb.oss.api.rest.UploadedPart;
import com.google.gson.reflect.TypeToken;

import java.io.*;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author reghao
 * @date 2023-06-02 16:19:04
 */
public class ObjectMultipartUploadService {
    private final String endpoint;
    private final String token;
    // 10MB
    private final FileSplitter fileSplitter = new FileSplitter();
    private final HttpClient httpClient = HttpClient.newBuilder().build();
    private final WebRequest webRequest = new DefaultWebRequest();

    public ObjectMultipartUploadService(String endpoint, String token) {
        this.endpoint = endpoint;
        this.token = token;
    }

    /**
     * 获取已上传的分片文件
     *
     * @param
     * @return
     * @date 2024-10-29 15:43:46
     */
    private UploadedPart getUploadedParts(String sha256sum, int channelCode) throws Exception {
        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher();
        publisher.addPart("sha256sum", sha256sum);

        String api = endpoint + "/?multiparts";
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .header("Authorization", token)
                .GET()
                .build();

        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        String body = httpResponse.body();
        Type type = new TypeToken<WebResult<UploadedPart>>(){}.getType();
        WebResult<UploadedPart> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = webResult.getMsg();
            return null;
        }

        return webResult.getData();
    }

    /**
     * 分片上传大文件
     *
     * @param
     * @return
     * @date 2024-10-29 15:44:13
     */
    public UploadFileRet uploadFilePart(File file, int channelCode) throws Exception {
        String identifier = DigestUtil.sha256sum(new FileInputStream(file));
        String filename = file.getName();
        String relativePath = file.getAbsolutePath();

        long totalSize = file.length();
        // 分片大小在 10MB - 100MB 之间，只有最后一个分片才允许小于 10MB(这无法避免)
        long chunkSize = fileSplitter.getPartSize();
        long totalChunks = totalSize/chunkSize;
        if (totalSize % chunkSize != 0) {
            totalChunks += 1;
        }

        Map<Integer, Long> map = new HashMap<>();
        List<Integer> failedChunkNumbers = new ArrayList<>();
        for (int i = 0; i < totalChunks; i++) {
            long start = i*chunkSize;
            // 从 start 位置开始读取 chunkSize 的数据
            byte[] part = fileSplitter.getPart(file.getAbsolutePath(), start);
            int chunkNumber = i + 1;
            map.put(chunkNumber, start);

            int currentChunkSize = part.length;
            UploadFilePart uploadFilePart = new UploadFilePart(channelCode, identifier, filename, relativePath,
                    totalSize, chunkSize, totalChunks, chunkNumber, currentChunkSize);

            UploadFileRet uploadFileRet = postObject(part, uploadFilePart);
            if (uploadFileRet == null) {
                System.out.printf("%s:%s upload failed\n", chunkNumber, currentChunkSize);
                failedChunkNumbers.add(chunkNumber);
            } else {
                System.out.printf("%s:%s uploaded %s bytes\n", totalChunks, chunkNumber, currentChunkSize);
                if (uploadFileRet.isMerged()) {
                    return uploadFileRet;
                }
            }
        }

        // 重传上传失败的文件分片
        if (!failedChunkNumbers.isEmpty()) {
            for (int chunkNumber : failedChunkNumbers) {
                long start = map.get(chunkNumber);
                byte[] part = fileSplitter.getPart(file.getAbsolutePath(), start);
                int currentChunkSize = part.length;
                UploadFilePart uploadFilePart = new UploadFilePart(channelCode, identifier, filename, relativePath,
                        totalSize, chunkSize, totalChunks, chunkNumber, currentChunkSize);

                UploadFileRet uploadFileRet = postObject(part, uploadFilePart);
                if (uploadFileRet == null) {
                    System.out.printf("%s:%s upload failed\n", chunkNumber, currentChunkSize);
                } else {
                    System.out.printf("%s:%s uploaded %s bytes\n", totalChunks, chunkNumber, currentChunkSize);
                    if (uploadFileRet.isMerged()) {
                        return uploadFileRet;
                    }
                }
            }
        }

        return null;
    }

    /**
     * jdk http 实现
     * 存在 bug: https://bugs.openjdk.org/browse/JDK-8222968
     *
     * @param
     * @return
     * @date 2023-05-24 14:56:50
     */
    private void postObject1(byte[] bytes, UploadFilePart uploadFilePart)
            throws URISyntaxException, IOException, InterruptedException {
        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher();
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        publisher.addPart("file", () -> bais, uploadFilePart.getFilename(), "")
                //.addPart("file", Path.of(""))
                .addPart("channelCode", uploadFilePart.getChannelCode()+"")
                .addPart("identifier", uploadFilePart.getIdentifier())
                .addPart("filename", uploadFilePart.getFilename())
                .addPart("relativePath", uploadFilePart.getRelativePath())
                .addPart("totalSize", uploadFilePart.getTotalSize()+"")
                .addPart("chunkSize", uploadFilePart.getChunkSize()+"")
                .addPart("currentChunkSize", uploadFilePart.getCurrentChunkSize()+"")
                .addPart("totalChunks", uploadFilePart.getTotalChunks()+"")
                .addPart("chunkNumber", uploadFilePart.getChunkNumber()+"");

        String authorization = "";
        String api = endpoint + "/?multipart";
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .header("Authorization", authorization)
                .header("Content-Type", "multipart/form-data; boundary=" + publisher.getBoundary())
                .POST(publisher.build())
                .build();

        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        System.out.println();
    }

    private UploadFileRet postObject(byte[] bytes, UploadFilePart uploadFilePart) {
        UploadParam uploadParam = new UploadParam(bytes, "");
        Map<String, String> params = new HashMap<>();
        params.put("channelCode", uploadFilePart.getChannelCode()+"");
        params.put("identifier", uploadFilePart.getIdentifier());
        params.put("filename", uploadFilePart.getFilename());
        params.put("relativePath", uploadFilePart.getRelativePath());
        params.put("totalSize", uploadFilePart.getTotalSize()+"");
        params.put("chunkSize", uploadFilePart.getChunkSize()+"");
        params.put("currentChunkSize", uploadFilePart.getCurrentChunkSize()+"");
        params.put("totalChunks", uploadFilePart.getTotalChunks()+"");
        params.put("chunkNumber", uploadFilePart.getChunkNumber()+"");
        uploadParam.setTextParams(params);

        String api = endpoint + "/?multipart";
        WebResponse webResponse = webRequest.upload(api, uploadParam, token);
        int statusCode = webResponse.getStatusCode();
        if (statusCode != 200) {
            System.out.println("请求失败");
            return null;
        }

        String body = webResponse.getBody();
        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = webResult.getMsg();
            System.out.printf("请求失败 -> %s\n", errMsg);
            return null;
        }

        return webResult.getData();
    }
}
