package com.trade.service.filestorage.util.oss;

import com.aliyun.oss.*;
import com.aliyun.oss.model.*;
import com.trade.service.filestorage.util.FileUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Ali OSS Util </Br>
 * 经网络测试，阿里云OSS的Endpoint可以写内网地址，速度更快。</Br>
 * 阿里云OSS的Endpoint格式为：<存储空间所在地域>.<服务类型>.<数据中心> </Br>
 * 例如：oss-cn-beijing-internal.aliyuncs.com </Br>
 * 阿里云OSS的Endpoint列表：https://help.aliyun.com/document_detail/31837.html </Br>
 * 本地测试需要访问外网节点 </Br>
 */
@Component
public class OSSUtil {

    public static final String fileSep = "/";// 目录分隔符
    private static final Logger logger = LogManager.getLogger(OSSUtil.class);
    // 北京2节点
    private static final String endpoint = "http://oss-cn-beijing.aliyuncs.com";
    // 北京2节点
    private static final String internalEndpoint = "http://oss-cn-beijing-internal.aliyuncs.com";
    @Value("${oss.accessKeyId}")
    private String accessKeyId;
    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${oss.bucketName}")
    private String bucketName;

    private OSS internalOSS() {
        return new OSSClientBuilder().build(internalEndpoint, accessKeyId, accessKeySecret);
    }

    private OSS initOSS() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 创建bucket
     * 
     * @param bucketName bucketName
     */
    public void createBucket(String bucketName) {
        OSS oss = initOSS();
        try {
            if (!oss.doesBucketExist(bucketName)) {
                oss.createBucket(bucketName);
            }
        } catch (OSSException oe) {
            logger.error("Error Code:{} , Error Message: {}, Request ID:{}, Host ID: {}", oe.getErrorCode(),
                    oe.getErrorMessage(), oe.getRequestId(), oe.getHostId());
        } catch (ClientException ce) {
            ce.printStackTrace();
        } finally {
            if (oss != null) {
                oss.shutdown();
            }
        }
    }

    /**
     * 获取bucket列表
     * 
     * @return List<Bucket>
     */
    public List<Bucket> getBucketList() {
        OSS oss = initOSS();
        List<Bucket> buckets = new ArrayList<>();
        try {
            buckets = oss.listBuckets();
        } catch (OSSException | ClientException oe) {
            oe.printStackTrace();
        } finally {
            if (oss != null) {
                oss.shutdown();
            }
        }
        return buckets;
    }

    /**
     * 上传文件 默认bucket
     * 
     * @param objectName 文件名称
     * @param filePath 源文件路径
     * @return 文件ETag
     */
    public String uploadFile(String objectName, String filePath) {
        try {
            return uploadFile(bucketName, objectName, new FileInputStream(filePath));
        } catch (FileNotFoundException e) {
            logger.error("文件未找到");
        }
        return null;
    }

    /**
     * 上传文件 默认bucket
     *
     * @param objectName 文件名称
     * @param inputStream 文件流
     * @return 文件ETag
     */
    public String uploadFile(String objectName, InputStream inputStream) {
        return uploadFile(bucketName, objectName, inputStream);
    }

    /**
     * 本地文件上传
     *
     * @param bucketName bucketName
     * @param objectName 文件名称
     * @param inputStream 文件流
     * @return 文件ETag
     */
    public String uploadFile(String bucketName, String objectName, InputStream inputStream) {
        OSS ossClient = initOSS();
        try {
            // PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
            // 上传文件流。
            PutObjectResult result = ossClient.putObject(bucketName, objectName, inputStream);
            if (StringUtils.isNotEmpty(result.getETag())) {
                return result.getETag();
            }
        } catch (OSSException oe) {
            logger.error("Error Code:{} , Error Message: {}, Request ID:{}, Host ID: {}", oe.getErrorCode(),
                    oe.getErrorMessage(), oe.getRequestId(), oe.getHostId());
        } catch (ClientException ce) {
            ce.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return null;
    }

    /**
     * 判断文件是否存在
     * 
     * @param bucketName bucketName
     * @param objectName objectName
     * @return boolean
     */
    public boolean fileExist(String bucketName, String objectName) {
        OSS ossClient = initOSS();
        boolean found = false;
        try {
            found = ossClient.doesObjectExist(bucketName, objectName);
        } catch (OSSException oe) {
            oe.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return found;
    }

    public InputStream downloadFile(String objectName) {
        return downloadFile(bucketName, objectName);
    }

    /**
     * 下载文件
     * 
     * @param bucketName bucketName
     * @param objectName objectName
     * @return InputStream 文件流
     */
    public InputStream downloadFile(String bucketName, String objectName) {
        OSS ossClient = initOSS();
        InputStream objectContent = null;
        try {
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);
            objectContent = ossObject.getObjectContent();
            if (objectContent != null) {
                // 注意此处不能直接返回ossObject.getObjectContent()，否则会导致流读取完毕后无法关闭，导致连接泄漏。
                // 实际上ossObject.getObjectContent()返回的流是包装过的，需要先转换为字节数组，再转换为InputStream。
                // 由于ossClient.shutdown()会关闭ossObject，直接返回ossObject.getObjectContent() ，外面使用时可能会socket关闭异常。
                byte[] bytes = FileUtil.toByteArray(objectContent);
                return new ByteArrayInputStream(bytes);
            }
        } catch (OSSException | IOException oe) {
            oe.printStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
            // 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
            if (objectContent != null) {
                try {
                    objectContent.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 下载文件到本地文件
     * 
     * @param objectName objectName
     * @param localPath 本地文件路径
     * @return File
     */
    public File download2LocalFile(String objectName, String localPath) {
        OSS ossClient = initOSS();
        // 下载Object到本地文件，并保存到指定的本地路径中。如果指定的本地文件存在会覆盖，不存在则新建。
        // 如果未指定本地路径，则下载后的文件默认保存到示例程序所属项目对应本地路径中。
        File localFile = new File(localPath);
        localFile.deleteOnExit();
        try {
            ossClient.getObject(new GetObjectRequest(bucketName, objectName), localFile);
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return localFile;
    }

    /**
     * 删除文件
     * 
     * @param bucketName bucketName
     * @param objectName objectName
     */
    public void deleteFile(String bucketName, String objectName) {
        OSS ossClient = initOSS();
        try {
            ossClient.deleteObject(bucketName, objectName);
        } catch (OSSException oe) {
            oe.printStackTrace();
        }
    }
}
