package shop.alien.store.controller;

import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.sts20150401.models.AssumeRoleResponse;
import com.aliyun.sts20150401.models.AssumeRoleResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * OSS直传新版本Controller（使用OSS4-HMAC-SHA256签名方式）
 * 
 * @author system
 * @date 2025-01-XX
 */
@Slf4j
@Api(tags = {"OSS直传新版本接口"})
@RestController
@RequestMapping("/oss/direct/new")
@RefreshScope
@CrossOrigin
public class OSSDirectUploadNewController {

    @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;

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

    public static String STATIC_ACCESS_KEY_ID;
    public static String STATIC_ACCESS_KEY_SECRET;
    public static String STATIC_END_POINT;
    public static String STATIC_BUCKET_NAME;
    public static String OSS_STS_ROLE_ARN;
    // 3. 初始化方法：对象创建后立即执行，把非静态值赋给静态变量
    @PostConstruct
    public void init() {
        STATIC_ACCESS_KEY_ID = this.accessKeyId;
        STATIC_ACCESS_KEY_SECRET = this.accessKeySecret;
        STATIC_END_POINT = this.endPoint;
        STATIC_BUCKET_NAME = this.bucketName;
        OSS_STS_ROLE_ARN = this.ossStsRoleArn;
    }


    // 从endPoint提取region（例如：oss-cn-beijing.aliyuncs.com -> cn-beijing）
    private String getRegion() {
        if (endPoint != null && endPoint.contains("oss-")) {
            int start = endPoint.indexOf("oss-") + 4;
            int end = endPoint.indexOf(".aliyuncs.com");
            if (end > start) {
                return endPoint.substring(start, end);
            }
        }
        return "cn-beijing"; // 默认值
    }

    /**
     * 从 OSS Endpoint 得到 STS Endpoint。获取临时凭证必须使用 STS 端点，不能使用 OSS 端点。
     * 例如：oss-cn-beijing.aliyuncs.com -> sts.cn-beijing.aliyuncs.com
     */
    private static String getStsEndpointFromOssEndpoint(String ossEndpoint) {
        if (ossEndpoint != null && ossEndpoint.contains("oss-")) {
            int start = ossEndpoint.indexOf("oss-") + 4;
            int end = ossEndpoint.indexOf(".aliyuncs.com");
            if (end > start) {
                String region = ossEndpoint.substring(start, end);
                return "sts." + region + ".aliyuncs.com";
            }
        }
        return "sts.cn-beijing.aliyuncs.com";
    }

    // 构建host地址
    private String getHost() {
        return "https://" + bucketName + "." + endPoint;
    }

    // 设置上传回调URL（可选，如果需要回调功能）
    @Value("${ali.oss.callbackUrl:}")
    private String callbackUrl;

    // 限定上传到OSS的文件前缀（默认值）
    @Value("${ali.oss.uploadDir:upload/}")
    private String uploadDir;

    // 指定过期时间，单位为秒（默认1小时）
    @Value("${ali.oss.expireTime:3600}")
    private Long expireTime;

    /**
     * 通过指定有效的时长（秒）生成过期时间。
     * @param seconds 有效时长（秒）。
     * @return ISO8601 时间字符串，如："2014-12-01T12:00:00.000Z"。
     */
    public static String generateExpiration(long seconds) {
        // 获取当前时间戳（以秒为单位）
        long now = Instant.now().getEpochSecond();
        // 计算过期时间的时间戳
        long expirationTime = now + seconds;
        // 将时间戳转换为Instant对象，并格式化为ISO8601格式
        Instant instant = Instant.ofEpochSecond(expirationTime);
        // 定义时区为UTC
        ZoneId zone = ZoneOffset.UTC;
        // 将 Instant 转换为 ZonedDateTime
        ZonedDateTime zonedDateTime = instant.atZone(zone);
        // 定义日期时间格式，例如2023-12-03T13:00:00.000Z
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        // 格式化日期时间
        String formattedDate = zonedDateTime.format(formatter);
        // 输出结果
        return formattedDate;
    }
    //初始化STS Client（如果需要使用STS，取消注释并配置相关环境变量）
    public static com.aliyun.sts20150401.Client createStsClient() throws Exception {
        // 工程代码泄露可能会导致 AccessKey 泄露，并威胁账号下所有资源的安全性。以下代码示例仅供参考。
        // 建议使用更安全的 STS 方式。
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                // 必填，请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_ID。
                .setAccessKeyId(STATIC_ACCESS_KEY_ID)
                // 必填，请确保代码运行环境设置了环境变量 OSS_ACCESS_KEY_SECRET。
                .setAccessKeySecret(STATIC_ACCESS_KEY_SECRET);
        // 获取临时凭证必须使用 STS 端点，不能使用 OSS 端点。参见 https://api.aliyun.com/product/Sts
        config.endpoint = getStsEndpointFromOssEndpoint(STATIC_END_POINT);
        return new com.aliyun.sts20150401.Client(config);
    }

    //获取STS临时凭证
    // 注意：此方法需要STS服务支持，如果不需要STS，可以直接使用AccessKey
    // 这里暂时注释掉STS相关代码，直接使用配置的AccessKey
    public static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials getCredential() throws Exception {
        com.aliyun.sts20150401.Client client = OSSDirectUploadNewController.createStsClient();
        com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest()
                // 必填，请确保代码运行环境设置了环境变量 OSS_STS_ROLE_ARN
                .setRoleArn(OSS_STS_ROLE_ARN)
                .setRoleSessionName("ossDirectPass");// 自定义会话名称
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        try {
            // 复制代码运行请自行打印 API 的返回值
            AssumeRoleResponse response = client.assumeRoleWithOptions(assumeRoleRequest, runtime);
            // credentials里包含了后续要用到的AccessKeyId、AccessKeySecret和SecurityToken。
            return response.body.credentials;
        } catch (Exception error) {
            String msg = error.getMessage() != null ? error.getMessage() : "";
            if (msg.contains("NoSuchBucket") || msg.contains("bucket does not exist")) {
                log.error("指定的Bucket不存在，请检查配置 ali.oss.bucketName: {}", STATIC_BUCKET_NAME, error);
                throw new RuntimeException("指定的Bucket不存在，请检查配置 ali.oss.bucketName: " + STATIC_BUCKET_NAME, error);
            }
            if (msg.contains("403") || msg.contains("not authorized") || msg.contains("authorized by RAM")) {
                log.error("STS AssumeRole 无权限(403)：当前 RAM 用户/角色未授权扮演角色，请到阿里云 RAM 控制台检查：1) 当前 AccessKey 对应用户需有 sts:AssumeRole 权限；2) 角色 {} 的信任策略需允许该用户扮演。", OSS_STS_ROLE_ARN, error);
                throw new RuntimeException("STS 临时凭证无权限(403)：请在 RAM 控制台为当前 AccessKey 对应用户授权 sts:AssumeRole，并确保角色 " + OSS_STS_ROLE_ARN + " 的信任策略允许该用户扮演。", error);
            }
            log.error("获取STS凭证失败: {}", error.getMessage(), error);
            throw new RuntimeException("获取STS凭证失败", error);
        }
    }

    @ApiOperation("生成OSS直传签名（OSS4-HMAC-SHA256方式）")
    @GetMapping("/signature")
    public ResponseEntity<Map<String, String>> getPostSignatureForOssUpload() {
        try {
//            // 直接使用配置的AccessKey（如果使用STS，需要调用getCredential()方法）
//            String accesskeyid = accessKeyId;
//            String accesskeysecret = accessKeySecret;
//            String securitytoken = ""; // 如果使用STS，这里应该是securityToken
            AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials sts_data = getCredential();

            String accesskeyid =  sts_data.accessKeyId;
            String accesskeysecret =  sts_data.accessKeySecret;
            String securitytoken =  sts_data.securityToken;
            
            String region = getRegion();
            String host = getHost();

        //获取x-oss-credential里的date，当前日期，格式为yyyyMMdd
        ZonedDateTime today = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = today.format(formatter);

        //获取x-oss-date
        ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(java.time.ZoneOffset.UTC);
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
        String x_oss_date = now.format(formatter2);

        // 步骤1：创建policy。
        String x_oss_credential = accesskeyid + "/" + date + "/" + region + "/oss/aliyun_v4_request";

        ObjectMapper mapper = new ObjectMapper();

        Map<String, Object> policy = new HashMap<>();
        policy.put("expiration", generateExpiration(expireTime));

        List<Object> conditions = new ArrayList<>();

        Map<String, String> bucketCondition = new HashMap<>();
        bucketCondition.put("bucket", bucketName);
        conditions.add(bucketCondition);

            // 如果使用STS，需要添加security-token条件
            if (securitytoken != null && !securitytoken.isEmpty()) {
                Map<String, String> securityTokenCondition = new HashMap<>();
                securityTokenCondition.put("x-oss-security-token", securitytoken);
                conditions.add(securityTokenCondition);
            }

        Map<String, String> signatureVersionCondition = new HashMap<>();
        signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
        conditions.add(signatureVersionCondition);

        Map<String, String> credentialCondition = new HashMap<>();
        credentialCondition.put("x-oss-credential", x_oss_credential); // 替换为实际的 access key id
        conditions.add(credentialCondition);

        Map<String, String> dateCondition = new HashMap<>();
        dateCondition.put("x-oss-date", x_oss_date);
        conditions.add(dateCondition);


        conditions.add(Arrays.asList("content-length-range", 1, 104857600));
        conditions.add(Arrays.asList("eq", "$success_action_status", "200"));
        conditions.add(Arrays.asList("starts-with", "$key", uploadDir));

        policy.put("conditions", conditions);

        String jsonPolicy = mapper.writeValueAsString(policy);

        // 步骤2：构造待签名字符串（StringToSign）。使用 UTF-8 与文档/服务端一致，避免签名差异。
        String stringToSign = new String(Base64.encodeBase64(jsonPolicy.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);

        // 步骤3：计算SigningKey。
        byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), date);
        byte[] dateRegionKey = hmacsha256(dateKey, region);
        byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
        byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");

        // 步骤4：计算Signature。
        byte[] result = hmacsha256(signingKey, stringToSign);
        String signature = BinaryUtil.toHex(result);

        // 步骤5：设置回调（可选）。
        String base64CallbackBody = "";
        if (callbackUrl != null && !callbackUrl.isEmpty()) {
            JSONObject jasonCallback = new JSONObject();
            jasonCallback.put("callbackUrl", callbackUrl);
            jasonCallback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
            jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
            base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes(StandardCharsets.UTF_8));
        }

        Map<String, String> response = new HashMap<>();
        // 与文档一致：version / x_oss_signature_version 均返回，便于前端按文档示例取值
        response.put("version", "OSS4-HMAC-SHA256");
        response.put("x_oss_signature_version", "OSS4-HMAC-SHA256");
        // 这里是易错点，不能直接传policy，需要做一下Base64编码
        response.put("policy", stringToSign);
        response.put("x_oss_credential", x_oss_credential);
        response.put("x_oss_date", x_oss_date);
        response.put("signature", signature);
        // 只有在使用STS时才添加security_token
        if (securitytoken != null && !securitytoken.isEmpty()) {
            response.put("security_token", securitytoken);
        }
        response.put("dir", uploadDir);
        response.put("host", host);
        // 前端 PostObject 表单字段名必须与 OSS 约定一致，否则会返回 MethodNotAllowed(405)：
        // policy, x-oss-signature(本接口返回 signature), x-oss-signature-version, x-oss-credential, x-oss-date,
        // x-oss-security-token(有 STS 时), key(=dir+文件名), success_action_status, file(必须最后一项)
        if (!base64CallbackBody.isEmpty()) {
            response.put("callback", base64CallbackBody);
        }
        
        log.info("生成OSS直传签名成功（OSS4方式）: dir={}, host={}", uploadDir, host);
        // 返回带有状态码 200 (OK) 的 ResponseEntity，返回给Web端，进行PostObject操作
        return ResponseEntity.ok(response);
        } catch (Exception e) {
            log.error("生成OSS直传签名失败（OSS4方式）: {}", e.getMessage(), e);
            Map<String, String> errorResponse = new HashMap<>();
            errorResponse.put("error", "生成签名失败: " + e.getMessage());
            return ResponseEntity.status(500).body(errorResponse);
        }
    }
    public static byte[] hmacsha256(byte[] key, String data) {
        try {
            // 初始化HMAC密钥规格，指定算法为HMAC-SHA256并使用提供的密钥。
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");

            // 获取Mac实例，并通过getInstance方法指定使用HMAC-SHA256算法。
            Mac mac = Mac.getInstance("HmacSHA256");
            // 使用密钥初始化Mac对象。
            mac.init(secretKeySpec);

            // 执行HMAC计算，通过doFinal方法接收需要计算的数据并返回计算结果的数组。
            byte[] hmacBytes = mac.doFinal(data.getBytes());

            return hmacBytes;
        } catch (Exception e) {
            throw new RuntimeException("Failed to calculate HMAC-SHA256", e);
        }
    }
}