/* (C) 2022 YiRing, Inc. */
package com.yiring.common.core;

import com.yiring.common.config.MinioConfig;
import io.minio.*;
import io.minio.Result;
import io.minio.messages.Item;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.Optional;
import lombok.Cleanup;
import lombok.NonNull;
import lombok.SneakyThrows;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

/**
 * Minio
 *
 * @author Jim
 * @version 0.1
 * 2021/9/16 16:37
 */

@SuppressWarnings({ "unused" })
@Component
public record Minio(MinioConfig config, MinioClient client) {
    /**
     * 业务数据存储桶
     */
    public static final String BUSINESS_BUCKET = "business";

    /**
     * 获取文件访问地址
     *
     * @param object 文件相对地址（含路径）
     * @param bucket 存储桶名称
     * @return URI
     */
    public String getURI(String object, String bucket) {
        return String.join("/", config.getDomain(), bucket, object);
    }

    /**
     * 获取文件访问地址（默认桶）
     *
     * @param object 文件相对地址（含路径）
     * @return URI
     */
    public String getDefaultURI(String object) {
        return String.join("/", config.getDomain(), config.getBucket(), object);
    }

    /**
     * 构建文件上传地址
     *
     * @param name   名称
     * @param suffix 后缀，可以是空字符串
     * @param folder 前缀目录，支持多层级
     * @return 文件 object 相对地址
     */
    public String buildUploadPath(@NonNull String name, @NonNull String suffix, String... folder) {
        String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/M/d"));
        String prefix = "upload/" + date;

        if (Objects.nonNull(folder) && folder.length > 0) {
            prefix += "/" + String.join("/", folder);
        }

        return prefix + "/" + name + suffix;
    }

    /**
     * 上传文件流
     *
     * @param is          文件流
     * @param contentType 文件类型
     * @param object      文件存储相对地址
     * @return 上传结果
     * @throws Exception 异常
     */
    public ObjectWriteResponse putObject(InputStream is, String contentType, String object) throws Exception {
        PutObjectArgs args = PutObjectArgs
            .builder()
            .bucket(config.getBucket())
            .stream(is, -1, PutObjectArgs.MIN_MULTIPART_SIZE)
            .object(object)
            .contentType(Optional.ofNullable(contentType).orElse(MediaType.APPLICATION_OCTET_STREAM_VALUE))
            .build();
        return client.putObject(args);
    }

    /**
     * 将文件上传到当前日期文件夹
     *
     * @param file 文件 MultipartFile
     * @return 上传结果
     * @throws Exception 异常
     */
    public ObjectWriteResponse putObject(File file, String folder) throws Exception {
        String object = file.getName();
        if (folder != null) {
            object = folder + "/" + object;
        }

        Path path = file.toPath();
        @Cleanup
        InputStream inputStream = Files.newInputStream(path);
        return putObject(inputStream, Files.probeContentType(path), object);
    }

    /**
     * 将文件上传到当前日期文件夹
     *
     * @param file 文件 MultipartFile
     * @return 上传结果
     * @throws Exception 异常
     */
    public ObjectWriteResponse putObject(MultipartFile file, String folder) throws Exception {
        String object = file.getOriginalFilename();
        if (folder != null) {
            object = folder + "/" + object;
        }

        @Cleanup
        InputStream inputStream = file.getInputStream();
        return putObject(inputStream, file.getContentType(), object);
    }

    /**
     * 删除文件
     *
     * @param object 文件相对地址
     * @throws Exception 异常
     */
    public void remove(String bucket, String object) throws Exception {
        RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(bucket).object(object).build();
        client.removeObject(args);
    }

    /**
     * 获取文件
     *
     * @param object 文件相对地址
     * @return 文件流
     * @throws Exception 异常
     */
    public GetObjectResponse getObject(String bucket, String object) throws Exception {
        GetObjectArgs args = GetObjectArgs.builder().bucket(bucket).object(object).build();
        return client.getObject(args);
    }

    /**
     * 查询文件信息
     *
     * @param object 文件相对地址
     * @return 文件信息
     * @throws Exception 异常
     */
    public StatObjectResponse objectStat(String bucket, String object) throws Exception {
        StatObjectArgs args = StatObjectArgs.builder().bucket(bucket).object(object).build();
        return client.statObject(args);
    }

    /**
     * 查询 list
     *
     * @param prefix 文件路径前缀
     * @return 文件/文件夹集合
     */
    public Iterable<Result<Item>> listObjects(String bucket, String prefix) {
        ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucket).prefix(prefix).build();
        return client.listObjects(args);
    }

    /**
     * 拷贝一个文件
     *
     * @param source 源文件 object
     * @param target 目标文件 object
     * @throws Exception 异常
     */
    public void copyObject(String bucket, String source, String target) throws Exception {
        CopySource copySource = CopySource.builder().bucket(bucket).object(source).build();
        CopyObjectArgs args = CopyObjectArgs.builder().bucket(bucket).object(target).source(copySource).build();
        client.copyObject(args);
    }

    /**
     * 上传业务数据文件
     *
     * @param path   本地文件地址（通常是一个临时文件）
     * @param object 文件 object
     * @return 上传结果
     */
    public ObjectWriteResponse putBusinessObject(Path path, String object) throws Exception {
        UploadObjectArgs args = UploadObjectArgs
            .builder()
            // 固定地储存桶位置
            .bucket(BUSINESS_BUCKET)
            .filename(path.toString())
            .object(object)
            .build();

        return client.uploadObject(args);
    }

    /**
     * 校验 Minio 是否可用
     *
     * @return 是否可用
     */
    @SneakyThrows
    public boolean isValid() {
        return client != null && client.bucketExists(BucketExistsArgs.builder().bucket(config.getBucket()).build());
    }
}
