package shop.alien.store.util.oss;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * OSS直传工具类
 * 支持：签名生成、分片上传、断点续传、上传进度
 *
 * @author system
 * @date 2025-01-XX
 */
@Slf4j
@Component
@RefreshScope
public class OSSDirectUploadUtil {

    @Value("${ali.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${ali.oss.accessKeySecret}")
    private String accessKeySecret;

    @Value("${ali.oss.endPoint}")
    private String endPoint;

    @Value("${ali.oss.bucketName}")
    private String bucketName;

    // 默认过期时间：1小时
    private static final long DEFAULT_EXPIRE_TIME = 3600 * 1000L;

    /**
     * 生成OSS直传签名（Post Policy方式）
     * 
     * @param dir 上传目录（如：video/、image/）
     * @param fileName 文件名（可选，不传则自动生成）
     * @param maxSize 最大文件大小（字节，默认100MB）
     * @param expireTime 过期时间（毫秒，默认1小时）
     * @return 签名信息
     */
    public OSSSignatureResult generatePostSignature(String dir, String fileName, Long maxSize, Long expireTime) {
        try {
            // 参数校验和默认值
            if (StringUtils.isEmpty(dir)) {
                dir = "upload/";
            }
            if (!dir.endsWith("/")) {
                dir += "/";
            }
            if (StringUtils.isEmpty(fileName)) {
                fileName = UUID.randomUUID().toString();
            }
            if (maxSize == null || maxSize <= 0) {
                maxSize = 100 * 1024 * 1024L; // 默认100MB
            }
            if (expireTime == null || expireTime <= 0) {
                expireTime = DEFAULT_EXPIRE_TIME;
            }

            // 生成OSS文件路径
            String ossKey = dir + fileName;
            
            // 过期时间（Unix时间戳，秒）
            long expire = (System.currentTimeMillis() + expireTime) / 1000;
            Date expiration = new Date(expire * 1000);
            
            // 构建Post Policy
            // 使用 JSONObject 和 JSONArray 确保格式完全符合 OSS 规范
            JSONObject policyObj = new JSONObject();
            
            // 1. 设置过期时间（必须未来时间，ISO8601 格式，UTC 时区）
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
            String expirationStr = sdf.format(expiration);
            policyObj.put("expiration", expirationStr);
            
            // 2. 构建 conditions 数组（必须严格按照 OSS 规范）
            // OSS 支持的匹配方式：content-length-range, eq, starts-with, in, not-in
            // 注意：不支持 ends-with 等其他匹配方式
            JSONArray conditions = new JSONArray();
            
            // bucket 条件：["eq", "$bucket", "bucket-name"]（必须使用 eq 和 $bucket）
            JSONArray bucketCondition = new JSONArray();
            bucketCondition.add("eq");
            bucketCondition.add("$bucket");
            bucketCondition.add(bucketName);
            conditions.add(bucketCondition);
            
            // key 条件：["starts-with", "$key", "dir/"]（前缀匹配，允许上传到指定目录下的任意文件）
            // 使用 starts-with 而不是 eq，这样前端可以上传任意文件名到指定目录
            // 注意：dir 已经确保以 "/" 结尾
            JSONArray keyCondition = new JSONArray();
            keyCondition.add("starts-with");
            keyCondition.add("$key");
            keyCondition.add(dir);  // 使用目录前缀，如 "uploads/report/"
            conditions.add(keyCondition);
            
            // 文件大小范围：["content-length-range", "0", "maxSize"]
            JSONArray sizeCondition = new JSONArray();
            sizeCondition.add("content-length-range");
            sizeCondition.add("0");
            sizeCondition.add(String.valueOf(maxSize));
            conditions.add(sizeCondition);
            
            // 成功返回状态码：["eq", "$success_action_status", "200"]（可选，但推荐）
            JSONArray statusCondition = new JSONArray();
            statusCondition.add("eq");
            statusCondition.add("$success_action_status");
            statusCondition.add("200");
            conditions.add(statusCondition);
            
            policyObj.put("conditions", conditions);
            
            // 3. 转换为 JSON 字符串（使用 toJSONString 确保格式正确）
            String policyJson = policyObj.toJSONString();
            
            // 4. 验证 JSON 是否有效
            try {
                JSON.parseObject(policyJson);
                log.info("Policy JSON 验证通过: {}", policyJson);
            } catch (Exception e) {
                log.error("Policy JSON 格式无效: {}", policyJson, e);
                throw new RuntimeException("生成的 Policy JSON 格式无效", e);
            }
            
            // 5. Base64 编码（确保没有换行符）
            String policyBase64 = BinaryUtil.toBase64String(policyJson.getBytes(StandardCharsets.UTF_8));
            // 移除可能的换行符（Base64 编码不应该包含换行符）
            policyBase64 = policyBase64.replaceAll("\\s+", "");
            log.info("Policy Base64: {}", policyBase64);
            
            // 6. 验证 Base64 解码后是否为合法 JSON
            try {
                String decoded = new String(java.util.Base64.getDecoder().decode(policyBase64), StandardCharsets.UTF_8);
                JSON.parseObject(decoded);
                log.info("Base64 解码验证通过，解码后的 JSON: {}", decoded);
            } catch (Exception e) {
                log.error("Base64 解码后不是合法 JSON，原始 Base64: {}", policyBase64, e);
                throw new RuntimeException("Base64 解码后不是合法 JSON", e);
            }



            // 计算签名
            String signature = calculateSignature(policyBase64);
            
            // 构建返回结果
            OSSSignatureResult result = new OSSSignatureResult();
            result.setAccessKeyId(accessKeyId);
            result.setPolicy(policyBase64);
            result.setSignature(signature);
            result.setDir(dir);
            result.setHost("https://" + bucketName + "." + endPoint);
            result.setExpire(String.valueOf(expire));
            result.setOssKey(ossKey);
            result.setCallbackUrl(""); // 回调URL（可选）
            result.setCallbackBody(""); // 回调Body（可选）
            
            // 输出完整的签名信息（用于调试）
            log.info("生成OSS直传签名成功: dir={}, fileName={}, ossKey={}", dir, fileName, ossKey);
            log.info("签名信息 - Policy长度: {}, Signature: {}", policyBase64.length(), signature);
            
            return result;
            
        } catch (Exception e) {
            log.error("生成OSS直传签名失败: {}", e.getMessage(), e);
            throw new RuntimeException("生成OSS签名失败: " + e.getMessage(), e);
        }
    }

    /**
     * 构建 Post Policy JSON
     * 按照 OSS 规范：先生成合法的 JSON 字符串，再进行 Base64 编码
     * 使用 JSONObject 和 JSONArray 手动构建，确保格式完全符合 OSS 要求
     */
    private String buildPostPolicy(String bucket, String key, long maxSize, Date expiration) {
        try {
            // 1. 设置过期时间（必须未来时间，ISO8601 格式，UTC 时区）
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
            String expirationStr = sdf.format(expiration);
            
            // 2. 使用 JSONObject 和 JSONArray 手动构建 Policy JSON
            // 这样可以确保格式完全符合 OSS 规范，避免 FastJSON 自动序列化的问题
            JSONObject policyObj = new JSONObject();
            policyObj.put("expiration", expirationStr);
            
            // 3. 构建 conditions 数组（必须严格按照 OSS 规范）
            JSONArray conditions = new JSONArray();
            
            // bucket 条件：["eq", "$bucket", "bucket-name"]
            JSONArray bucketCondition = new JSONArray();
            bucketCondition.add("eq");
            bucketCondition.add("$bucket");
            bucketCondition.add(bucket);
            conditions.add(bucketCondition);
            
            // key 条件：["eq", "$key", "file-path"]
            JSONArray keyCondition = new JSONArray();
            keyCondition.add("eq");
            keyCondition.add("$key");
            keyCondition.add(key);
            conditions.add(keyCondition);
            
            // 文件大小范围：["content-length-range", "0", "maxSize"]
            // 注意：OSS 要求所有参数都是字符串格式
            JSONArray sizeCondition = new JSONArray();
            sizeCondition.add("content-length-range");
            sizeCondition.add("0");
            sizeCondition.add(String.valueOf(maxSize));
            conditions.add(sizeCondition);
            
            policyObj.put("conditions", conditions);
            
            // 4. 转换为 JSON 字符串（使用 toJSONString 确保格式正确）
            String policyJson = policyObj.toJSONString();
            
            // 5. 验证 JSON 是否有效（用于调试）
            try {
                JSON.parseObject(policyJson);
                log.info("Policy JSON 验证通过: {}", policyJson);
            } catch (Exception e) {
                log.error("Policy JSON 格式无效: {}", policyJson, e);
                throw new RuntimeException("生成的 Policy JSON 格式无效", e);
            }
            
            // 6. Base64 编码后返回
            String policyBase64 = BinaryUtil.toBase64String(policyJson.getBytes(StandardCharsets.UTF_8));
            log.info("Policy Base64: {}", policyBase64);
            
            // 7. 验证 Base64 解码后是否为合法 JSON（用于调试）
            try {
                String decoded = new String(java.util.Base64.getDecoder().decode(policyBase64), StandardCharsets.UTF_8);
                JSON.parseObject(decoded);
                log.info("Base64 解码验证通过，解码后的 JSON: {}", decoded);
            } catch (Exception e) {
                log.error("Base64 解码后不是合法 JSON，原始 Base64: {}", policyBase64, e);
                // 尝试手动解码查看内容
                try {
                    String decoded = new String(java.util.Base64.getDecoder().decode(policyBase64), StandardCharsets.UTF_8);
                    log.error("Base64 解码后的内容: {}", decoded);
                } catch (Exception decodeEx) {
                    log.error("无法解码 Base64: {}", decodeEx.getMessage());
                }
                throw new RuntimeException("Base64 解码后不是合法 JSON", e);
            }
            
            return policyBase64;
        } catch (Exception e) {
            log.error("构建 Post Policy 失败：{}", e.getMessage(), e);
            throw new RuntimeException("构建 Post Policy 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 计算签名
     */
    private String calculateSignature(String policy) {
        try {
            Mac hmac = Mac.getInstance("HmacSHA256");
            hmac.init(new SecretKeySpec(accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byte[] signData = hmac.doFinal(policy.getBytes(StandardCharsets.UTF_8));
            return BinaryUtil.toBase64String(signData);
        } catch (Exception e) {
            log.error("计算签名失败: {}", e.getMessage(), e);
            throw new RuntimeException("计算签名失败", e);
        }
    }

    /**
     * 初始化分片上传
     * 
     * @param ossKey OSS文件路径
     * @return 上传ID
     */
    public String initMultipartUpload(String ossKey) {
        OSS ossClient = null;
        try {
            ossClient = createOSSClient();
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, ossKey);
            InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
            String uploadId = result.getUploadId();
            log.info("初始化分片上传成功: ossKey={}, uploadId={}", ossKey, uploadId);
            return uploadId;
        } catch (Exception e) {
            log.error("初始化分片上传失败: ossKey={}, error={}", ossKey, e.getMessage(), e);
            throw new RuntimeException("初始化分片上传失败: " + e.getMessage(), e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 上传分片
     * 
     * @param ossKey OSS文件路径
     * @param uploadId 上传ID
     * @param partNumber 分片序号（从1开始）
     * @param partData 分片数据
     * @return 分片ETag
     */
    public String uploadPart(String ossKey, String uploadId, int partNumber, byte[] partData) {
        OSS ossClient = null;
        try {
            ossClient = createOSSClient();
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(ossKey);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setPartNumber(partNumber);
            uploadPartRequest.setInputStream(new java.io.ByteArrayInputStream(partData));
            uploadPartRequest.setPartSize(partData.length);
            
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            String eTag = uploadPartResult.getETag();
            log.debug("上传分片成功: ossKey={}, partNumber={}, eTag={}", ossKey, partNumber, eTag);
            return eTag;
        } catch (Exception e) {
            log.error("上传分片失败: ossKey={}, partNumber={}, error={}", ossKey, partNumber, e.getMessage(), e);
            throw new RuntimeException("上传分片失败: " + e.getMessage(), e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 完成分片上传（合并所有分片）
     * 
     * @param ossKey OSS文件路径
     * @param uploadId 上传ID
     * @param partETags 所有分片的ETag列表
     * @return 文件URL
     */
    public String completeMultipartUpload(String ossKey, String uploadId, List<PartETag> partETags) {
        OSS ossClient = null;
        try {
            ossClient = createOSSClient();
            CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(
                    bucketName, ossKey, uploadId, partETags);
            CompleteMultipartUploadResult completeResult = ossClient.completeMultipartUpload(completeRequest);
            
            String fileUrl = "https://" + bucketName + "." + endPoint + "/" + ossKey;
            log.info("完成分片上传: ossKey={}, uploadId={}, fileUrl={}", ossKey, uploadId, fileUrl);
            return fileUrl;
        } catch (Exception e) {
            log.error("完成分片上传失败: ossKey={}, uploadId={}, error={}", ossKey, uploadId, e.getMessage(), e);
            throw new RuntimeException("完成分片上传失败: " + e.getMessage(), e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 取消分片上传（清理未完成的分片）
     * 
     * @param ossKey OSS文件路径
     * @param uploadId 上传ID
     */
    public void abortMultipartUpload(String ossKey, String uploadId) {
        OSS ossClient = null;
        try {
            ossClient = createOSSClient();
            AbortMultipartUploadRequest abortRequest = new AbortMultipartUploadRequest(bucketName, ossKey, uploadId);
            ossClient.abortMultipartUpload(abortRequest);
            log.info("取消分片上传: ossKey={}, uploadId={}", ossKey, uploadId);
        } catch (Exception e) {
            log.error("取消分片上传失败: ossKey={}, uploadId={}, error={}", ossKey, uploadId, e.getMessage(), e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 列出已上传的分片
     * 
     * @param ossKey OSS文件路径
     * @param uploadId 上传ID
     * @return 已上传的分片列表
     */
    public List<PartSummary> listParts(String ossKey, String uploadId) {
        OSS ossClient = null;
        try {
            ossClient = createOSSClient();
            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, ossKey, uploadId);
            PartListing partListing = ossClient.listParts(listPartsRequest);
            return partListing.getParts();
        } catch (Exception e) {
            log.error("列出分片失败: ossKey={}, uploadId={}, error={}", ossKey, uploadId, e.getMessage(), e);
            throw new RuntimeException("列出分片失败: " + e.getMessage(), e);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 验证OSS回调签名
     * 
     * @param authorizationHeader Authorization头
     * @param pubKeyUrl 公钥URL
     * @param callbackBody 回调Body
     * @return 是否验证通过
     */
    public boolean verifyCallback(String authorizationHeader, String pubKeyUrl, String callbackBody) {
        try {
            // 从公钥URL获取公钥
            String pubKey = getPublicKey(pubKeyUrl);
            
            // 验证签名
            String[] authParts = authorizationHeader.split(":");
            if (authParts.length != 2) {
                return false;
            }
            
            String signature = authParts[1];
            String stringToSign = callbackBody;
            
            // 使用公钥验证签名
            java.security.Signature signatureVerifier = java.security.Signature.getInstance("SHA1withRSA");
            signatureVerifier.initVerify(java.security.KeyFactory.getInstance("RSA")
                    .generatePublic(new java.security.spec.X509EncodedKeySpec(
                            Base64.getDecoder().decode(pubKey))));
            signatureVerifier.update(stringToSign.getBytes(StandardCharsets.UTF_8));
            
            return signatureVerifier.verify(Base64.getDecoder().decode(signature));
        } catch (Exception e) {
            log.error("验证OSS回调签名失败: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 从URL获取公钥
     */
    private String getPublicKey(String pubKeyUrl) {
        try {
            java.net.URL url = new java.net.URL(pubKeyUrl);
            java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.connect();
            
            StringBuilder response = new StringBuilder();
            try (java.io.BufferedReader reader = new java.io.BufferedReader(
                    new java.io.InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
            }
            
            // 移除公钥的头部和尾部
            String pubKey = response.toString();
            pubKey = pubKey.replace("-----BEGIN PUBLIC KEY-----", "");
            pubKey = pubKey.replace("-----END PUBLIC KEY-----", "");
            pubKey = pubKey.replace("\n", "");
            pubKey = pubKey.replace("\r", "");
            
            return pubKey;
        } catch (Exception e) {
            log.error("获取公钥失败: {}", e.getMessage(), e);
            throw new RuntimeException("获取公钥失败", e);
        }
    }



    /**
     * 生成 HMAC-SHA1 签名
     * @param data 待签名数据（Policy Base64）
     * @param secretKey 秘钥（AccessKeySecret）
     * @return 签名结果（Base64 编码）
     */
    private static String generateHmacSHA1Signature(String data, String secretKey) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA1");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
        mac.init(secretKeySpec);
        byte[] signBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return BinaryUtil.toBase64String(signBytes);
    }









    /**
     * 创建OSS客户端
     */
    private OSS createOSSClient() {
        com.aliyun.oss.ClientBuilderConfiguration config = new com.aliyun.oss.ClientBuilderConfiguration();
        config.setSignatureVersion(SignVersion.V4);
        return new OSSClientBuilder().build(endPoint, accessKeyId, accessKeySecret);
    }

    /**
     * OSS签名结果
     */
    /**
     * OSS签名结果
     * 注意：使用 @JSONField 确保字段名正确序列化
     */
    @Data
    public static class OSSSignatureResult {
        @JSONField(name = "accessKeyId")
        private String accessKeyId;
        
        @JSONField(name = "policy")
        private String policy;
        
        @JSONField(name = "signature")
        private String signature;
        
        @JSONField(name = "dir")
        private String dir;
        
        @JSONField(name = "host")
        private String host;
        
        @JSONField(name = "expire")
        private String expire;
        
        @JSONField(name = "ossKey")
        private String ossKey;
        
        @JSONField(name = "callbackUrl")
        private String callbackUrl;
        
        @JSONField(name = "callbackBody")
        private String callbackBody;
    }
}
