package com.fdkankan.filestorage.cos;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.fdkankan.filestorage.Consumer;
import com.fdkankan.filestorage.InnerUtils;
import com.fdkankan.filestorage.FileStorageTemplate;
import com.fdkankan.filestorage.properties.CosProperties;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.*;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;


/**
 * 阿里云操作模版类, 简化常见操作
 */
@Getter
@Slf4j
@Service("CosTemplate")
@ConditionalOnProperty(prefix = CosProperties.PREFIX,name = "filestorage.active",havingValue = "cos")
public class CosTemplate implements FileStorageTemplate {

    private final COSClient cosClient;
    private final CosProperties cosProperties;
    @Override
    public String getActive() {
        return this.cosProperties.getActive();
    }
    public CosTemplate(COSClient ossClient, CosProperties cosProperties) {
        this.cosClient = ossClient;
        this.cosProperties = cosProperties;
    }

    @Override
    public String uploadFile(String pathKey, String filePath) {
        return uploadFile(cosProperties.getBucket(), pathKey, filePath);
    }

    @Override
    public String uploadFile(String pathKey, String filePath, Map<String, String> headers) {
        return uploadFile(cosProperties.getBucket(), pathKey, filePath,headers);
    }

    @Override
    public String uploadFile(String bucket, String pathKey, String filePath) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        File file = new File(filePath);
        if (!file.exists()) {
            log.warn("要上传的文件不存在,filePath" + filePath);
            return null;
        }
        try (InputStream ins = new FileInputStream(file)){
            ObjectMetadata metadata = new ObjectMetadata();
            if (filePath.contains(".jpg")) {
                metadata.setContentType("image/jpeg");
            } else if (filePath.contains(".png")) {
                metadata.setContentType("image/png");
            }else  if (filePath.contains(".mp4")) {
                metadata.setContentType("video/mp4");
            } else if (filePath.contains(".mp3")) {
                metadata.setContentType("audio/mp3");
            }
            cosClient.putObject(bucket, pathKey, ins, metadata);
            log.info("文件上传成功，path:{}", filePath);
        } catch (Exception e) {
            log.error("cos上传文件失败,filePath:"+filePath, e);
        }

        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String uploadFile(String bucket, String pathKey, String filePath, Map<String, String> headers) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        File file = new File(filePath);
        if (!file.exists()) {
            log.warn("要上传的文件不存在,filePath" + filePath);
            return null;
        }
        try (InputStream ins = new FileInputStream(file)){
            ObjectMetadata metadata = new ObjectMetadata();
            if (ObjectUtil.isNotEmpty(headers)) {
                for (Map.Entry<String, String> header : headers.entrySet()) {
                    metadata.setHeader(header.getKey(), header.getValue());
                }
            }
            cosClient.putObject(bucket, pathKey, ins, metadata);
            log.info("文件上传成功，path:{}", filePath);
        } catch (Exception e) {
            log.error("cos上传文件失败,filePath:"+filePath, e);
        }

        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String uploadFileText(String pathKey, String content) {
        return uploadFileText(cosProperties.getBucket(), pathKey, content);
    }

    @Override
    public String uploadFileText(String bucket, String pathKey, String content) {
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
        return uploadFileBytes(bucket, pathKey, bytes);
    }

    @Override
    public String uploadFileBase64Image(String pathKey, String content) {
        return uploadFileBase64Image(cosProperties.getBucket(), pathKey, content);
    }

    @Override
    public String uploadFileBase64Image(String bucket, String pathKey, String content) {
        byte[] bytes = Base64Utils.decodeFromString(content);
        return uploadFileBytes(bucket, pathKey, bytes);
    }

    @Override
    public String uploadFileBytes(String pathKey, byte[] bytes) {
        return uploadFileBytes(cosProperties.getBucket(), pathKey, bytes);
    }

    @Override
    public String uploadFileBytes(String bucket, String pathKey, byte[] bytes) {
        ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
        return uploadFileStream(bucket, pathKey, stream);
    }

    @Override
    public String uploadFileStream(String pathKey, InputStream stream) {
        return uploadFileStream(cosProperties.getBucket(), pathKey, stream);
    }

    @Override
    public String uploadFileStream(String bucket, String pathKey, InputStream stream) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        InnerUtils.checkArgument(stream != null, "stream can't be null");
        ObjectMetadata metadata = new ObjectMetadata();
        cosClient.putObject(bucket, pathKey, stream,metadata);
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public File downloadFileTmp(String pathKey) {
        return downloadFileTmp(cosProperties.getBucket(), pathKey);
    }

    @Override
    public File downloadFileTmp(String bucket, String pathKey) {
        String tmpDir = System.getProperty("java.io.tmpdir");
        String file = tmpDir + UUID.randomUUID();
        downloadFile(bucket, pathKey, file);
        return new File(file);
    }

    @Override
    public ObjectMetadata downloadFile(String pathKey, String file) {
        return downloadFile(cosProperties.getBucket(), pathKey, file);
    }

    @Override
    public ObjectMetadata downloadFile(String bucket, String pathKey, String file) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        InnerUtils.checkArgument(file != null && file.length() > 0, "file can't be empty");
        GetObjectRequest getObjectRequest = new  GetObjectRequest(bucket, pathKey);
        log.info("下载开始：下载bucket={},下载pathKey={},下载filePath={}", bucket, pathKey, file);
        File downloadFile = new File(file);
        if (!FileUtil.exist(downloadFile.getParent())){
            FileUtil.mkdir(downloadFile.getParent());
        }
        return cosClient.getObject(getObjectRequest,downloadFile);
    }

    @Override
    public ObjectMetadata downloadFile(String pathKey, Consumer<InputStream> handler) {
        return downloadFile(cosProperties.getBucket(), pathKey, handler);
    }

    @Override
    public ObjectMetadata downloadFile(String bucket, String pathKey, Consumer<InputStream> handler) {
        InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
        InnerUtils.checkArgument(pathKey != null && pathKey.length() > 0, "pathKey can't be empty");
        InnerUtils.checkArgument(handler != null, "handler can't be null");
        COSObject COSObject = cosClient.getObject(bucket, pathKey);
        InputStream content = COSObject.getObjectContent();
        try {
            handler.accept(content);
        } finally {
            if (content != null) {
                try {
                    content.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return COSObject.getObjectMetadata();
    }

    @Override
    public boolean ossDownloadFileToLocal(String bucket,String path, String localPath) {
        File file = new File(localPath);
        if (!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }

        log.info("localPath : " + localPath);

        if("/".equals(path.substring(0,1))){
            path = path.substring(1);
        }
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, path);
        cosClient.getObject(getObjectRequest, new File(localPath));

        return true;
    }

    @Override
    public boolean ossDownloadFileToLocal(String path, String localPath) {
        return ossDownloadFileToLocal(cosProperties.getBucket(),path,localPath);
    }

    @Override
    public String calculateUrl(String pathKey) {
        return calculateUrl(cosProperties.getBucket(), pathKey);
    }

    @Override
    public String calculateUrl(String bucket, String pathKey) {
        String host = cosProperties.getHostByBucket(bucket);
        return cosProperties.isFullPath()?host + "/" + pathKey:pathKey;
    }
    @Override
    public String getHostByBucket(String bucket) {
        return cosProperties.getHostByBucket(bucket);
    }
    public String calculatePathKey(String url) {
        String urlPath = InnerUtils.getUrlPath(url);
        if (urlPath != null && urlPath.startsWith("/")) {
            String pathKey = urlPath.substring(1);
            if (pathKey.length() > 0) {
                return InnerUtils.decodeUrl(pathKey);
            }
        }
        return null;
    }

    public String calculateHost(String endpoint, String bucket) {
        return InnerUtils.generateHost(endpoint, bucket);
    }


    @Override
    public Boolean copyObject(String oldPath, String newPath) {
        return copyObject(cosProperties.getBucket(), oldPath, cosProperties.getBucket(), newPath);
    }

    @Override
    public Boolean copyObject(String bucket, String oldPath, String toBucket, String newPath) {
//        log.info("拷贝开始：oldBucket={},oldPath={},newBucket={},newPath={}", bucket, oldPath, toBucket, newPath);
        String versionId = cosClient.copyObject(bucket, oldPath, toBucket, newPath).getVersionId();
        if (StrUtil.isNotBlank(versionId)) {
            return true;
        }
        return false;
    }

    @Override
    public void deleteObject(String keyName) {
        if (doesObjectExist(keyName)) {
            deleteObject(cosProperties.getBucket(), keyName);
        } else {
            log.warn("文件不存在,KeyName{}", keyName);
        }
    }

    @Override
    public void deleteObject(String bucket, String keyName) {
        cosClient.deleteObject(bucket, keyName);
    }

    @Override
    public List<String> getFileFolder(String keyName) {
        return getFileFolder(cosProperties.getBucket(), keyName);
    }

    @Override
    public List<String> getFileFolder(String bucket, String keyName) {
        log.info("获取文件夹bucket-{}，路径{}",bucket,keyName);
        List<String> keyList = new ArrayList<>();
        long totalSubFileNum = 0;
        try {
            if (!keyName.endsWith("/")) {
                keyName = keyName + "/";
            }
            boolean flag = true;
            String nextMaker = null;
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
            listObjectsRequest.setBucketName(bucket);
            //指定下一级文件
            listObjectsRequest.setPrefix(keyName);
            //设置分页的页容量
            listObjectsRequest.setMaxKeys(1000);
            do {
                //获取下一页的起始点，它的下一项
                listObjectsRequest.setMarker(nextMaker);
                ObjectListing objectListing = cosClient.listObjects(listObjectsRequest);
                List<String> collect = objectListing.getObjectSummaries().parallelStream()
                        .filter(summary -> !summary.getKey().endsWith("/")).map(COSObjectSummary::getKey).collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(collect)) {
                    totalSubFileNum = totalSubFileNum + collect.size();
                }
                nextMaker = objectListing.getNextMarker();
                //全部执行完后，为false
                flag = objectListing.isTruncated();
            } while (flag);
        } catch (Exception e) {
            log.error("获取文件列表失败，path=" + keyName, e);
            e.printStackTrace();
        }
        log.info("获取文件夹集合={}", JSON.toJSONString(keyList));
        return keyList;
    }

    @Override
    public boolean doesObjectExist(String bucket, String keyName) {
        if("/".equals(keyName.substring(0,1))){
            keyName = keyName.substring(1);
        }
        boolean b = cosClient.doesObjectExist(bucket, keyName);
        log.info("文件是否存在={}", b);
        return b;
    }

    @Override
    public boolean doesObjectExist(String keyName) {
        return doesObjectExist(cosProperties.getBucket(), keyName);
    }

    @Override
    public String getBucket() {
        return cosProperties.getBucket();
    }

    @Override
    public String uploadFileImage(String pathKey, String filePath) {
        return uploadFileImage(cosProperties.getBucket(), pathKey, filePath);
    }

    @Override
    public String uploadFileImage(String bucket, String pathKey, String filePath) {
        File file=new File(filePath);
        if (!file.exists()) {
            log.warn("要上传的文件不存在,filePath" + filePath);
            return null;
        }
        try (InputStream ins = new FileInputStream(file)){
            ObjectMetadata metadata = new  ObjectMetadata();
            if (filePath.contains(".jpg")) {
                metadata.setContentType("image/jpeg");
            } else if (filePath.contains(".png")) {
                metadata.setContentType("image/png");
            }
            log.info("上传开始：上传bucket={},上传pathKey={},上传filePath={}", bucket, pathKey, filePath);
            cosClient.putObject(bucket, pathKey, ins, metadata);
            log.info("文件上传成功，path:{}", filePath);
        } catch (Exception e) {
            log.error("cos上传文件失败,filePath:"+filePath, e);
        }
        
        
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String getFileContent(String bucket, String keyName) throws Exception {
        try (COSObject object = cosClient.getObject(bucket, keyName);
            InputStream objectContent = object.getObjectContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader(objectContent))){
            StringBuilder contentJson = new StringBuilder();
            while (true) {
                String line = reader.readLine();
                if (line == null) break;
                contentJson.append(line);
            }
            return contentJson.toString();
        } catch (Exception e) {
            throw e;
        }
    }

    @Override
    public String getFileContent(String keyName) throws Exception {
        return this.getFileContent(this.getBucket(), keyName);
    }

    @Override
    public Long getSpace(String bucket, String key) {
        Long total = 0L;
        boolean flag = true;
        String nextMaker = null;
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        listObjectsRequest.setBucketName(bucket);
        //指定下一级文件
        listObjectsRequest.setPrefix(key);
        //设置分页的页容量
        listObjectsRequest.setMaxKeys(1000);
        do {
            //获取下一页的起始点，它的下一项
            listObjectsRequest.setMarker(nextMaker);
            ObjectListing objectListing = cosClient.listObjects(listObjectsRequest);
            Long space = objectListing.getObjectSummaries().parallelStream()
                    .mapToLong(COSObjectSummary::getSize).sum();
            total += space;
            nextMaker = objectListing.getNextMarker();
            //全部执行完后，为false
            flag = objectListing.isTruncated();
        } while (flag);
        return total;
    }

    @Override
    public Long getSpace(String key) {
        return  getSpace(cosProperties.getBucket(), key);
    }
    @SneakyThrows
    @Override
    public String getInternalEndpoint(String bucket ,String key) {
        URI uri = new URI(cosProperties.getInternalEndpoint());
        String host = uri.getHost();
        host=bucket+"."+host;
        return new URI(uri.getScheme(), host, uri.getPath()+"/"+key, uri.getFragment()).toString();
    }

    @Override
    @SneakyThrows
    public String getInternalEndpoint(String key) {
        return getInternalEndpoint(cosProperties.getBucket(), key);

    }

    @Override
    public void copyFolder(String sourcePath, String targetPath) {
        copyFolder(cosProperties.getBucket(),   sourcePath,  cosProperties.getBucket(),   targetPath);
    }
    @Override
    public void copyFolder(String sourceBucketName, String sourcePath, String targetBucketName, String targetPath) {
        try {
            List<String> files = this.getFileFolder(sourceBucketName, sourcePath);
            if (ObjectUtils.isEmpty(files)) {
                return;
            }
            files.parallelStream().forEach(file -> {
               this.copyObject(sourceBucketName, file, targetBucketName, file.replace(sourcePath, targetPath));
            });
        } catch (Exception e) {
            log.error("列举文件目录失败,key:" + sourcePath, e);
        }
    }

    @Override
    public URL getPresignedUrl(String bucket, String key) {
        java.util.Date expiration = new java.util.Date();
        long expTimeMillis = expiration.getTime();
        expTimeMillis += 1000 * 60 * 60 * 8;
        expiration.setTime(expTimeMillis);
        GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, key);
        generatePresignedUrlRequest.setMethod(HttpMethodName.PUT);
        generatePresignedUrlRequest.setExpiration(expiration);
        return cosClient.generatePresignedUrl(generatePresignedUrlRequest);
    }

    @Override
    public URL getPresignedUrl(String key) {
        return getPresignedUrl(cosProperties.getBucket(), key);
    }
}
