package com.dji.sample.component.oss.service.impl;

import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.service.IOssService;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sdk.cloudapi.storage.CredentialsToken;
import com.dji.sdk.cloudapi.storage.OssTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import javax.annotation.PreDestroy;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * VersityGW S3 兼容实现（AWS SDK v2 + path-style）。
 * <p>
 * STS：默认通过 Admin API 创建短期 IAM 用户（{@code oss.sts-mode=temp}），到期后删除；
 * 仍以明文 AK/SK 下发，但不再泄露长期 root，且可回收。
 */
@Service
@Slf4j
public class VersityGwServiceImpl implements IOssService {

    private static final SecureRandom RANDOM = new SecureRandom();
    private static final char[] SECRET_CHARS =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();

    private S3Client client;
    private S3Presigner presigner;

    @Autowired
    private VersityGwAdminClient adminClient;

    @Override
    public OssTypeEnum getOssType() {
        return OssTypeEnum.VERSITYGW;
    }

    @Override
    public CredentialsToken getCredentials() {
        if ("static".equalsIgnoreCase(OssConfiguration.stsMode)) {
            return new CredentialsToken(
                    OssConfiguration.accessKey,
                    OssConfiguration.secretKey,
                    "",
                    OssConfiguration.expire);
        }
        return createTempCredentials();
    }

    private CredentialsToken createTempCredentials() {
        String accessKey = "tmp" + UUID.randomUUID().toString().replace("-", "").substring(0, 17);
        String secretKey = randomSecret(40);
        String role = StringUtils.hasText(OssConfiguration.stsUserRole)
                ? OssConfiguration.stsUserRole : "admin";
        adminClient.createUser(accessKey, secretKey, role);

        long expireSec = OssConfiguration.expire == null ? 3600L : OssConfiguration.expire;
        long expireAt = System.currentTimeMillis() / 1000 + expireSec;
        RedisOpsUtils.hashSet(RedisConst.VGW_STS_USERS, accessKey, expireAt);

        return new CredentialsToken(accessKey, secretKey, "", expireSec);
    }

    private static String randomSecret(int length) {
        char[] buf = new char[length];
        for (int i = 0; i < length; i++) {
            buf[i] = SECRET_CHARS[RANDOM.nextInt(SECRET_CHARS.length)];
        }
        return new String(buf);
    }

    /**
     * 清理已过期的临时 IAM 用户。
     */
    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
    public void cleanupExpiredTempUsers() {
        if (!OssConfiguration.enable || OssConfiguration.provider != OssTypeEnum.VERSITYGW) {
            return;
        }
        if ("static".equalsIgnoreCase(OssConfiguration.stsMode)) {
            return;
        }
        Set<Object> fields = RedisOpsUtils.hashKeys(RedisConst.VGW_STS_USERS);
        if (fields == null || fields.isEmpty()) {
            return;
        }
        long now = System.currentTimeMillis() / 1000;
        for (Object field : fields) {
            String accessKey = String.valueOf(field);
            Object value = RedisOpsUtils.hashGet(RedisConst.VGW_STS_USERS, accessKey);
            long expireAt;
            try {
                expireAt = Long.parseLong(String.valueOf(value));
            } catch (Exception e) {
                RedisOpsUtils.hashDel(RedisConst.VGW_STS_USERS, new Object[]{accessKey});
                continue;
            }
            if (expireAt > now) {
                continue;
            }
            try {
                adminClient.deleteUser(accessKey);
            } catch (Exception e) {
                log.warn("Failed to delete expired VersityGW user {}: {}", accessKey, e.getMessage());
            } finally {
                RedisOpsUtils.hashDel(RedisConst.VGW_STS_USERS, new Object[]{accessKey});
            }
        }
    }

    @Override
    public URL getObjectUrl(String bucket, String objectKey) {
        try {
            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofSeconds(OssConfiguration.expire))
                    .getObjectRequest(b -> b.bucket(bucket).key(objectKey))
                    .build();
            return presigner.presignGetObject(presignRequest).url();
        } catch (Exception e) {
            log.error("Failed to generate presigned URL for {}/{}", bucket, objectKey, e);
            throw new RuntimeException("The file does not exist on the OssConfiguration.");
        }
    }

    @Override
    public Boolean deleteObject(String bucket, String objectKey) {
        try {
            client.deleteObject(DeleteObjectRequest.builder().bucket(bucket).key(objectKey).build());
            return true;
        } catch (Exception e) {
            log.error("Failed to delete file {}/{}", bucket, objectKey, e);
            return false;
        }
    }

    @Override
    public InputStream getObject(String bucket, String objectKey) {
        try {
            byte[] bytes = client.getObjectAsBytes(
                    GetObjectRequest.builder().bucket(bucket).key(objectKey).build()).asByteArray();
            return new ByteArrayInputStream(bytes);
        } catch (Exception e) {
            log.error("Failed to get object {}/{}", bucket, objectKey, e);
            return InputStream.nullInputStream();
        }
    }

    @Override
    public void putObject(String bucket, String objectKey, InputStream input) {
        if (Boolean.TRUE.equals(objectExists(bucket, objectKey))) {
            throw new RuntimeException("The filename already exists.");
        }
        try {
            byte[] bytes = input.readAllBytes();
            PutObjectResponse response = client.putObject(
                    PutObjectRequest.builder().bucket(bucket).key(objectKey).build(),
                    RequestBody.fromBytes(bytes));
            log.info("Upload FlighttaskCreateFile: {}", response.eTag());
        } catch (Exception e) {
            log.error("Failed to upload FlighttaskCreateFile {}.", objectKey, e);
        }
    }

    @Override
    public void createClient() {
        if (Objects.nonNull(this.client)) {
            return;
        }
        StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
                AwsBasicCredentials.create(OssConfiguration.accessKey, OssConfiguration.secretKey));
        S3Configuration s3Configuration = S3Configuration.builder()
                .pathStyleAccessEnabled(true)
                .chunkedEncodingEnabled(false)
                .build();
        URI endpoint = URI.create(OssConfiguration.endpoint);
        Region region = Region.of(OssConfiguration.region);

        this.client = S3Client.builder()
                .endpointOverride(endpoint)
                .region(region)
                .credentialsProvider(credentialsProvider)
                .serviceConfiguration(s3Configuration)
                .build();

        this.presigner = S3Presigner.builder()
                .endpointOverride(endpoint)
                .region(region)
                .credentialsProvider(credentialsProvider)
                .serviceConfiguration(s3Configuration)
                .build();
    }

    @PreDestroy
    public void destroy() {
        if (presigner != null) {
            presigner.close();
            presigner = null;
        }
        if (client != null) {
            client.close();
            client = null;
        }
    }

    @Override
    public List<Map<String, String>> listFolders(String bucket, String prefix) {
        List<Map<String, String>> folders = new ArrayList<>();
        try {
            String normalizedPrefix = prefix == null ? "" : prefix;
            if (!normalizedPrefix.isEmpty() && !normalizedPrefix.endsWith("/")) {
                normalizedPrefix += "/";
            }
            final String listPrefix = normalizedPrefix;
            String continuationToken = null;
            do {
                ListObjectsV2Request.Builder requestBuilder = ListObjectsV2Request.builder()
                        .bucket(bucket)
                        .prefix(listPrefix)
                        .delimiter("/");
                if (continuationToken != null) {
                    requestBuilder.continuationToken(continuationToken);
                }
                ListObjectsV2Response response = client.listObjectsV2(requestBuilder.build());
                if (response.commonPrefixes() != null) {
                    for (CommonPrefix commonPrefix : response.commonPrefixes()) {
                        String folderPath = commonPrefix.prefix();
                        String folderName = folderPath.substring(listPrefix.length());
                        if (folderName.endsWith("/")) {
                            folderName = folderName.substring(0, folderName.length() - 1);
                        }
                        Map<String, String> folderInfo = new HashMap<>();
                        folderInfo.put("name", folderName);
                        folderInfo.put("prefix", folderPath);
                        folders.add(folderInfo);
                    }
                }
                continuationToken = Boolean.TRUE.equals(response.isTruncated())
                        ? response.nextContinuationToken()
                        : null;
            } while (continuationToken != null);
        } catch (Exception e) {
            log.error("Failed to list folders: {}", e.getMessage());
        }
        return folders;
    }

    @Override
    public Boolean objectExists(String bucket, String objectKey) {
        try {
            client.headObject(HeadObjectRequest.builder().bucket(bucket).key(objectKey).build());
            return true;
        } catch (NoSuchKeyException e) {
            return false;
        } catch (S3Exception e) {
            if (e.statusCode() == 404) {
                return false;
            }
            throw e;
        }
    }

    @Override
    public Boolean copyObject(String bucket, String sourceKey, String destKey) {
        try {
            client.copyObject(CopyObjectRequest.builder()
                    .sourceBucket(bucket)
                    .sourceKey(sourceKey)
                    .destinationBucket(bucket)
                    .destinationKey(destKey)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("Failed to copy object: {}", e.getMessage());
            return false;
        }
    }
}
