package com.skr.mdm.manager.oss;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.net.URLDecoder;
import com.skr.mdm.config.MinIoConfig;
import com.skr.mdm.dao.AuCompanyDao;
import com.skr.mdm.publish.MdmEventPublisher;
import com.skr.mdm.util.PinyinUtils;
import com.skr.mdm.util.RedisGetKeyUtil;
import com.skr.mdm.util.RedisUtils;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author nfq
 * @date 2023-03-06 09:33
 */
@Slf4j
@Component
public class MinIoManager {

    public static final String BUCKET = "oss";

    @Autowired
    private AuCompanyDao companyDao;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MinIoConfig minIoConfig;
    @Autowired
    private MdmEventPublisher eventPublisher;

    /**
     * minio文件上传
     *
     * @param companyId  公司ID
     * @param object     minio对象
     * @param file       上传文件
     * @param isProgress 是否需要进度条
     * @return 文件URL
     */
    public String putObjectMinIo(Integer companyId, String object, MultipartFile file, boolean isProgress) {
        long fileSize = file.getSize();
        if (fileSize > 1024 && fileSize < 1024 * 1024) {
            log.info("MINIO上传文件 大小：{}KB", fileSize / 1024);
        } else {
            log.info("MINIO上传文件 大小：{}M", fileSize / (1024 * 1024));
        }
        try (BufferedInputStream bis = new BufferedInputStream(file.getInputStream());
             ProgressInputStream progressInputStream = new ProgressInputStream(bis, process -> {
                 if (isProgress) {
                     redisUtils.set(RedisGetKeyUtil.getFileProgressKey(String.valueOf(fileSize)), process, 30);
                     eventPublisher.publishFileUploadProgressEvent(String.valueOf(fileSize), String.valueOf(process));
                 }
             })
        ) {
            return putObject(companyId, object, progressInputStream);
        } catch (Exception e) {
            log.error("MINIO文件上传失败", e);
        }
        return null;
    }

    /**
     * minio文件上传
     *
     * @param companyId  公司ID
     * @param object     minio对象
     * @param file       上传文件
     * @param isProgress 是否需要进度条
     * @return 文件URL
     */
    public String putObjectMinIo(Integer companyId, String object, File file, boolean isProgress) {
        long fileSize = file.length();
        if (fileSize > 1024 && fileSize < 1024 * 1024) {
            log.info("MINIO上传文件 大小：{}KB", fileSize / 1024);
        } else {
            log.info("MINIO上传文件 大小：{}M", fileSize / (1024 * 1024));
        }
        try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()));
             ProgressInputStream progressInputStream = new ProgressInputStream(bis, process -> {
                 if (isProgress) {
                     redisUtils.set(RedisGetKeyUtil.getFileProgressKey(String.valueOf(fileSize)), process, 30);
                     log.info("上传进度：{}", process);
                 }
             })
        ) {
            return putObject(companyId, object, progressInputStream);
        } catch (Exception e) {
            log.error("MINIO文件上传失败", e);
        }
        return null;
    }

    private String putObject(Integer companyId, String object, ProgressInputStream progressInputStream) throws Exception {
        if (!checkBucketExist()) {
            log.warn("{}存储桶不存在", BUCKET);
            createBucket();
        }
        // 获取公司名
        String companyName = getCompanyName(companyId);
        object = companyName + "/" + object;
        // 获取预签名地址
        String resignedObjectUrl = getResignedObjectUrl(object, minioClient);
        if (StringUtils.contains(resignedObjectUrl, "?")) {
            resignedObjectUrl = StringUtils.substringBefore(resignedObjectUrl, "?");
        }
        // 判断文件是否存在 如果已经存在则直接返回路径
        if (isDownloadable(resignedObjectUrl)) {
            log.info("当前上传数据已存在  {}", resignedObjectUrl);
            return resignedObjectUrl;
        }
        // 上传
        minioClient.putObject(PutObjectArgs.builder()
                .bucket(BUCKET)
                .object(object)
                .stream(progressInputStream, progressInputStream.available(), -1)
                .build());
        return resignedObjectUrl;
    }

    /**
     * 获取文件下载
     *
     * @param object      对象名称
     * @param minioClient 服务客户端
     * @return 返回数据url
     */
    private String getResignedObjectUrl(String object, MinioClient minioClient) throws ErrorResponseException,
            InsufficientDataException, InternalException, InvalidBucketNameException, InvalidExpiresRangeException,
            InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, XmlParserException,
            ServerException {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .method(Method.GET)
                        .bucket(BUCKET)
                        .object(object)
                        .build());
    }

    /**
     * 检查容器是否已经存在
     *
     * @return
     * @throws Exception
     */
    public boolean checkBucketExist() throws Exception {
        return minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(BUCKET)
                .build());
    }

    /**
     * 删除MinIO对象(文件相对路径)
     *
     * @param companyId
     * @param objects
     */
    public void removeObject(Integer companyId, List<String> objects) {
        try {
            if (checkBucketExist()) {
                if (CollectionUtils.isNotEmpty(objects)) {
                    String companyName = getCompanyName(companyId);
                    for (String oldObject : objects) {
                        oldObject = companyName + "/" + oldObject;
                        minioClient.removeObject(RemoveObjectArgs.builder()
                                .bucket(BUCKET)
                                .object(oldObject)
                                .build());
                        log.info("MinIO删除文件成功：{}", oldObject);
                    }
                }
            }
        } catch (Exception e) {
            log.error("MinIO删除失败", e);
        }
    }

    /**
     * 删除MinIO对象(文件完整URL)
     *
     * @param fileUrlList 文件地址
     */
    public void removeObject(List<String> fileUrlList) {
        try {
            if (CollectionUtils.isEmpty(fileUrlList)) {
                return;
            }
            List<String> objects = fileUrlList.stream().map(fileUrl -> {
                String minioObject = StringUtils.substringAfter(fileUrl, MinIoManager.BUCKET);
                return URLDecoder.decode(minioObject, Charset.defaultCharset());
            }).collect(Collectors.toList());
            if (checkBucketExist()) {
                if (CollectionUtils.isNotEmpty(objects)) {
                    for (String object : objects) {
                        minioClient.removeObject(RemoveObjectArgs.builder()
                                .bucket(BUCKET)
                                .object(object)
                                .build());
                        log.info("MinIO删除文件成功：{}", object);
                    }
                }
            }
        } catch (Exception e) {
            log.error("MinIO删除失败", e);
        }
    }

    public void downLoadMinIoObject(String object, String realPath) throws Exception {
        minioClient.downloadObject(DownloadObjectArgs.builder()
                .bucket(BUCKET)
                .object(object)
                .filename(realPath)
                .build());
    }

    /**
     * 删除对象容器
     *
     * @param companyName 公司名称
     */
    public void removeCompanyBucket(String companyName) {
        String companyNamePinYin = getCompanyNamePinyin(companyName);
        try {
            if (checkBucketExist()) {
                TimeInterval timer = DateUtil.timer();
                log.info("公司:{}MinIo文件删除开始", companyNamePinYin);
                Iterable<Result<Item>> resultIterable = minioClient.listObjects(ListObjectsArgs
                        .builder()
                        .bucket(BUCKET)
                        .prefix(companyNamePinYin)
                        .recursive(true)
                        .useUrlEncodingType(false)
                        .build());
                for (Result<Item> itemResult : resultIterable) {
                    Item item = itemResult.get();
                    String objectName = item.objectName();
                    minioClient.removeObject(RemoveObjectArgs
                            .builder()
                            .object(objectName)
                            .bucket(BUCKET)
                            .build());
                }
                log.info("公司:{}MinIo文件删除完成 耗时：{}毫秒", companyNamePinYin, timer.intervalMs());
            }
        } catch (Exception e) {
            log.error("删除公司下所有文件异常: " + companyNamePinYin, e);
        }
    }

    /**
     * 创建桶容器
     *
     * @throws Exception
     */
    private void createBucket() throws Exception {
        minioClient.makeBucket(MakeBucketArgs
                .builder()
                .bucket(BUCKET)
                .build());
        log.info("创建存储桶: {}", BUCKET);
    }


    public String getCompanyName(Integer companyId) {
        String bucket = companyDao.selectById(companyId).getCompanyName();
        if (bucket.length() < 3) {
            bucket = "file" + "-" + PinyinUtils.getPinYin(bucket);
        } else {
            bucket = PinyinUtils.getPinYin(bucket);
        }
        return bucket;
    }

    public String getCompanyNamePinyin(String companyName) {
        String bucket = companyName;
        if (bucket.length() < 3) {
            bucket = "file" + "-" + PinyinUtils.getPinYin(bucket);
        } else {
            bucket = PinyinUtils.getPinYin(bucket);
        }
        return bucket;
    }

    public String getEndpointIp() {
        String endpoint = StringUtils.substringBeforeLast(minIoConfig.getEndpoint(), ":");
        return StringUtils.substringAfter(endpoint, "//");
    }

    /**
     * 判断url是否是可可下载资源文件
     *
     * @param url
     * @return
     * @throws Exception
     */
    private Boolean isDownloadable(String url) throws Exception {
        URL urlSource = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) urlSource.openConnection();
        connection.setRequestMethod("HEAD");
        int responseCode = connection.getResponseCode();
        return (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL);
    }

}