package cn.reghao.oss.sdk;

import cn.reghao.jutil.jdk.result.WebResult;
import cn.reghao.jutil.jdk.serializer.JsonConverter;
import cn.reghao.oss.sdk.model.OssProperties;
import cn.reghao.oss.storeapi.dto.ObjectChannel;
import cn.reghao.oss.storeapi.dto.ObjectInfo;
import cn.reghao.oss.storeapi.dto.ServerInfo;
import cn.reghao.oss.storeapi.dto.StoreProperties;
import cn.reghao.oss.storeapi.dto.media.AudioInfo;
import cn.reghao.oss.storeapi.dto.media.ConvertedImageInfo;
import cn.reghao.oss.storeapi.dto.media.ImageInfo;
import cn.reghao.oss.storeapi.dto.media.VideoInfo;
import cn.reghao.oss.storeapi.rest.UploadFileRet;
import com.google.gson.reflect.TypeToken;

import java.io.File;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author reghao
 * @date 2024-02-23 21:47:39
 */
public class OssConsoleClient {
    private final HttpClient httpClient = HttpClient.newBuilder().build();
    private final String endpoint;
    private String token;

    public OssConsoleClient(OssProperties ossProperties) throws Exception {
        this.endpoint = ossProperties.getConsoleUrl();
        auth(ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
    }

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

    // ****************************************************************************************************************
    // oss-console 认证接口
    // ****************************************************************************************************************
    private void auth(String accessKeyId, String accessKeySecret) throws Exception {
        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher()
                .addPart("accessKeyId", accessKeyId)
                .addPart("accessKeySecret", accessKeySecret);

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

        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }

        this.token = webResult.getData();
    }

    // ****************************************************************************************************************
    // oss-store 调用的接口(不需要认证)
    // ****************************************************************************************************************
    public void registerNode(String jsonPayload) throws Exception {
        String api = String.format("%s/api/oss/store/register", endpoint);
        HttpClient httpClient = HttpClient.newBuilder().build();
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("content-type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }
    }

    public List<ObjectChannel> getObjectChannels(String nodeAddr) throws Exception {
        String api = String.format("%s/api/oss/store/channels?nodeAddr=%s", endpoint, nodeAddr);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<List<ObjectChannel>>>(){}.getType();
        WebResult<List<ObjectChannel>> 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 ObjectChannel getObjectChannel(int channelId) throws Exception {
        String api = String.format("%s/api/oss/server/channel?channelId=%s", endpoint, channelId);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<ObjectChannel>>(){}.getType();
        WebResult<ObjectChannel> 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 StoreProperties getStoreProperties(String nodeAddr) throws Exception {
        String api = String.format("%s/api/oss/store/properties?nodeAddr=%s", endpoint, nodeAddr);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<StoreProperties>>(){}.getType();
        WebResult<StoreProperties> 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();
    }

    // ****************************************************************************************************************
    // oss-store 相关接口
    // ****************************************************************************************************************
    public ServerInfo getServerInfo(int channelId) throws Exception {
        String api = String.format("%s/api/oss/server/info?channelId=%s", endpoint, channelId);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<ServerInfo>>(){}.getType();
        WebResult<ServerInfo> 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();
    }

    // ****************************************************************************************************************
    // Object 相关接口
    // ****************************************************************************************************************
    public void setObjectsScope(int channelId, List<String> objectIds, int scope) throws Exception {
        String objectIdsStr = objectIds.toString().replace("[", "").replace("]", "");
        Map<String, String> formData = new HashMap<>();
        formData.put("channelId", channelId+"");
        formData.put("scope", scope+"");
        formData.put("objectIds", objectIdsStr);

        String api = String.format("%s/api/oss/object/scope/objects", endpoint);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .headers("content-type", "application/x-www-form-urlencoded")
                .version(HttpClient.Version.HTTP_1_1)
                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }
    }

    private String getFormDataAsString(Map<String, String> formData) {
        StringBuilder formBodyBuilder = new StringBuilder();
        for (Map.Entry<String, String> singleEntry : formData.entrySet()) {
            if (formBodyBuilder.length() > 0) {
                formBodyBuilder.append("&");
            }
            formBodyBuilder.append(URLEncoder.encode(singleEntry.getKey(), StandardCharsets.UTF_8));
            formBodyBuilder.append("=");
            formBodyBuilder.append(URLEncoder.encode(singleEntry.getValue(), StandardCharsets.UTF_8));
        }
        return formBodyBuilder.toString();
    }

    public void deleteByObjectIds(int channelId, List<String> objectIds) throws Exception {
        String objectIdsStr = objectIds.toString().replace("[", "").replace("]", "");
        Map<String, String> formData = new HashMap<>();
        formData.put("channelId", channelId+"");
        formData.put("objectIds", objectIdsStr);

        String api = String.format("%s/api/oss/object/delete/ids", endpoint);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .headers("content-type", "application/x-www-form-urlencoded")
                .version(HttpClient.Version.HTTP_1_1)
                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }
    }

    public void deleteByObjectUrl(String objectUrl) throws Exception {
        Map<String, String> formData = new HashMap<>();
        formData.put("objectUrl", objectUrl);

        String api = String.format("%s/api/oss/object/delete/url", endpoint);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .headers("content-type", "application/x-www-form-urlencoded")
                .version(HttpClient.Version.HTTP_1_1)
                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }
    }

    public String getSignedUrl(int channelId, String objectId) throws Exception {
        String api = String.format("%s/api/oss/object/url?channelId=%s&objectId=%s", endpoint, channelId, objectId);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> 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 ObjectInfo getObjectInfo(int channelId, String objectId) throws Exception {
        String api = String.format("%s/api/oss/object/info?channelId=%s&objectId=%s", endpoint, channelId, objectId);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<ObjectInfo>>(){}.getType();
        WebResult<ObjectInfo> 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 VideoInfo getVideoInfo(int channelId, String videoFileId) throws Exception {
        String api = String.format("%s/api/oss/media/video/info?channelId=%s&videoFileId=%s", endpoint, channelId, videoFileId);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<VideoInfo>>(){}.getType();
        WebResult<VideoInfo> 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 List<ImageInfo> getImagesInfo(int channelId, List<String> imageFileIds) throws Exception {
        String imageFileIdsStr = imageFileIds.toString().replace("[", "").replace("]", "");
        Map<String, String> formData = new HashMap<>();
        formData.put("channelId", ""+channelId);
        formData.put("imageFileIds", imageFileIdsStr);

        String api = String.format("%s/api/oss/media/image/info", endpoint);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .headers("content-type", "application/x-www-form-urlencoded")
                .version(HttpClient.Version.HTTP_1_1)
                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<List<ImageInfo>>>(){}.getType();
        WebResult<List<ImageInfo>> 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 List<ConvertedImageInfo> getWebpInfo(int channelId, List<String> imageFileIds) throws Exception {
        String imageFileIdsStr = imageFileIds.toString().replace("[", "").replace("]", "");
        Map<String, String> formData = new HashMap<>();
        formData.put("channelId", ""+channelId);
        formData.put("imageFileIds", imageFileIdsStr);

        String api = String.format("%s/api/oss/media/image/webp", endpoint);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .headers("content-type", "application/x-www-form-urlencoded")
                .version(HttpClient.Version.HTTP_1_1)
                .POST(HttpRequest.BodyPublishers.ofString(getFormDataAsString(formData)))
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<List<ConvertedImageInfo>>>(){}.getType();
        WebResult<List<ConvertedImageInfo>> 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 AudioInfo getAudioInfo(int channelId, String audioFileId) throws Exception {
        String api = String.format("%s/api/oss/media/audio/info/?channelId=%s&audioFileId=%s", endpoint, channelId, audioFileId);
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .GET()
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<AudioInfo>>(){}.getType();
        WebResult<AudioInfo> 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 convertAudio(String audioFileId) throws Exception {
        String api = String.format("%s/api/oss/media/convert/audio/%s", endpoint, audioFileId);
        convert(api);
    }

    public void convertVideo(String videoFileId) throws Exception {
        String api = String.format("%s/api/oss/media/convert/video/%s", endpoint, videoFileId);
        convert(api);
    }

    private void convert(String api) throws Exception {
        MultiPartBodyPublisher publisher = new MultiPartBodyPublisher();
        HttpRequest httpRequest = HttpRequest.newBuilder(new URI(api))
                .header("authorization", "Bearer " + token)
                .version(HttpClient.Version.HTTP_1_1)
                .POST(publisher.build())
                .build();
        HttpResponse<String> httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        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<String>>(){}.getType();
        WebResult<String> webResult = JsonConverter.jsonToObject(body, type);
        if (webResult.getCode() != 0) {
            String errMsg = String.format("%s - %s", webResult.getCode(), webResult.getMsg());
            throw new Exception(errMsg);
        }
    }

    public UploadFileRet uploadFile(int channelId, File file) throws Exception {
        ServerInfo serverInfo = getServerInfo(channelId);
        if (serverInfo == null) {
            throw new Exception("获取 server_info 失败");
        }
        String ossUrl = serverInfo.getOssUrl();
        String token = serverInfo.getToken();

        OssStoreClient ossStoreClient = new OssStoreClient(ossUrl);
        UploadFileRet uploadFileRet = ossStoreClient.postObjectWithJdkHttp(file, channelId, token);
        return uploadFileRet;
    }
}
