package com.fdkankan.filestorage.aliyun;


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.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.ServiceException;
import com.aliyun.oss.model.*;
import com.fdkankan.filestorage.Consumer;
import com.fdkankan.filestorage.InnerUtils;
import com.fdkankan.filestorage.FileStorageTemplate;
import com.fdkankan.filestorage.properties.AliyunOssProperties;
import com.fdkankan.filestorage.util.DomainUtil;
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 org.springframework.util.StringUtils;

import java.io.*;
import java.net.MalformedURLException;
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.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


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

    private final OSS ossClient;
    private final AliyunOssProperties ossProperties;

    @Override
    public String getActive() {
        return this.ossProperties.getActive();
    }

    public AliyunOssTemplate(OSS ossClient, AliyunOssProperties ossProperties) {
        this.ossClient = ossClient;
        this.ossProperties = ossProperties;
    }

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

    @Override
    public String uploadFile(String pathKey, String filePath, Map<String, String> headers) {
        return uploadFile(ossProperties.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");
        ObjectMetadata metadata = new ObjectMetadata();
        ossClient.putObject(bucket, pathKey, new File(filePath), metadata);
        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");
        ObjectMetadata metadata = new ObjectMetadata();
        if (ObjectUtil.isNotEmpty(headers)) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                metadata.setHeader(header.getKey(), header.getValue());
            }
        }
        ossClient.putObject(bucket, pathKey, new File(filePath), metadata);
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String uploadFileText(String pathKey, String content) {
        return uploadFileText(ossProperties.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(ossProperties.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(ossProperties.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(ossProperties.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();
        ossClient.putObject(bucket, pathKey, stream, metadata);
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public File downloadFileTmp(String pathKey) {
        return downloadFileTmp(ossProperties.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(ossProperties.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 request = 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 ossClient.getObject(request, downloadFile);
    }

    @Override
    public ObjectMetadata downloadFile(String pathKey, Consumer<InputStream> handler) {
        return downloadFile(ossProperties.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");
        OSSObject ossObject = ossClient.getObject(bucket, pathKey);
        InputStream content = ossObject.getObjectContent();
        try {
            handler.accept(content);
        } finally {
            if (content != null) {
                try {
                    content.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return ossObject.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);
        }
        try (FileOutputStream fos = new FileOutputStream(file)){
            // 下载OSS文件流
            OSSObject object = ossClient.getObject(bucket, path);

            InputStream is = object.getObjectContent();
            byte[] b = new byte[1024];
            int length;
            while((length= is.read(b)) != -1){
                fos.write(b,0,length);
            }

            is.close();

            // 关闭OSSClient。
        }catch (IOException e){
            throw new ServiceException(e.getMessage());
        }

        return true;
    }

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

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

    @Override
    public String calculateUrl(String bucket, String pathKey) {
        String host = ossProperties.getHostByBucket(bucket);
        return ossProperties.isFullPath()?host + "/" + pathKey:pathKey;
    }


    public String getHostByBucket(String bucket) {
        return ossProperties.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(ossProperties.getBucket(), oldPath, ossProperties.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 = ossClient.copyObject(bucket, oldPath, toBucket, newPath).getVersionId();
        if (StrUtil.isNotBlank(versionId)) {
            return true;
        }
        return false;
    }

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

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

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

    @Override
    public List<String> getFileFolder(String bucket, String keyName) {
        log.info("获取文件夹bucket-{}，路径{}",bucket,keyName);
        List<String> keyList = new ArrayList<>();
        try {
            boolean flag = true;
            String nextMaker = null;
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucket);
            //指定下一级文件
            listObjectsRequest.setPrefix(keyName);
            //设置分页的页容量
            listObjectsRequest.setMaxKeys(200);
            do {
                //获取下一页的起始点，它的下一项
                listObjectsRequest.setMarker(nextMaker);
                ObjectListing objectListing = ossClient.listObjects(listObjectsRequest);
                List<String> collect = objectListing.getObjectSummaries().parallelStream()
                        .map(OSSObjectSummary::getKey).collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(collect)) {
                    keyList.addAll(collect);
                }
                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 = ossClient.doesObjectExist(bucket, keyName);
        log.info("文件是否存在={}", b);
        return b;
    }

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

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

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

    @Override
    public String uploadFileImage(String bucket, String pathKey, String filePath) {
        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);
        ossClient.putObject(bucket, pathKey, new File(filePath), metadata);
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String getFileContent(String bucket, String keyName) throws Exception {
        try (OSSObject ossObject = ossClient.getObject(bucket, keyName);
            InputStream objectContent = ossObject.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(bucket);
        //指定下一级文件
        listObjectsRequest.setPrefix(key);
        //设置分页的页容量
        listObjectsRequest.setMaxKeys(200);
        do {
            //获取下一页的起始点，它的下一项
            listObjectsRequest.setMarker(nextMaker);
            ObjectListing objectListing = ossClient.listObjects(listObjectsRequest);
            Long space = objectListing.getObjectSummaries().parallelStream()
                    .mapToLong(OSSObjectSummary::getSize).sum();
            total += space;
            nextMaker = objectListing.getNextMarker();
            //全部执行完后，为false
            flag = objectListing.isTruncated();
        } while (flag);
        return total;
    }

    @Override
    public Long getSpace(String key) {
        return  getSpace(ossProperties.getBucket(), key);
    }

    @SneakyThrows
    @Override
    public String getInternalEndpoint(String bucket ,String key) {
        URI uri = new URI(ossProperties.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) {
        URI uri = new URI(ossProperties.getInternalEndpoint());
        String host = uri.getHost();
        host=this.getBucket()+"."+host;
        return new URI(uri.getScheme(), host, uri.getPath()+"/"+key, uri.getFragment()).toString();
    }

    @Override
    public void copyFolder(String sourcePath, String targetPath) {
        copyFolder(ossProperties.getBucket(),   sourcePath,  ossProperties.getBucket(),   targetPath);
    }
    @Override
    public void copyFolder(String sourceBucketName, String sourcePath, String targetBucketName, String targetPath) {
        try {
            log.info("复制开始{}->>{}",sourceBucketName,sourceBucketName);
            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));
            });
            log.info("复制结束");
        } 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(HttpMethod.PUT);
        generatePresignedUrlRequest.setExpiration(expiration);
        return ossClient.generatePresignedUrl(generatePresignedUrlRequest);
    }
    @Override
    public URL getPresignedUrl(String key) {
        return getPresignedUrl(ossProperties.getBucket(), key);
    }
}
