package com.mosty.common.core.minio;

import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mosty.common.base.constant.enums.DeletedEnum;
import com.mosty.common.base.domain.ResponseResult;
import com.mosty.common.base.exception.Asserts;
import com.mosty.common.base.exception.BusinessException;
import com.mosty.common.base.util.DateUtils;
import com.mosty.common.config.entity.SysConfig;
import com.mosty.common.config.mapper.SysConfigMapper;
import com.mosty.common.core.business.entity.SysOss;
import com.mosty.common.core.business.entity.vo.Base64Str;
import com.mosty.common.core.business.service.SysOssService;
import com.mosty.common.core.config.MinIoClientConfig;
import com.mosty.common.core.util.BASE64DecodedMultipartFile;
import com.mosty.common.util.ImageUtils;
import com.mosty.common.util.UUIDGenerator;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Slf4j
@RestController
@RequestMapping("/minio")
@AllArgsConstructor
@Api(tags = "图片上传服务")
@SuppressWarnings("unused")
public class UploadController {

    private final MinioClient minioClient;
    private final MinIoClientConfig minIoClientConfig;
    private final SysOssService sysOssService;
    private final SysConfigMapper sysConfigMapper;


    @GetMapping("/image/downloadBaes64/{id}")
    @ApiOperation(value = "获取图片Base64", httpMethod = "GET", response = String.class)
    public ResponseResult<String> downloadBaes64(@PathVariable("id") String id) {
        try {
            if (StringUtils.isEmpty(id)) {
                return ResponseResult.success();
            }
            SysOss oss = sysOssService.getById(id);
            if (!ObjectUtils.isEmpty(oss)) {
                byte[] data = ImageUtils.encodeImageToByte(this.getMonioUrl() + oss.getUrl());
                BASE64Encoder encoder = new BASE64Encoder();
                String base64 = encoder.encode(data).replaceAll("[\\s*\t\n\r]", "");
                return ResponseResult.success(base64);
            }
            return ResponseResult.success();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    @PostMapping("/file/upload")
    @ApiOperation(value = "前端上传组件使用：上传文件(返回文件的访问地址)", httpMethod = "POST", response = String.class)
    public ResponseResult<String> uploadFile(@JSONField(serialize = false) @RequestParam("file") MultipartFile file)
            throws Exception {
        if (file.getSize() > 0) {
            return uploadOssWithBucket(file, minIoClientConfig.getFileBucket());
        }
        return ResponseResult.fail("未选择任何文件，上传失败");
    }

    @PostMapping("/{bucketName}/upload")
    @ApiOperation(value = "指定桶上传文件(返回文件的访问地址)", httpMethod = "POST", response = String.class)
    public ResponseResult<String> upload(@JSONField(serialize = false) @RequestParam("file") MultipartFile file,
                                         @PathVariable("bucketName") String bucketName) throws Exception {
        Asserts.check(StringUtils.isEmpty(bucketName), "桶名称不能为空！");
        boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        Asserts.check(!bucketExists, "桶%s不存在！", bucketName);
        if (file.getSize() > 0) {
            return uploadOssWithBucket(file, bucketName);
        }
        return ResponseResult.fail("未选择任何文件，上传失败");
    }

    @PostMapping("/image/upload/id")
    @ApiOperation(value = "前端上传组件使用：上传图片(返回图片ID)", httpMethod = "POST", response = String.class)
    public ResponseResult<String> uploadImageId(@JSONField(serialize = false) @RequestParam("file") MultipartFile file)
            throws Exception {
        if (file.getSize() > 0) {
            return uploadOssWithBucketById(file, minIoClientConfig.getImageBucket());
        }
        return ResponseResult.fail("文件为空，或文件无内容！！");
    }

    @GetMapping("/image/download/{id}")
    @ApiOperation(value = "根据ID下载图片(返回二进制文件)", httpMethod = "GET", response = String.class)
    public ResponseEntity<byte[]> downloadImageId(@PathVariable("id") String id, HttpServletRequest request) {
        try {
            if (StringUtils.isEmpty(id)) {
                return ResponseEntity.ok(null);
            }
            return downloadFile(id, request);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    @GetMapping("/file/download/{id}")
    @ApiOperation(value = "根据ID查询文件的访问地址", httpMethod = "GET", response = String.class)
    public ResponseResult<Map<String, String>> downloadFileId(@PathVariable("id") String id) {
        if (StringUtils.isEmpty(id)) {
            throw new BusinessException("附件ID不能为空");
        }
        return downloadFileById(id);
    }

    @GetMapping("/file/getFileList")
    @ApiOperation(value = "查询文件", httpMethod = "GET", response = String.class)
    public ResponseResult<List<Map<String, String>>> getFileList(String fileIds) {
        List<Map<String, String>> list = new ArrayList<>();
        List<String> fileList = Arrays.asList(fileIds.split(","));
        fileList.forEach(item -> {
            SysOss oss = this.sysOssService.getById(item);
            if (oss != null) {
                Map<String, String> map = new HashMap<>();
                map.put("id", oss.getId());
                map.put("url", this.getMonioUrl() + oss.getUrl());
                map.put("name", oss.getFileName());
                map.put("originalName", oss.getOriginalName());
                map.put("suffix", oss.getFileSuffix());
                list.add(map);
            }
        });
        return ResponseResult.success(list);
    }

    // 获取minio的请求地址
    private String getMonioUrl() {
        SysConfig config = this.sysConfigMapper.selectOne(
                new LambdaQueryWrapper<SysConfig>()
                        .eq(SysConfig::getPzj, "MINIO_URL")
                        .eq(SysConfig::getXtZxbz, DeletedEnum.NATURE)
        );
        if (config != null) {
            return config.getPzz();
        }
        return "http://80.35.1.150:9009/";
    }

    @NotNull
    private ResponseResult<String> uploadOssWithBucket(MultipartFile file, String bucketName)
            throws IOException, ServerException, InsufficientDataException, ErrorResponseException,
            NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        // 原文件名
        String fileName = file.getOriginalFilename();
        if (StringUtils.isEmpty(fileName)) {
            return ResponseResult.fail("文件名不能为空！");
        }
        // 文件类型
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        // 生成新文件名，确保唯一性
        String objectName = UUIDGenerator.getUUID();
        // 文件类型
        String fileType = file.getContentType();
        String fileUrl = DateUtils.datePathString() + "/" + objectName + suffixName;
        // 使用putObject上传一个文件到存储桶中
        PutObjectArgs build = PutObjectArgs.builder().object(fileUrl)
                .bucket(bucketName)
                .contentType(file.getContentType())
                .stream(file.getInputStream(), file.getSize(), -1).build();

        boolean exist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        Asserts.check(!exist, "minio bucket %s 不存在，请联系管理员", bucketName);
        minioClient.putObject(build);
        // 得到文件 url
        String url = bucketName + "/" + fileUrl;
        SysOss oss = new SysOss();
        oss.setId(objectName);
        oss.setFileName(objectName + suffixName);
        oss.setOriginalName(fileName);
        oss.setFileSuffix(suffixName);
        oss.setUrl(url);
        oss.setXtCjsj(new Date());
        oss.setXtLrsj(new Date());
        oss.setXtZhxgsj(new Date());
        oss.setXtZxbz("0");
        sysOssService.save(oss);
        return ResponseResult.success(this.getMonioUrl() + url);
    }

    @NotNull
    private ResponseResult<String> uploadOssWithBucketById(MultipartFile file, String bucketName) throws IOException,
            ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException,
            InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        // 原文件名
        String fileName = file.getOriginalFilename();
        if (StringUtils.isEmpty(fileName)) {
            return ResponseResult.fail("文件名不能为空！");
        }
        // 文件类型
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        // 生成新文件名，确保唯一性
        String objectName = UUIDGenerator.getUUID();
        // 文件类型
        String fileType = file.getContentType();
        String fileUrl = DateUtils.datePathString() + "/" + objectName + suffixName;
        // 使用putObject上传一个文件到存储桶中
        PutObjectArgs build = PutObjectArgs.builder().object(fileUrl)
                .bucket(bucketName)
                .contentType(file.getContentType())
                .stream(file.getInputStream(), file.getSize(), -1).build();
        boolean exist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        Asserts.check(!exist, "minio bucket %s 不存在，请联系管理员", bucketName);
        minioClient.putObject(build);
        // 得到文件 url
        String url = bucketName + "/" + fileUrl;
        SysOss oss = new SysOss();
        oss.setId(objectName);
        oss.setFileName(objectName + suffixName);
        oss.setOriginalName(fileName);
        oss.setFileSuffix(suffixName);
        oss.setUrl(url);
        oss.setXtCjsj(new Date());
        oss.setXtLrsj(new Date());
        oss.setXtZhxgsj(new Date());
        oss.setXtZxbz("0");
        sysOssService.save(oss);
        return ResponseResult.success(objectName);
    }

    // 下载图片（返回文件的URL）
    public ResponseResult<Map<String, String>> downloadFileById(String id) {
        try {
            SysOss oss = sysOssService.getById(id);
            Map<String, String> map = new HashMap<>();
            map.put("id", oss.getId());
            map.put("url", this.getMonioUrl() + oss.getUrl());
            map.put("name", oss.getFileName());
            map.put("originalName", oss.getOriginalName());
            map.put("suffix", oss.getFileSuffix());
            return ResponseResult.success(map);
        } catch (Exception e) {
            log.error("Minio-Obs下载对象失败：", e);
            throw new BusinessException("获取附件下载地址失败！！");
        }
    }

    //　下载图片(返回二进制)
    public ResponseEntity<byte[]> downloadFile(String id, HttpServletRequest request) {
        try {

            SysOss oss = sysOssService.getById(id);
            byte[] obsBytes = ImageUtils.encodeImageToByte(this.getMonioUrl() + oss.getUrl());
            HttpHeaders headers = new HttpHeaders();
            headers.setContentDispositionFormData("attachment", oss.getFileName());
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            return new ResponseEntity<>(obsBytes, headers, HttpStatus.CREATED);
        } catch (Exception e) {
            log.error("Minio-Obs下载对象失败：", e);
            throw new BusinessException("下载对象失败");
        }
    }

    //　inputStream转换成byte数组
    private byte[] inputStreamTobyte(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc;
        while ((rc = inputStream.read(buff, 0, 100)) > 0) {
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }

}