package cn.reghao.oss.sdk;

import cn.reghao.jutil.jdk.http.UploadParam;
import cn.reghao.jutil.jdk.http.WebResponse;
import cn.reghao.jutil.jdk.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.oss.storeapi.rest.UploadFileRet;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * @author reghao
 * @date 2023-06-02 16:19:04
 */
@Slf4j
public class OssStoreClient {
    private final String endpoint;
    private final HttpClient httpClient = HttpClient.newBuilder().build();
    private final DefaultWebRequest webRequest = new DefaultWebRequest();
    private final String baseDir = "/opt/tmp/";

    public OssStoreClient(String endpoint) {
        this.endpoint = endpoint;
    }

    public UploadFileRet putObject(File file, int channelId, int userId) throws Exception {
        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
        String api = String.format("%s/", endpoint);
        HttpRequest.Builder builder = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .header("x-content-sha256sum", sha256sum)
                .header("x-channel-id", channelId+"")
                .header("x-user-id", userId+"");

        HttpRequest httpRequest = builder.PUT(HttpRequest.BodyPublishers.ofFile(Path.of(file.getAbsolutePath()))).build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        return getResult(httpResponse);
    }

    public UploadFileRet putObjectWithJdkHttp(InputStream inputStream, int channelId, int userId) throws Exception {
        String api = String.format("%s/", endpoint);
        HttpRequest.Builder builder = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .header("x-content-sha256sum", "1234567890")
                .header("x-channel-id", channelId+"")
                .header("x-user-id", userId+"");

        BufferedInputStream bis = new BufferedInputStream(inputStream);
        Supplier<? extends InputStream> streamSupplier = (Supplier<BufferedInputStream>) () -> bis;
        HttpRequest httpRequest = builder.PUT(HttpRequest.BodyPublishers.ofInputStream(streamSupplier)).build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        return getResult(httpResponse);
    }

    public UploadFileRet postObjectWithJdkHttp(File file, int channelId, String token) throws Exception {
        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher()
                .addPart("file", Paths.get(file.getAbsolutePath()))
                .addPart("client", "oss-sdk-1.0")
                .addPart("sha256sum", sha256sum)
                .addPart("channelId", channelId+"");

        String api = String.format("%s/", endpoint);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .header("authorization", "Bearer " + token)
                .header("content-type", "multipart/form-data; boundary=" + publisher.getBoundary())
                .POST(publisher.build())
                .build();

        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        return getResult(httpResponse);
    }

    public UploadFileRet postObject(File file, int channelId, String token) throws Exception {
        String sha256sum = DigestUtil.sha256sum(file.getAbsolutePath());
        Map<String, String> map = new HashMap<>();
        map.put("channelId", ""+channelId);
        map.put("client", "client");
        map.put("sha256sum", sha256sum);
        UploadParam uploadParam = new UploadParam(file, map);

        String api = String.format("%s/", endpoint);
        WebResponse webResponse = webRequest.upload(api, uploadParam, token);
        int statusCode = webResponse.getStatusCode();
        String body = webResponse.getBody();
        if (statusCode != 200) {
            String errMsg = String.format("%s -> %s", statusCode, body);
            throw new Exception(errMsg);
        }

        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            throw new Exception(webResult.getMsg());
        }

        return webResult.getData();
    }

    private UploadFileRet getResult(HttpResponse<String> httpResponse) throws Exception {
        int statusCode = httpResponse.statusCode();
        String body = httpResponse.body();
        if (statusCode != 200) {
            String errMsg = String.format("%s -> %s", statusCode, body);
            throw new Exception(errMsg);
        }

        Type type = new TypeToken<WebResult<UploadFileRet>>(){}.getType();
        WebResult<UploadFileRet> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }

        return webResult.getData();
    }

    public void headObject(String objectName) {
        try {
            String api = String.format("%s/%s", endpoint, objectName);
            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                    .version(HttpClient.Version.HTTP_1_1)
                    .method("HEAD", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean headObject1(String sha256sum) {
        try {
            String api = String.format("%s?sha256sum=%s", endpoint, sha256sum);
            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                    .version(HttpClient.Version.HTTP_1_1)
                    .method("HEAD", HttpRequest.BodyPublishers.noBody())
                    .build();

            HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
            int statusCode = httpResponse.statusCode();
            if (statusCode == 200) {
                return true;
            } else if (statusCode == 404) {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    public void getObject(String objectName, String filePath) {
        try {
            String version = "1.0.0";
            String api = String.format("%s/%s?client=%s", endpoint, objectName, version);
            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                    .version(HttpClient.Version.HTTP_1_1)
                    .GET()
                    .build();

            HttpResponse<InputStream> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofInputStream());
            int statusCode = httpResponse.statusCode();
            if (statusCode == 200) {
                String localPath = saveFile(filePath, httpResponse.body());
                log.info("saved to {}", localPath);
            } else {
                log.error("{}", statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String saveFile(String filePath, InputStream in) {
        File file = new File(filePath);
        File parentDir = file.getParentFile();
        try {
            if (!parentDir.exists()) {
                FileUtils.forceMkdir(parentDir);
            }

            FileOutputStream fos = new FileOutputStream(file);
            // 1MiB
            int len = 1024*1024*5;
            byte[] buf = new byte[len];
            int readLen;
            while ((readLen = in.read(buf, 0, len)) != -1) {
                fos.write(buf, 0, readLen);
            }
            fos.close();
            return filePath;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public void getObject(String objectName) {
        try {
            String version = "1.0.0";
            String api = String.format("%s/%s?client=%s", endpoint, objectName, version);
            HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                    .version(HttpClient.Version.HTTP_1_1)
                    .GET()
                    .build();

            HttpResponse<InputStream> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofInputStream());
            int statusCode = httpResponse.statusCode();
            if (statusCode == 200) {
                String localPath = saveFile(httpResponse.body(), objectName);
                log.info("saved to {}", localPath);
            } else {
                log.error("{}", statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String saveFile(InputStream in, String objectName) {
        String filePath = baseDir + objectName;
        File file = new File(filePath);
        File parentDir = file.getParentFile();
        try {
            if (!parentDir.exists()) {
                FileUtils.forceMkdir(parentDir);
            }

            FileOutputStream fos = new FileOutputStream(file);
            // 1MiB
            int len = 1024*1024*5;
            byte[] buf = new byte[len];
            int readLen;
            while ((readLen = in.read(buf, 0, len)) != -1) {
                fos.write(buf, 0, readLen);
            }
            fos.close();
            return filePath;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}
