package com.fdkankan.filestorage.aws;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.HttpMethod;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.fdkankan.filestorage.Consumer;
import com.fdkankan.filestorage.InnerUtils;
import com.fdkankan.filestorage.FileStorageTemplate;
import com.fdkankan.filestorage.properties.AwsProperties;
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.ObjectUtils;

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.stream.Collectors;


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

    private final AmazonS3 amazonS3Client;
    private final AwsProperties awsProperties;
    @Override
    public String getActive() {
        return this.awsProperties.getActive();
    }
    public AwsTemplate(AmazonS3 amazonS3Client, AwsProperties awsProperties) {
        this.amazonS3Client = amazonS3Client;
        this.awsProperties = awsProperties;
    }

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

    @Override
    public String uploadFile(String pathKey, String filePath, Map<String, String> headers) {
        return uploadFile(awsProperties.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");
        uploadFileStream(bucket, pathKey, FileUtil.getInputStream(new File(filePath)));
        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");
        BufferedInputStream stream =null;
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            if (ObjectUtil.isNotEmpty(headers)) {
                for (Map.Entry<String, String> header : headers.entrySet()) {
                    metadata.setHeader(header.getKey(), header.getValue());
                }
            }
             stream = FileUtil.getInputStream(new File(filePath));
            metadata.setContentLength(stream.available());
            PutObjectRequest request = new PutObjectRequest(bucket, pathKey,stream ,metadata);
            request.withCannedAcl(CannedAccessControlList.PublicRead);
            PutObjectResult putObjectResult = amazonS3Client.putObject(request);
            if (StrUtil.isNotEmpty(putObjectResult.getETag())) {
                log.info("s3上传文件成功：" + pathKey);
            }

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String uploadFileText(String pathKey, String content) {
        return uploadFileText(awsProperties.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(awsProperties.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(awsProperties.getBucket(), pathKey, bytes);
    }

    public void createbucket() {
        try {
            if (StrUtil.isEmpty(awsProperties.getBucket())) {
                throw new IllegalArgumentException("桶名称不能为空!");
            }
            try {
                amazonS3Client.createBucket(awsProperties.getBucket());
            } catch (SdkClientException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @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(awsProperties.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");
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            if (pathKey.contains(".jpg")) {
                metadata.setContentType("image/jpeg");
            } else if (pathKey.contains(".png")) {
                metadata.setContentType("image/png");
            } else if (pathKey.contains(".json")) {
                metadata.setContentType("application/json");

            }
            metadata.setContentLength(stream.available());
            PutObjectRequest request = new PutObjectRequest(bucket, pathKey,stream ,metadata);
            request.withCannedAcl(CannedAccessControlList.PublicRead);
            PutObjectResult putObjectResult = amazonS3Client.putObject(request);
            if (StrUtil.isNotEmpty(putObjectResult.getETag())) {
                log.info("s3上传文件成功：" + pathKey);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public File downloadFileTmp(String pathKey) {
        return downloadFileTmp(awsProperties.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(awsProperties.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);
        try {
            File downloadFile = new File(file);
            if (!FileUtil.exist(downloadFile.getParent())){
                FileUtil.mkdir(downloadFile.getParent());
            }
            return amazonS3Client.getObject(request,downloadFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public ObjectMetadata downloadFile(String pathKey, Consumer<InputStream> handler) {
        return downloadFile(awsProperties.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");
        GetObjectRequest request  = new GetObjectRequest(bucket,pathKey);
        S3Object object = amazonS3Client.getObject(request);
        InputStream content = object.getObjectContent();
        try {
            handler.accept(content);
        } finally {
            if (content != null) {
                try {
                    object.close();
                    content.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
        return object.getObjectMetadata();
    }

    public String calculateUrl(String pathKey) {
        return calculateUrl(awsProperties.getBucket(), pathKey);
    }

    public String calculateUrl(String bucket, String pathKey) {
        String host = awsProperties.getHostByBucket(bucket);
        return awsProperties.isFullPath()?host + "/" + pathKey:pathKey;
    }
    @Override
    public String getHostByBucket(String bucket) {
        return awsProperties.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(awsProperties.getBucket(), oldPath, awsProperties.getBucket(), newPath);
    }

    @Override
    public Boolean copyObject(String bucket, String oldPath, String toBucket, String newPath) {
        try {
            List<String> sourceKeyList = this.getFileFolder(oldPath);

            // 复制文件
            sourceKeyList.parallelStream().forEach(key -> {
                amazonS3Client.copyObject(bucket, key, toBucket, key.replace(oldPath, newPath));
            });
            CopyObjectResult copyObjectResult = amazonS3Client.copyObject(bucket, oldPath, toBucket, newPath);

            if (StrUtil.isNotBlank(copyObjectResult.getVersionId())) {
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("文件不存在,KeyName{}", e.getMessage());
            return false;
        }
    }

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

    @Override
    public void deleteObject(String bucket, String keyName) {
        try {
            InnerUtils.checkArgument(bucket != null && bucket.length() > 0, "bucket can't be empty");
            InnerUtils.checkArgument(keyName != null && keyName.length() > 0, "keyName can't be empty");
            amazonS3Client.deleteObject(bucket, keyName);
        } catch (Exception e) {
            log.info("文件删除失败" + e.getMessage());
        }
    }

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

    @Override
    public List<String> getFileFolder(String bucket, String keyName) {
        List<String> list = new ArrayList<>();
        try {
            String nextMaker = null;
            boolean flag = true;
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
            listObjectsRequest.setBucketName(bucket);
            listObjectsRequest.setPrefix(keyName);
            listObjectsRequest.setMaxKeys(200);
            do{
                listObjectsRequest.setMarker(nextMaker);
                ObjectListing objectListing = amazonS3Client.listObjects(listObjectsRequest);
                List<S3ObjectSummary> objectSummaries = objectListing.getObjectSummaries();
                List<String> collect =objectSummaries.stream().map(summary->{
                    return summary.getKey();
                }).collect(Collectors.toList());
                if(CollUtil.isNotEmpty(collect)){
                    list.addAll(collect);
                }
                nextMaker = objectListing.getNextMarker();
                flag = objectListing.isTruncated();
            }while (flag);
        } catch (Exception e) {
            log.error("获取文件夹集合失败={}", e.getMessage());
        }

        return list;
    }

    @Override
    public boolean doesObjectExist(String bucket, String keyName) {
        try {
            if("/".equals(keyName.substring(0,1))){
                keyName = keyName.substring(1);
            }
            return amazonS3Client.doesObjectExist(bucket,keyName);
        } catch (AmazonServiceException e) {
            if (e.getErrorCode().equalsIgnoreCase("NoSuchKey")) {
                log.error("NoSuchKey", e.getMessage());
                return false;
            }
        } catch (Exception e) {
            log.error("连接异常", e.getMessage());
            return false;
        }
        return false;
    }
    @Override
    public String getBucket() {
        return awsProperties.getBucket();
    }

    @Override
    public boolean ossDownloadFileToLocal(String bucket,String keyName, String localPath) {
        try {
            GetObjectRequest request  = new GetObjectRequest(bucket,keyName);
            amazonS3Client.getObject(request,new File(localPath));
            return true;
        } catch (Exception e) {
            log.error("amazonS3下载文件失败，key=" + amazonS3Client, e);
        }finally {
        }
        return false;
    }

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

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

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

    @Override
    public String uploadFileImage(String bucket, String pathKey, String filePath) {
        InputStream stream = null;
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            if (pathKey.contains(".jpg")) {
                metadata.setContentType("image/jpeg");
            } else if (pathKey.contains(".png")) {
                metadata.setContentType("image/png");
            } else if (pathKey.contains(".json")) {
                metadata.setContentType("application/json");
            }
            stream = FileUtil.getInputStream(new File(filePath));
            metadata.setContentLength(stream.available());
            PutObjectRequest request = new PutObjectRequest(bucket, pathKey,stream ,metadata);
            request.withCannedAcl(CannedAccessControlList.PublicRead);
            PutObjectResult putObjectResult = amazonS3Client.putObject(request);
        } catch (Exception e) {
            return "";
        } finally {
            IoUtil.close(stream);
        }
        return calculateUrl(bucket, pathKey);
    }

    @Override
    public String getFileContent(String bucket, String keyName) throws Exception {
        try (S3Object object = amazonS3Client.getObject(new GetObjectRequest(bucket,keyName));
             S3ObjectInputStream inputStream = object.getObjectContent();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))){
            StringBuilder content = new StringBuilder();
            while (true) {
                String line = reader.readLine();
                if (line == null) break;
                content.append(line);
            }
            return content.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) {
        List<String> keyList = new ArrayList<>();
        Long total = 0L;
        boolean flag = true;
        String nextMaker = null;
        ListObjectsRequest listObjectsRequest = new ListObjectsRequest();
        listObjectsRequest.setBucketName(bucket);
        listObjectsRequest.setPrefix(key);
        listObjectsRequest.setMaxKeys(200);

        do {
            listObjectsRequest.setMarker(nextMaker);
            ObjectListing objectListing = amazonS3Client.listObjects(listObjectsRequest);
            List<S3ObjectSummary> objectSummaries = objectListing.getObjectSummaries();
            Long space = objectSummaries.stream().mapToLong(S3ObjectSummary::getSize).sum();
            total += space;
            nextMaker = objectListing.getNextMarker();
            flag = objectListing.isTruncated();
        } while (flag);

        return total;
    }

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

    @Override
    @SneakyThrows
    public String getInternalEndpoint(String key) {
        return getInternalEndpoint(awsProperties.getBucket(), key);
    }
    @Override
    public void copyFolder(String sourcePath, String targetPath) {
        copyFolder(awsProperties.getBucket(),   sourcePath,  awsProperties.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.stream().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)
                .withMethod(HttpMethod.PUT).withExpiration(expiration);
        return amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest);
    }
    @Override
    public URL getPresignedUrl(String key) {
        return getPresignedUrl(awsProperties.getBucket(), key);
    }
}
