package com.fdkankan.filestorage.minio;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.aliyun.oss.HttpMethod;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.fdkankan.filestorage.Consumer;
import com.fdkankan.filestorage.InnerUtils;
import com.fdkankan.filestorage.properties.MinioProperties;

import com.fdkankan.filestorage.FileStorageTemplate;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.COSObject;
import com.qcloud.cos.model.GeneratePresignedUrlRequest;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.ObjectUtils;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 阿里云操作模版类, 简化常见操作
 */
@Getter
@Slf4j
@Service("MinioOssTemplate")
@ConditionalOnProperty(prefix = MinioProperties.PREFIX,name = "filestorage.active",havingValue = "minio")
public class MinioTemplate implements FileStorageTemplate {

    private final MinioClient minioClient;
    private final MinioProperties minioProperties;
    @Override
    public String getActive() {
        return this.minioProperties.getActive();
    }
    public MinioTemplate(MinioClient minioClient, MinioProperties minioProperties) {
        this.minioClient = minioClient;
        this.minioProperties = minioProperties;
    }

    @Override
    public String uploadFile(String pathKey, String filePath) {
        return uploadFile(minioProperties.getBucket(), pathKey, filePath);
    }

    @Override
    public String uploadFile(String pathKey, String filePath, Map<String, String> headers) {
        return uploadFile(minioProperties.getBucket(), pathKey, filePath,headers);
    }

    @Override
    public String uploadFile(String bucket, String pathKey, String filePath) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        uploadFileStream(bucket, pathKey, FileUtil.getInputStream(new File(filePath)));
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String uploadFile(String bucket, String pathKey, String filePath, Map<String, String> headers) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        BufferedInputStream stream =null;
        try {
            stream=FileUtil.getInputStream(new File(filePath));
            PutObjectArgs objectArgs = PutObjectArgs.builder().object(pathKey)
                    .bucket(bucket)
                    .headers(headers)
                    .stream(stream, stream.available(), -1).build();
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(objectArgs);
            log.info(objectWriteResponse.object());

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String uploadFileText(String pathKey, String content) {
        return uploadFileText(minioProperties.getBucket(), pathKey, content);
    }

    @Override
    public String uploadFileText(String bucket, String pathKey, String content) {
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
        return uploadFileBytes(bucket, pathKey, bytes);
    }

    @Override
    public String uploadFileBase64Image(String pathKey, String content) {
        return uploadFileBase64Image(minioProperties.getBucket(), pathKey, content);
    }

    @Override
    public String uploadFileBase64Image(String bucket, String pathKey, String content) {
        byte[] bytes = Base64Utils.decodeFromString(content);
        return uploadFileBytes(bucket, pathKey, bytes);
    }

    @Override
    public String uploadFileBytes(String pathKey, byte[] bytes) {
        return uploadFileBytes(minioProperties.getBucket(), pathKey, bytes);
    }

    public void createbucket() {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(minioProperties.getBucket()).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public String uploadFileBytes(String bucket, String pathKey, byte[] bytes) {
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        return uploadFileStream(bucket, pathKey, stream);
    }

    @Override
    public String uploadFileStream(String pathKey, InputStream stream) {
        return uploadFileStream(minioProperties.getBucket(), pathKey, stream);
    }

    @Override
    public String uploadFileStream(String bucket, String pathKey, InputStream stream) {

        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        InnerUtils.checkArgument(stream != null, "stream can't be null");
        try {
            String contentType = "application/octet-stream";
            if (pathKey.contains(".jpg")) {
                contentType = ("image/jpeg");
            } else if (pathKey.contains(".png")) {
                contentType = ("image/png");
            } else if (pathKey.contains(".json")) {
                contentType = ("application/json");
            }
            PutObjectArgs objectArgs = PutObjectArgs.builder().object(pathKey)
                    .bucket(bucket)
                    .contentType(contentType)
                    .stream(stream, stream.available(), -1).build();
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(objectArgs);
            log.info(objectWriteResponse.object());

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public File downloadFileTmp(String pathKey) {
        return downloadFileTmp(minioProperties.getBucket(), pathKey);
    }

    @Override
    public File downloadFileTmp(String bucket, String pathKey) {
        String tmpDir = System.getProperty("java.io.tmpdir");
        String file = tmpDir + UUID.randomUUID();
        downloadFile(bucket, pathKey, file);
        return new File(file);
    }

    @Override
    public GetObjectResponse downloadFile(String pathKey, String file) {
        return downloadFile(minioProperties.getBucket(), pathKey, file);
    }

    @Override
    public GetObjectResponse downloadFile(String bucket, String pathKey, String file) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        InnerUtils.checkArgument(file != null && file.length() > 0, "file can't be empty");


        log.info("下载开始：下载bucket={},下载pathKey={},下载filePath={}", bucket, pathKey, file);
        try {
            File downloadFile = new File(file);
            if (!FileUtil.exist(downloadFile.getParent())){
                FileUtil.mkdir(downloadFile.getParent());
            }
            DownloadObjectArgs objectArgs = DownloadObjectArgs.builder().object(pathKey)
                    .bucket(bucket).filename(file).build();
            minioClient.downloadObject(objectArgs);
            return null;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public GetObjectResponse downloadFile(String pathKey, Consumer<InputStream> handler) {
        return downloadFile(minioProperties.getBucket(), pathKey, handler);
    }

    @Override
    public GetObjectResponse downloadFile(String bucket, String pathKey, Consumer<InputStream> handler) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        InnerUtils.checkArgument(handler != null, "handler can't be null");
        GetObjectArgs objectArgs = GetObjectArgs.builder().object(pathKey)
                .bucket(bucket).build();
        InputStream in=null;
        try {
            in = minioClient.getObject(objectArgs);
            handler.accept(in);
            return null;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(in!=null){
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return null;
    }

    public String calculateUrl(String pathKey) {
        return calculateUrl(minioProperties.getBucket(), pathKey);
    }

    public String calculateUrl(String bucket, String pathKey) {
        String host = minioProperties.getHostByBucket(bucket);
        return minioProperties.isFullPath()?host + "/" + pathKey:pathKey;
    }
    @Override
    public String getHostByBucket(String bucket) {
        return minioProperties.getHostByBucket(bucket);
    }
    public String calculatePathKey(String url) {
        String urlPath = InnerUtils.getUrlPath(url);
        if (urlPath != null && urlPath.startsWith("/")) {
            String pathKey = urlPath.substring(1);
            if (pathKey.length() > 0) {
                return InnerUtils.decodeUrl(pathKey);
            }
        }
        return null;
    }

    public String calculateHost(String endpoint, String bucket) {
        return InnerUtils.generateHost(endpoint, bucket);
    }


    @Override
    public Boolean copyObject(String oldPath, String newPath) {
        return copyObject(minioProperties.getBucket(), oldPath, minioProperties.getBucket(), newPath);
    }

    @Override
    public Boolean copyObject(String bucket, String oldPath, String toBucket, String newPath) {
        try {
            ObjectWriteResponse response = minioClient.copyObject(CopyObjectArgs.builder()
                    .source(CopySource.builder().bucket(bucket).object(oldPath).build())
                    .bucket(toBucket)
                    .object(newPath)
                    .build());
            if (StrUtil.isNotBlank(response.versionId())) {
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("文件不存在,KeyName{}", e.getMessage());
            return false;
        }
    }

    @Override
    public void deleteObject(String keyName) {
        if (doesObjectExist(keyName)) {
            deleteObject(minioProperties.getBucket(), keyName);
        } else {
            log.warn("文件不存在,KeyName{}", keyName);
        }
    }

    @Override
    public void deleteObject(String bucket, String keyName) {
        try {
            RemoveObjectArgs objectArgs = RemoveObjectArgs.builder().object(keyName)
                    .bucket(bucket).build();
            minioClient.removeObject(objectArgs);
        } catch (Exception e) {
            log.info("文件删除失败" + e.getMessage());
        }
    }

    @Override
    public List<String> getFileFolder(String keyName) {
        return getFileFolder(minioProperties.getBucket(), keyName);
    }

    @Override
    public List<String> getFileFolder(String bucket, String keyName) {
        List<String> list = new ArrayList<>();
        try {
            ListObjectsArgs objectArgs = ListObjectsArgs.builder()
                    .bucket(bucket).recursive(true).prefix(keyName + "/").build();
            Iterator<Result<Item>> iterator = minioClient.listObjects(objectArgs).iterator();
            while (iterator.hasNext()){
                Item item = iterator.next().get();
                list.add(item.objectName());
            }
            log.info("获取文件夹集合={}", JSON.toJSONString(list));
        } catch (Exception e) {
            log.error("获取文件夹集合失败={}", e.getMessage());
        }

        return list;
    }

    @Override
    public boolean doesObjectExist(String bucket, String keyName) {
        if("/".equals(keyName.substring(0,1))){
            keyName = keyName.substring(1);
        }
        GetObjectArgs getArgs = GetObjectArgs.builder()
                .bucket(bucket)
                .object(keyName)
                .build();
        InputStream in=null;

        try {
            in = minioClient.getObject(getArgs);
            return true;
        } catch (ErrorResponseException e) {
            if (e.errorResponse().code().equals("NoSuchKey")) {
                return false;
            }
            log.error("NoSuchKey", e.getMessage());
        } catch (Exception e) {
            log.error("连接异常", e.getMessage());
            return false;
        }finally {
            if(in!=null){
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return false;
    }
    @Override
    public String getBucket() {
        return minioProperties.getBucket();
    }

    @Override
    public boolean ossDownloadFileToLocal(String bucket,String keyName, String localPath) {
        try {
            minioClient.downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(bucket)
                            .object(keyName)
                            .filename(localPath)
                            .build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    @Override
    public boolean ossDownloadFileToLocal(String path, String localPath) {
        return ossDownloadFileToLocal(minioProperties.getBucket(),path,localPath);
    }

    @Override
    public boolean doesObjectExist(String keyName) {
        return doesObjectExist(minioProperties.getBucket(), keyName);
    }

    @Override
    public String uploadFileImage(String pathKey, String filePath) {
        return uploadFileImage(minioProperties.getBucket(), pathKey, filePath);
    }

    @Override
    public String uploadFileImage(String bucket, String pathKey, String filePath) {
        InputStream stream = null;
        try {
            String contentType = "application/octet-stream";
            if (filePath.contains(".jpg")) {
                contentType = ("image/jpeg");
            } else if (filePath.contains(".png")) {
                contentType = ("image/png");
            } else if (filePath.contains(".json")) {
                contentType = ("application/json");
            }
            stream = FileUtil.getInputStream(new File(filePath));
            PutObjectArgs objectArgs = PutObjectArgs.builder().object(pathKey)
                    .bucket(bucket)
                    .contentType(contentType)
                    .stream(stream, stream.available(), -1).build();
            log.info("上传开始：上传bucket={},上传pathKey={},上传filePath={}", bucket, pathKey, filePath);
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(objectArgs);
        } catch (Exception e) {
            return "";
        } finally {
            IoUtil.close(stream);
        }
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String getFileContent(String bucket, String keyName) throws Exception {
        BufferedReader reader =null;
        InputStreamReader in =null;
        try {
            GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(keyName).build());
            in = new InputStreamReader(object);
            reader = new BufferedReader(in);
            StringBuilder contentJson = new StringBuilder();
            while (true) {
                String line = reader.readLine();
                if (line == null) break;
                contentJson.append(line);
            }
            return contentJson.toString();
        } catch (Exception e) {
            throw e;
        }finally {
            IoUtil.close(in);
            IoUtil.close(reader);
        }
    }

    @Override
    public String getFileContent(String keyName) throws Exception {
        return this.getFileContent(this.getBucket(), keyName);
    }

    @Override
    public Long getSpace(String bucket, String key) {
        try {
            List<String> list = new ArrayList<>();
            Long total = 0L;
            ListObjectsArgs objectArgs = ListObjectsArgs.builder()
                    .bucket(bucket).recursive(true).prefix(key).build();
            Iterator<Result<Item>> iterator = minioClient.listObjects(objectArgs).iterator();
            while (iterator.hasNext()){
                Item item = iterator.next().get();
                Long space =item.size();
                total += space;
            }
            log.info("获取文件夹大小={}", total);
            return total;
        } catch (Exception e) {
            log.error("获取文件夹大小失败={}", e.getMessage());
        }
        return 0L;
    }


    @Override
    public Long getSpace(String key) {
        return  getSpace(minioProperties.getBucket(), key);
    }
    @SneakyThrows
    @Override
    public String getInternalEndpoint(String bucket ,String key) {
        return minioProperties.getInternalEndpoint()+"/"+bucket+"/"+key;
    }

    @Override
    @SneakyThrows
    public String getInternalEndpoint(String key) {
        return getInternalEndpoint(minioProperties.getBucket(), key);
    }
    @Override
    public void copyFolder(String sourcePath, String targetPath) {
        copyFolder(minioProperties.getBucket(),   sourcePath,  minioProperties.getBucket(),   targetPath);
    }
    @Override
    public void copyFolder(String sourceBucketName, String sourcePath, String targetBucketName, String targetPath) {
        try {
            List<String> files = this.getFileFolder(sourceBucketName, sourcePath);
            if (ObjectUtils.isEmpty(files)) {
                return;
            }
            files.parallelStream().forEach(file -> {
                this.copyObject(sourceBucketName, file, targetBucketName, file.replace(sourcePath, targetPath));
            });
        } catch (Exception e) {
            log.error("列举文件目录失败,key:" + sourcePath, e);
        }
    }

    @Override
    public URL getPresignedUrl(String bucket, String key) {
        GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
                .method(Method.PUT)
                .bucket(bucket)
                .object(key)
                .expiry(8, TimeUnit.HOURS) //过期时间
                //.extraQueryParams(reqParams)
                .build();
        try {
            return new URL(minioClient.getPresignedObjectUrl(build));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public URL getPresignedUrl(String key) {
        return getPresignedUrl(minioProperties.getBucket(), key);
    }
}
