package com.ycwl.basic.storage.adapters;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.HttpMethod;
import com.amazonaws.Protocol;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.S3ClientOptions;
import com.amazonaws.services.s3.model.*;
import com.ycwl.basic.storage.entity.AwsOssStorageConfig;
import com.ycwl.basic.storage.entity.StorageConfig;
import com.ycwl.basic.storage.entity.StorageFileObject;
import com.ycwl.basic.storage.enums.StorageAcl;
import com.ycwl.basic.storage.exceptions.StorageConfigException;
import com.ycwl.basic.storage.exceptions.StorageException;
import com.ycwl.basic.storage.exceptions.UploadFileFailedException;
import com.ycwl.basic.storage.utils.StorageUtil;
import org.apache.commons.lang3.StringUtils;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class AwsOssAdapter extends AStorageAdapter {
    private AwsOssStorageConfig config;

    @Override
    public void loadConfig(Map<String, String> _config) {
        AwsOssStorageConfig config = new AwsOssStorageConfig();
        config.setAccessKeyId(_config.get("accessKeyId"));
        config.setAccessKeySecret(_config.get("accessKeySecret"));
        config.setBucketName(_config.get("bucketName"));
        config.setEndpoint(_config.get("endpoint"));
        config.setRegion(_config.get("region"));
        config.setUrl(_config.get("url"));
        config.setPrefix(_config.get("prefix"));
        config.checkEverythingOK();
        this.config = config;
    }

    @Override
    public void setConfig(StorageConfig config) {
        if (config == null) {
            throw new StorageConfigException("配置为空");
        }
        if (config instanceof AwsOssStorageConfig) {
            this.config = (AwsOssStorageConfig) config;
        } else {
            throw new StorageConfigException("配置类型错误，传入的类为：" + config.getClass().getName());
        }
    }

    @Override
    public String uploadFile(String contentType, InputStream inputStream, String... path) {
        if (inputStream == null) {
            return null;
        }
        String fullPath = buildPath(path);
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(inputStream.available());
            if (StringUtils.isNotBlank(contentType)) {
                metadata.setContentType(contentType);
            }
            PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, inputStream, metadata);
            putObjectRequest.withCannedAcl(CannedAccessControlList.PublicRead); // 设置访问权限，让所有用户都允许访问
            s3Client.putObject(putObjectRequest);
            return getUrl(path);
        } catch (Exception e) {
            throw new UploadFileFailedException("上传文件失败：" + e.getMessage());
        }
    }

    @Override
    public boolean deleteFile(String... path) {
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            s3Client.deleteObject(config.getBucketName(), buildPath(path));
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public String getUrl(String... path) {
        return config.getUrl() + "/" + buildPath(path);
    }

    @Override
    public String getUrlForDownload(Date expireDate, String... path) {
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            URL url = s3Client.generatePresignedUrl(config.getBucketName(), buildPath(path), expireDate);
            return url.toString();
        }
    }

    @Override
    public String getUrlForUpload(Date expireDate, String contentType, String... path) {
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(config.getBucketName(), buildPath(path));
            request.setMethod(HttpMethod.PUT);
            if (StringUtils.isNotBlank(contentType)) {
                request.setContentType(contentType);
            }
            request.setExpiration(expireDate);
            URL url = s3Client.generatePresignedUrl(request);
            return url.toString();
        }
    }

    @Override
    public List<StorageFileObject> listDir(String... path) {
        ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request()
                .withBucketName(config.getBucketName())
                .withPrefix(buildPath(path) + "/")
                .withMaxKeys(1000);
        boolean isTruncated = true;
        String continuationToken = null;
        List<S3ObjectSummary> objectList = new ArrayList<>();
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            while (isTruncated) {
                if (continuationToken != null) {
                    listObjectsV2Request.setContinuationToken(continuationToken);
                }

                // 列举文件。
                ListObjectsV2Result result = s3Client.listObjectsV2(listObjectsV2Request);

                objectList.addAll(result.getObjectSummaries());

                isTruncated = result.isTruncated();
                continuationToken = result.getNextContinuationToken();
            }
            return objectList.stream().map(item -> {
                StorageFileObject object = new StorageFileObject();
                object.setPath(getRelativePath(item.getKey().substring(0, item.getKey().lastIndexOf("/"))));
                object.setName(item.getKey().substring(item.getKey().lastIndexOf("/") + 1));
                object.setSize(item.getSize());
                object.setRawObject(item);
                return object;
            }).collect(Collectors.toList());
        } catch (Exception e) {
            throw new StorageException("列举文件失败：" + e.getMessage());
        }
    }

    @Override
    public boolean deleteDir(String... path) {
        List<StorageFileObject> objectList = listDir(buildPath(path));
        if (objectList.isEmpty()) {
            return true;
        }
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            int idx = 0;
            int batchSize = 999;
            while (objectList.size() > idx) {
                if (objectList.size() - idx < batchSize) {
                    batchSize = objectList.size() - idx;
                }
                List<StorageFileObject> subList = objectList.subList(idx, idx + batchSize);
                idx += batchSize;
                DeleteObjectsRequest request = new DeleteObjectsRequest(config.getBucketName())
                        .withKeys(subList.stream().map(StorageFileObject::getFullPath).toArray(String[]::new));
                try {
                    s3Client.deleteObjects(request);
                } catch (Exception e) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private CannedAccessControlList convertAcl(StorageAcl acl) {
        return switch (acl) {
            case PUBLIC_READ -> CannedAccessControlList.PublicRead;
            case PUBLIC_READ_WRITE -> CannedAccessControlList.PublicReadWrite;
            case PRIVATE -> CannedAccessControlList.Private;
            case AUTHENTICATED_READ -> CannedAccessControlList.AuthenticatedRead;
            default -> CannedAccessControlList.PublicRead;
        };
    }

    @Override
    public boolean setAcl(StorageAcl acl, String... path) {
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            s3Client.setObjectAcl(config.getBucketName(), buildPath(path), convertAcl(acl));
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean isExists(String... path) {
        try (S3Wrapper wrapper = getS3Client()) {
            AmazonS3Client s3Client = wrapper.s3Client();
            return s3Client.doesObjectExist(config.getBucketName(), buildPath(path));
        } catch (Exception e) {
            return false;
        }
    }

    private S3Wrapper getS3Client() {
        BasicAWSCredentials basicAwsCred = new BasicAWSCredentials(config.getAccessKeyId(), config.getAccessKeySecret());
        ClientConfiguration clientConfiguration = new ClientConfiguration();
        clientConfiguration.setProtocol(Protocol.HTTPS);
        AmazonS3Client s3 = new AmazonS3Client(basicAwsCred,clientConfiguration);
        S3ClientOptions options = S3ClientOptions.builder().setPathStyleAccess(true).setPayloadSigningEnabled(true).disableChunkedEncoding().build();
        s3.setS3ClientOptions(options);
        s3.setEndpoint(config.getEndpoint());
        return new S3Wrapper(s3);
    }

    private String buildPath(String... paths) {
        if (StringUtils.isNotBlank(config.getPrefix())) {
            return StorageUtil.joinPath(config.getPrefix(), paths);
        } else {
            return StorageUtil.joinPath(paths);
        }
    }

    private String getRelativePath(String path) {
        return StorageUtil.getRelativePath(path, config.getPrefix());
    }

    public record S3Wrapper(AmazonS3Client s3Client) implements AutoCloseable {
        @Override
        public void close() {
            s3Client.shutdown();
        }
    }
}