package com.ruoyi.cms.handler;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AliyunNlsTokenUtil {
    private static final Logger logger = LoggerFactory.getLogger(AliyunNlsTokenUtil.class);
    // Nginx 代理配置（与 Nginx 一致）
    private static final String PROXY_HOST = "192.168.2.102";
    private static final int PROXY_PORT = 10044;
    // Token 接口代理地址（通过 Nginx 转发）
    private static final String TOKEN_PROXY_URL = "http://" + PROXY_HOST + ":" + PROXY_PORT + "/";

    // 单例 OkHttp 客户端（带代理，全局复用）
    private static OkHttpClient proxyOkHttpClient;

    static {
        // 初始化带代理的 OkHttp 客户端（仅初始化一次）
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
        proxyOkHttpClient = new OkHttpClient.Builder()
                .proxy(proxy)
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .build();
    }

    /**
     * 手动生成 Token（绕开 SDK 无代理逻辑）
     * @param accessKeyId 阿里云 AccessKeyId
     * @param accessKeySecret 阿里云 AccessKeySecret
     * @param accessToken SDK 的 AccessToken 对象（用于设置 token 和 expireTime）
     */
    public static void generateToken(String accessKeyId, String accessKeySecret, com.alibaba.nls.client.AccessToken accessToken) throws Exception {
        // 1. 生成阿里云 API 签名（HMAC-SHA1）
        String timestamp = getIso8601UtcTimestamp();
        String nonce = String.valueOf(System.currentTimeMillis());
        String signature = generateSignature(accessKeyId, accessKeySecret, timestamp, nonce);

        // 2. 构造请求 URL（带签名参数）
        String requestUrl = TOKEN_PROXY_URL +
                "?Action=CreateToken" +
                "&Format=JSON" +
                "&Version=2019-02-28" +
                "&AccessKeyId=" + URLEncoder.encode(accessKeyId, "UTF-8") +
                "&Timestamp=" + URLEncoder.encode(timestamp, "UTF-8") +
                "&SignatureNonce=" + URLEncoder.encode(nonce, "UTF-8") +
                "&Signature=" + URLEncoder.encode(signature, "UTF-8") +
                "&SignatureMethod=HMAC-SHA1" +
                "&SignatureVersion=1.0";

        // 打印完整请求 URL（用于排查参数问题）
        logger.info("Token 生成请求 URL：{}", requestUrl);

        // 3. 发送 GET 请求（走 Nginx 代理）
        Request request = new Request.Builder().url(requestUrl).build();
        try (Response response = proxyOkHttpClient.newCall(request).execute()) {
            String responseBody = response.body() != null ? response.body().string() : "无响应内容";
            logger.info("Token 接口响应 - 状态码：{}，响应体：{}", response.code(), responseBody);
            if (!response.isSuccessful()) {
                throw new RuntimeException("Token 生成失败，状态码：" + response.code() + "，错误信息：" + responseBody);
            }
            com.alibaba.fastjson.JSONObject json = com.alibaba.fastjson.JSON.parseObject(responseBody);

            // 4. 提取 Token 和过期时间
            String token = json.getJSONObject("Token").getString("Id");
            long expireTime = json.getJSONObject("Token").getLong("ExpireTime");

            // 5. 反射设置到 SDK 的 AccessToken 对象
            setAccessTokenField(accessToken, "token", token);
            setAccessTokenField(accessToken, "expireTime", expireTime);
            logger.info("Token 生成成功，过期时间：{}", expireTime);
        }
    }

    /**
     * 生成阿里云 API 签名（遵循阿里云规范）
     */
    private static String generateSignature(String accessKeyId, String accessKeySecret, String timestamp, String nonce) throws Exception {
        // 1. 按字典序拼接参数（必须严格排序）
        StringBuilder paramBuilder = new StringBuilder();
        paramBuilder.append("AccessKeyId=").append(URLEncoder.encode(accessKeyId, "UTF-8"))
                .append("&Action=").append(URLEncoder.encode("CreateToken", "UTF-8"))
                .append("&Format=").append(URLEncoder.encode("JSON", "UTF-8"))
                .append("&SignatureMethod=").append(URLEncoder.encode("HMAC-SHA1", "UTF-8"))
                .append("&SignatureNonce=").append(URLEncoder.encode(nonce, "UTF-8"))
                .append("&SignatureVersion=").append(URLEncoder.encode("1.0", "UTF-8"))
                .append("&Timestamp=").append(URLEncoder.encode(timestamp, "UTF-8"))
                .append("&Version=").append(URLEncoder.encode("2019-02-28", "UTF-8"));

        // 2. 构造签名原文（Method + & + 编码后的URL + & + 编码后的参数）
        String method = "GET";
        String encodedUrl = URLEncoder.encode("/", "UTF-8"); // 根路径编码
        String encodedParams = URLEncoder.encode(paramBuilder.toString(), "UTF-8");
        String signatureText = method + "&" + encodedUrl + "&" + encodedParams;

        // 调试日志：对比服务器端预期的签名原文
        logger.info("本地计算的签名原文：{}", signatureText);

        // 3. HMAC-SHA1 加密 + Base64 编码
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(new SecretKeySpec((accessKeySecret + "&").getBytes("UTF-8"), "HmacSHA1"));
        byte[] signatureBytes = mac.doFinal(signatureText.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(signatureBytes);
    }

    /**
     * 生成 ISO8601 格式的 UTC 时间戳（如：2025-11-24T10:00:00Z）
     */
    private static String getIso8601UtcTimestamp() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // 强制使用 UTC 时区
        return sdf.format(new Date());
    }

    /**
     * 反射设置 AccessToken 的私有字段
     */
    private static void setAccessTokenField(com.alibaba.nls.client.AccessToken accessToken, String fieldName, Object value) throws Exception {
        java.lang.reflect.Field field = com.alibaba.nls.client.AccessToken.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(accessToken, value);
    }
}