package net.northking.cctp.common.s3.impl;

import io.minio.*;
import io.minio.messages.Item;
import net.northking.cctp.common.enums.FileBusinessTypeEnum;
import net.northking.cctp.common.s3.*;
import net.northking.cctp.common.util.UUIDUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

public class MinioFileStorage implements SimpleStorageService {
    private static final Logger logger = LoggerFactory.getLogger(MinioFileStorage.class);

    private AmazonS3Properties properties;

    private MinioClient client;
    private AttachmentProducer attachmentProducer;



    public MinioFileStorage(AmazonS3Properties properties, AttachmentProducer attachmentProducer) throws Exception{
        this.properties = properties;
        this.attachmentProducer = attachmentProducer;
        boolean tls = properties.isTls();
        String host = properties.getHost();
        int port = properties.getPort();
        String accessKey = properties.getAccessKey();
        String secretKey = properties.getSecretKey();

        client = MinioClient.builder().endpoint(host, port, tls)
                .credentials(accessKey, secretKey).build();
        logger.info("初始化Minio客户端：TLS={}, host={}, port={}, accessKey={}, secretKey={}, useMQ={}",
                tls, host, port, accessKey, secretKey, properties.isUseMsgQueue());
        logger.info("注册公共组件提示：统一配置 SimpleStorageService 使用 S3云存储服务,文件服务模式【minio】。");
        checkBucket(properties.getBucket());
    }


    private void checkBucket(String bucket) throws Exception
    {
        try
        {
            boolean bucketExists = client.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
            if (!bucketExists)
            {
                client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
            }
        } catch (Exception e)
        {
            throw new Exception("检查 Bucket 是否存在，当Bucket不存在时自动创建。bucket=" + bucket, e);
        }
    }

    public NKFile upload(String tenant, MultipartFile multipartFile, String objId, FileBusinessTypeEnum businessCode) throws FileUploadException, IOException
    {
        S3File s3File = new S3File(properties.getBucket(), tenant, multipartFile, objId, businessCode);
        putObject(s3File, multipartFile.getInputStream());

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    public NKFile uploadChunk(String tenantId, MultipartFile multipartFile, String uploadId, String chunkIndex) throws FileUploadException, IOException
    {
        S3File s3File = new S3File(properties.getBucket(), tenantId, multipartFile, uploadId, chunkIndex);
        putObject(s3File, multipartFile.getInputStream());

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    public NKFile mergeChunkFiles(String tenantId, String uploadId, String suffixName) throws FileUploadException {
        S3File s3File = new S3File(properties.getBucket(), tenantId, suffixName);
        mergeChunkFiles(s3File, uploadId);

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    public NKFile upload(String tenant, File file, String objId, FileBusinessTypeEnum businessCode) throws FileUploadException
    {
        S3File s3File = new S3File(properties.getBucket(), tenant, file, objId, businessCode);
        uploadObject(s3File, file);

        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.process(s3File);
        }
        return s3File;
    }

    /**
     * 上传文件
     *
     * @param tenant
     * @param file
     * @param idDashFlag
     * @return
     * @throws FileUploadException
     */
    @Override
    public NKFile upload(String tenant, File file, boolean idDashFlag, String objId, FileBusinessTypeEnum businessCode) throws FileUploadException {
        NKFile uploadFile = upload(tenant, file, objId,businessCode);
        if (idDashFlag) {
            String id = uploadFile.getId();
            id = tenant + id.replace("/", "_");
            uploadFile.setId(id);
        }
        return uploadFile;
    }

    /**
     * 更新文件
     *
     * @param resourceUrl 文件id（tenantId_yyyy_mm_dd_id.xxx）
     * @param file   文件
     * @return
     */
    @Override
    public NKFile update(String resourceUrl, File file) throws FileUploadException {
        S3File s3File = new S3File();
        if (null != resourceUrl && file != null && resourceUrl.contains("_")) {
            // 判断是否同类型文件，不同类型不能覆盖
            int dotIdx = file.getName().lastIndexOf(".");
            boolean dotContain = resourceUrl.contains(".");
            String contentType = ContentType.findCode(null);  // 默认 Application_Octet_Stream
            // 对后缀进行判断
            if (dotContain && dotIdx > 0) { // 都有后缀
                String originType = resourceUrl.substring(resourceUrl.lastIndexOf(".") + 1);
                String fileType = file.getName().substring(dotIdx + 1);
                if (!originType.equalsIgnoreCase(fileType)) { // 后缀不一致
                    throw new FileUploadException(String.format("上传文件类型[%s]与原文件类型[%s]不一致，覆盖文件失败", fileType, originType));
                }
                contentType = ContentType.findCode(originType);
            } else if ((dotContain && dotIdx < 0) || (!dotContain && dotIdx > 0) ) {  // 一个有后缀，一个没有没有后缀
                throw new FileUploadException("上传文件类型与原文件类型不一致，覆盖文件失败");
            }
            String tenantId = resourceUrl.substring(0, resourceUrl.indexOf("_"));
            String temp = resourceUrl.replace("_", "/");
            String fileId = temp.substring(temp.indexOf("/"));
            s3File.setBucket(properties.getBucket());
            s3File.setId(fileId);
            s3File.setTenantId(tenantId);
            s3File.setContentType(contentType);
            try {
                client.uploadObject(UploadObjectArgs.builder()
                        .bucket(s3File.getBucket())
                        .object(tenantId + fileId)
                        .filename(file.getAbsolutePath())
                        .contentType(s3File.getContentType())
                        .build());
            } catch (Exception e) {
                String msg = "上传文件:" + file.getAbsolutePath() + "，发生异常." + s3File.toString();
                throw new FileUploadException(msg, e);
            }
        }
        return s3File;
    }

    @Override
    public InputStream downloadAsStream(String tenant, String fileId) throws FileDownloadException
    {
        String objectId = tenant + fileId;
        try
        {
            return client.getObject(
                    GetObjectArgs.builder()
                            .bucket(properties.getBucket())
                            .object(objectId)
                            .build());
        } catch (Exception e)
        {
            throw new FileDownloadException("下载文件流发生异常.文件ID=" + fileId, e);
        }
    }

    @Override
    public File downloadAsFile(String tenant, String fileId) throws FileDownloadException
    {
        File file = createTempFileName(tenant, fileId);
        String objectId = tenant + fileId;
        try
        {
            client.downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(properties.getBucket())
                            .object(objectId)
                            .filename(file.getAbsolutePath())
                            .build()
            );
            logger.debug("从Minio下载文件到本地：{} , 文件ID={}", file.getAbsolutePath(), fileId);
            return file;
        } catch (Exception e)
        {
            throw new FileDownloadException("下载文件发生异常. 文件ID=" + fileId, e);
        }
    }

    @Override
    public void getFileAndDirectResponseToHttp(ServletOutputStream out, String fileId) throws FileDownloadException {

    }

    private File createTempFileName(String tenant, String fileId)
    {
        int idx = fileId.lastIndexOf(".");
        String postfix = "";
        if (idx > 0)
        {
            postfix = fileId.substring(idx + 1);
        }
        SimpleDateFormat format = new SimpleDateFormat("/yyyy/MM/dd/");
        String datePath = format.format(new Date());
        String filePath = properties.getPath() + properties.getBucket() + "/" + tenant + datePath;
        File path = new File(filePath);
        path.mkdirs();
        String fileFullPath = filePath + UUIDUtil.create32UUID() + "." + postfix;
        return new File(fileFullPath);
    }

    private void uploadObject(S3File s3File, File file) throws FileUploadException
    {
        String objectId = s3File.getTenantId() + s3File.getId();
        try
        {
            client.uploadObject(UploadObjectArgs.builder()
                    .bucket(s3File.getBucket())
                    .object(objectId)
                    .filename(file.getAbsolutePath())
                    .contentType(s3File.getContentType())
                    .build());
            logger.debug("上传文件到云存储，" + s3File.toString());
        } catch (Exception e)
        {
            String msg = "上传文件:" + file.getAbsolutePath() + ",发生异常." + s3File.toString();
            throw new FileUploadException(msg, e);
        }
    }

    private void putObject(S3File s3File, InputStream inputStream) throws FileUploadException
    {
        String objectId = s3File.getTenantId() + s3File.getId();
        try (
                InputStream stream = inputStream
        )
        {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(s3File.getBucket())
                    .object(objectId)
                    .contentType(s3File.getContentType())
                    .stream(stream, s3File.getLength(), -1)
                    .build();
            client.putObject(putObjectArgs);
            logger.debug("上传文件到云存储，" + s3File.toString());
        } catch (Exception e)
        {
            throw new FileUploadException("上传文件到云存储发生异常。" + s3File.toString(), e);
        }
    }

    private void mergeChunkFiles(S3File s3File, String uploadId) throws FileUploadException
    {
        try {
            String objectId = s3File.getTenantId() + s3File.getId();
            // 根据uploadId获取所有的临时文件
            String tempObjectId = s3File.getTenantId()+ "/temp/" + uploadId + "/";
            List<String> folderList = getFolderList(tempObjectId);
            if(!folderList.isEmpty()){
                List<Integer> indexNoList=new ArrayList<>();
                Map<Integer,String> map=new HashMap<>();
                for(String fileName:folderList){
                    fileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8.name());
                    String[] split = fileName.split("/");
                    indexNoList.add(Integer.parseInt(split[split.length-1]));
                    map.put(Integer.parseInt(split[split.length-1]),fileName);
                }
                Collections.sort(indexNoList);
                List<String> fileNameLast=new ArrayList<>();
                for (int j = 0; j < indexNoList.size(); j++) {
                    fileNameLast.add(map.get(indexNoList.get(j)));
                }
                List<ComposeSource> composeSourceList = new ArrayList<>();
                for (String name : fileNameLast) {
                    composeSourceList.add(ComposeSource.builder().bucket(properties.getBucket()).object(name).build());
                }
                client.composeObject(
                        ComposeObjectArgs.builder()
                                .bucket(properties.getBucket())
                                .object(objectId)
                                .sources(composeSourceList)
                                .build()
                );
                // 删除临时文件夹中的内容
                for (String objectName : fileNameLast) {
                    try {
                        client.removeObject(RemoveObjectArgs.builder().bucket(properties.getBucket()).object(objectName).build());
                    }catch (Exception e){
                        logger.debug("删除临时文件异常:{}",e.getMessage());
                    }
                }
                logger.debug("上传文件到云存储，" + s3File.toString());
            }
        }catch (Exception e){
            throw new FileUploadException("上传文件到云存储发生异常。" + s3File.toString(), e);
        }
    }

    public List<String> getFolderList(String objectName) {
        List<String> result = new ArrayList<>();
        try {
            if (objectName.endsWith("/")) {
                Iterable<Result<Item>> list = client.listObjects(ListObjectsArgs.builder().bucket(properties.getBucket()).prefix(objectName).recursive(false).build());
                Iterator<Result<Item>> iterator = list.iterator();
                while (iterator.hasNext()) {
                    Item item = iterator.next().get();
                    result.add(item.objectName());
                }
            }
        } catch (Exception e) {
            logger.error("获取文件夹列表失败", e);
        }
        return result;
    }

    @Override
    public void delete(String tenant, String fileId) throws Exception
    {
        if (properties.isUseMsgQueue() && attachmentProducer != null)
        {
            attachmentProducer.delete(tenant, fileId);
        } else
        {
            try
            {
                client.removeObject(RemoveObjectArgs.builder().bucket(properties.getBucket()).object(fileId).build());
            } catch (Exception e)
            {
                throw new Exception("删除云存储里的文件发生异常。ObjectId=" + fileId, e);
            }
        }
    }
    public void deleteFile(String fileId) throws Exception{
        try
        {
            client.removeObject(RemoveObjectArgs.builder().bucket(properties.getBucket()).object(fileId).build());
        } catch (Exception e)
        {
            throw new Exception("删除云存储里的文件发生异常。ObjectId=" + fileId, e);
        }
    }
}
