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 volatile AwsOssStorageConfig config;
    private volatile AmazonS3Client s3Client;
    private final Object clientLock = new Object();
    private final Object configLock = new Object();

    @Override
    public String identity() {
        AwsOssStorageConfig currentConfig = this.config;
        if (currentConfig == null) {
            return "AWS_OSS_UNCONFIGURED";
        }
        return currentConfig.identity();
    }

    @Override
    public void loadConfig(Map<String, String> _config) {
        AwsOssStorageConfig newConfig = new AwsOssStorageConfig();
        newConfig.setAccessKeyId(_config.get("accessKeyId"));
        newConfig.setAccessKeySecret(_config.get("accessKeySecret"));
        newConfig.setBucketName(_config.get("bucketName"));
        newConfig.setEndpoint(_config.get("endpoint"));
        newConfig.setRegion(_config.get("region"));
        newConfig.setUrl(_config.get("url"));
        newConfig.setPrefix(_config.get("prefix"));
        newConfig.checkEverythingOK();
        
        synchronized (configLock) {
            this.config = newConfig;
            // 配置更新后，需要重置客户端连接
            resetClient();
        }
    }

    @Override
    public void setConfig(StorageConfig config) {
        if (config == null) {
            throw new StorageConfigException("配置为空");
        }
        if (!(config instanceof AwsOssStorageConfig)) {
            throw new StorageConfigException("配置类型错误，传入的类为：" + config.getClass().getName());
        }
        
        synchronized (configLock) {
            this.config = (AwsOssStorageConfig) config;
            // 配置更新后，需要重置客户端连接
            resetClient();
        }
    }

    @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();
            
            // 读取完整内容并计算实际长度
            byte[] bytes = inputStream.readAllBytes();
            metadata.setContentLength(bytes.length);
            
            if (StringUtils.isNotBlank(contentType)) {
                metadata.setContentType(contentType);
            }
            
            // 使用字节数组创建新的 InputStream
            try (InputStream byteInputStream = new java.io.ByteArrayInputStream(bytes)) {
                PutObjectRequest putObjectRequest = new PutObjectRequest(config.getBucketName(), fullPath, byteInputStream, 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.setModifyTime(item.getLastModified());
                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() {
        AwsOssStorageConfig currentConfig = this.config;
        if (currentConfig == null) {
            throw new StorageConfigException("存储适配器未配置");
        }
        
        if (s3Client == null) {
            synchronized (clientLock) {
                if (s3Client == null) {
                    // 在同步块内再次检查配置，确保配置一致性
                    currentConfig = this.config;
                    if (currentConfig == null) {
                        throw new StorageConfigException("存储适配器未配置");
                    }
                    BasicAWSCredentials basicAwsCred = new BasicAWSCredentials(
                        currentConfig.getAccessKeyId(), 
                        currentConfig.getAccessKeySecret()
                    );
                    ClientConfiguration clientConfiguration = new ClientConfiguration();
                    clientConfiguration.setProtocol(Protocol.HTTPS);
                    s3Client = new AmazonS3Client(basicAwsCred, clientConfiguration);
                    S3ClientOptions options = S3ClientOptions.builder()
                        .setPathStyleAccess(true)
                        .setPayloadSigningEnabled(true)
                        .disableChunkedEncoding()
                        .build();
                    s3Client.setS3ClientOptions(options);
                    s3Client.setEndpoint(currentConfig.getEndpoint());
                }
            }
        }
        return new S3Wrapper(s3Client, false); // false 表示不自动关闭共享客户端
    }
    
    // 重置客户端连接（配置更新时调用）
    private void resetClient() {
        synchronized (clientLock) {
            if (s3Client != null) {
                s3Client.shutdown();
                s3Client = null;
            }
        }
    }
    
    // 手动关闭客户端的方法，用于适配器销毁时
    public void shutdown() {
        synchronized (clientLock) {
            if (s3Client != null) {
                s3Client.shutdown();
                s3Client = null;
            }
        }
    }

    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, boolean autoClose) implements AutoCloseable {
        
        public S3Wrapper(AmazonS3Client s3Client) {
            this(s3Client, true);
        }
        
        @Override
        public void close() {
            // 只有非共享客户端才自动关闭
            if (autoClose && s3Client != null) {
                s3Client.shutdown();
            }
        }
    }
}