package com.weaver.seconddev.chapanda.test;

import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.http.entity.ContentType;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * signature demo
 */
public class SignatureDemo {

    /**
     * 日期格式化工具，用于将日期时间字符串格式化为"yyyy-MM-dd'T'HH:mm:ss'Z'"的格式。
     */
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

    private static class SignatureRequest {
        // HTTP Method
        private final String httpMethod;
        // 请求路径，当资源路径为空时，使用正斜杠(/)作为CanonicalURI
        private final String canonicalUri;
        // endpoint
        private final String host;
        // API name
        private final String xAcsAction;
        // API version
        private final String xAcsVersion;
        // headers
        TreeMap<String, String> headers = new TreeMap<>();
        // body参数对应的字节数组，请求参数在元数据中显示"in":"body"或"in": "formData"，表示参数放在body中
        byte[] body;
        // query参数，请求参数在元数据中显示"in":"query"，表示参数拼接在请求URL上
        TreeMap<String, Object> queryParam = new TreeMap<>();

        public SignatureRequest(String httpMethod, String canonicalUri, String host, String xAcsAction, String xAcsVersion) {
            this.httpMethod = httpMethod;
            this.canonicalUri = canonicalUri;
            this.host = host;
            this.xAcsAction = xAcsAction;
            this.xAcsVersion = xAcsVersion;
            initHeader();
        }

        // init headers
        private void initHeader() {
            headers.put("host", host);
            headers.put("x-acs-action", xAcsAction);
            headers.put("x-acs-version", xAcsVersion);
            SDF.setTimeZone(new SimpleTimeZone(0, "GMT")); // 设置日期格式化时区为GMT
            headers.put("x-acs-date", SDF.format(new Date()));
            headers.put("x-acs-signature-nonce", UUID.randomUUID().toString());
        }
    }

    /**
     * System.getenv()表示通过环境变量获取Access Key ID和Access Key Secret。
     */
    private final static String ACCESS_KEY_ID = System.getenv("LTAI5tC2WxpLdtEdg1wLaaYp");
    private final static String ACCESS_KEY_SECRET = System.getenv("iPXi2unHR643PcKRkT89jg8aNrocPD");

    /**
     * 签名协议
     */
    private static final String ALGORITHM = "ACS3-HMAC-SHA256";

    /**
     * 签名示例，您需要根据实际情况替换main方法中的示例参数。
     * ROA接口和RPC接口只有canonicalUri取值逻辑是完全不同，其余内容都是相似的。
     * <p>
     * 通过API元数据获取请求方法（methods）、请求参数名称（name）、请求参数类型（type）、请求参数位置（in），并将参数封装到SignatureRequest中。
     * 1. 请求参数在元数据中显示"in":"query"，通过queryParam传参。
     * 2. 请求参数在元数据中显示"in": "body"，通过body传参。
     * 3. 请求参数在元数据中显示"in": "formData"，通过body传参。
     */
    public static void main(String[] args) throws IOException {
        // RPC接口请求示例一：请求参数"in":"query"
        String httpMethod = "POST"; // 请求方式，从元数据中可以获取，建议使用POST。
        String canonicalUri = "/"; // RPC接口无资源路径，故使用正斜杠（/）作为CanonicalURI
        String host = "ecs.cn-hangzhou.aliyuncs.com";  // 云产品服务接入点
        String xAcsAction = "DescribeInstanceStatus";  // API名称
        String xAcsVersion = "2014-05-26"; // API版本号
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        // DescribeInstanceStatus请求参数如下：
        // RegionId在元数据中显示的类型是String，"in":"query"，必填
        signatureRequest.queryParam.put("RegionId", "cn-hangzhou");
        // InstanceId的在元数据中显示的类型是array，"in":"query"，非必填
        // String[] instanceIds = {"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"};
        // signatureRequest.queryParam.put("InstanceId", Arrays.asList(instanceIds));

        /*// RPC接口请求示例二：请求参数"in":"body"
        String httpMethod = "POST";
        String canonicalUri = "/";
        String host = "ocr-api.cn-hangzhou.aliyuncs.com";
        String xAcsAction = "RecognizeGeneral";
        String xAcsVersion = "2021-07-07";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        // 请求参数在元数据中显示"in": "body"，通过body传参。
        signatureRequest.body = Files.readAllBytes(Paths.get("D:\\test.png"));
        signatureRequest.headers.put("content-type", "application/octet-stream");*/

        /*// RPC接口请求示例三：请求参数"in": "formData"
        String httpMethod = "POST";
        String canonicalUri = "/";
        String host = "mt.aliyuncs.com";
        String xAcsAction = "TranslateGeneral";
        String xAcsVersion = "2018-10-12";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        // TranslateGeneral请求参数如下：
        // Context在元数据中显示的类型是String，"in":"query"，非必填
        signatureRequest.queryParam.put("Context", "早上");
        // FormatType、SourceLanguage、TargetLanguage等参数，在元数据中显示"in":"formData"
        Map<String, Object> body = new HashMap<>();
        body.put("FormatType", "text");
        body.put("SourceLanguage", "zh");
        body.put("TargetLanguage", "en");
        body.put("SourceText", "你好");
        body.put("Scene", "general");
        String formDataToString = formDataToString(body);
        signatureRequest.body = formDataToString.getBytes(StandardCharsets.UTF_8);
        signatureRequest.headers.put("content-type", "application/x-www-form-urlencoded");*/

        /*// ROA接口POST请求
        String httpMethod = "POST";
        String canonicalUri = "/clusters"; // 从元数据中获取："path": "/clusters"
        String host = "cs.cn-beijing.aliyuncs.com"; // endpoint
        String xAcsAction= "CreateCluster"; // API名称
        String xAcsVersion=  "2015-12-15"; // API版本号
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        // 调用API所需要的参数，请求参数在元数据中显示"in": "body"，表示参数放在body中
        TreeMap<String, Object> body = new TreeMap<>();
        body.put("name", "测试");
        body.put("region_id", "cn-beijing");
        body.put("cluster_type", "ExternalKubernetes");
        body.put("vpcid", "vpc-2zeou1uod4ylaXXXXXXXX");
        body.put("container_cidr","10.0.0.0/8");
        body.put("service_cidr", "10.2.0.0/24");
        body.put("security_group_id", "sg-2ze1a0rlgeo7XXXXXXXX");
        body.put("vswitch_ids", Collections.singletonList(
                "vsw-2zei30dhfldu8XXXXXXXX"
        ));
        Gson gson = (new GsonBuilder()).disableHtmlEscaping().create();
        signatureRequest.body = gson.toJson(body).getBytes(StandardCharsets.UTF_8);
        signatureRequest.headers.put("content-type", "application/json");*/

        /*// ROA接口GET请求
        String httpMethod = "GET";
        // canonicalUri如果存在path参数，需要对path参数encode，percentCode({path参数})
        String canonicalUri = "/clusters/" + percentCode("cdb14b4f85130407da748fd3fXXXXXXXX") + "/resources";
        String host = "cs.cn-beijing.aliyuncs.com"; // endpoint
        String xAcsAction = "DescribeClusterResources"; // API名称
        String xAcsVersion = "2015-12-15"; // API版本号
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
        signatureRequest.queryParam.put("with_addon_resources", true);*/

        /*// ROA接口DELETE请求
        String httpMethod = "DELETE";
        String canonicalUri = "/clusters/" + percentCode("cdb14b4f85130407da748fd3fXXXXXXXX");
        String host = "cs.cn-beijing.aliyuncs.com";
        String xAcsAction = "DeleteCluster";
        String xAcsVersion = "2015-12-15";
        SignatureRequest signatureRequest = new SignatureRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);*/

        // 签名过程
        getAuthorization(signatureRequest);
        // 调用API
        callApi(signatureRequest);
    }

    private static void callApi(SignatureRequest signatureRequest) {
        try {
            // 通过HttpClient发送请求
            String url = "https://" + signatureRequest.host + signatureRequest.canonicalUri;
            URIBuilder uriBuilder = new URIBuilder(url);
            // 添加请求参数
            for (Map.Entry<String, Object> entry : signatureRequest.queryParam.entrySet()) {
                uriBuilder.addParameter(entry.getKey(), String.valueOf(entry.getValue()));
            }
            System.out.println(uriBuilder.build());
            HttpUriRequest httpRequest;
            switch (signatureRequest.httpMethod) {
                case "GET":
                    httpRequest = new HttpGet(uriBuilder.build());
                    break;
                case "POST":
                    HttpPost httpPost = new HttpPost(uriBuilder.build());
                    if (signatureRequest.body != null) {
                        httpPost.setEntity(new ByteArrayEntity(signatureRequest.body, ContentType.create(signatureRequest.headers.get("content-type"))));
                    }
                    httpRequest = httpPost;
                    break;
                case "DELETE":
                    httpRequest = new HttpDelete(uriBuilder.build());
                    break;
                default:
                    System.out.println("Unsupported HTTP method: " + signatureRequest.httpMethod);
                    throw new IllegalArgumentException("Unsupported HTTP method");
            }

            // 添加http请求头
            for (Map.Entry<String, String> entry : signatureRequest.headers.entrySet()) {
                httpRequest.addHeader(entry.getKey(), String.valueOf(entry.getValue()));
            }
            // 发送请求
            try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println(result);
            } catch (IOException e) {
                // 异常处理
                System.out.println("Failed to send request");
                e.printStackTrace();
            }
        } catch (URISyntaxException e) {
            // 异常处理
            System.out.println("Invalid URI syntax");
            e.printStackTrace();
        }
    }

    /**
     * 该方法用于根据传入的HTTP请求方法、规范化的URI、查询参数等，计算并生成授权信息。
     */
    private static void getAuthorization(SignatureRequest signatureRequest) {
        try {
            // 处理queryParam中参数值为List、Map类型的参数，将参数平铺
            TreeMap<String, Object> newQueryParam = new TreeMap<>();
            processObject(newQueryParam, "", signatureRequest.queryParam);
            signatureRequest.queryParam = newQueryParam;
            // 步骤 1：拼接规范请求串
            // 请求参数，当请求的查询字符串为空时，使用空字符串作为规范化查询字符串
            StringBuilder canonicalQueryString = new StringBuilder();
            signatureRequest.queryParam.entrySet().stream().map(entry -> percentCode(entry.getKey()) + "="
                    + percentCode(String.valueOf(entry.getValue()))).forEachOrdered(queryPart -> {
                // 如果canonicalQueryString已经不是空的，则在查询参数前添加"&"
                if (canonicalQueryString.length() > 0) {
                    canonicalQueryString.append("&");
                }
                canonicalQueryString.append(queryPart);
            });

            // 计算请求体的哈希值
            String requestPayload = ""; // 请求体，当请求正文为空时，比如GET请求，RequestPayload固定为空字符串
            String hashedRequestPayload = signatureRequest.body != null ? sha256Hex(signatureRequest.body) : sha256Hex(requestPayload.getBytes(StandardCharsets.UTF_8));
            signatureRequest.headers.put("x-acs-content-sha256", hashedRequestPayload);
            // 构造请求头，多个规范化消息头，按照消息头名称（小写）的字符代码顺序以升序排列后拼接在一起
            StringBuilder canonicalHeaders = new StringBuilder();
            // 已签名消息头列表，多个请求头名称（小写）按首字母升序排列并以英文分号（;）分隔
            StringBuilder signedHeadersSb = new StringBuilder();
            signatureRequest.headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") || "host".equalsIgnoreCase(entry.getKey()) || "content-type".equalsIgnoreCase(entry.getKey())).sorted(Map.Entry.comparingByKey()).forEach(entry -> {
                String lowerKey = entry.getKey().toLowerCase();
                String value = String.valueOf(entry.getValue()).trim();
                canonicalHeaders.append(lowerKey).append(":").append(value).append("\n");
                signedHeadersSb.append(lowerKey).append(";");
            });
            String signedHeaders = signedHeadersSb.substring(0, signedHeadersSb.length() - 1);
            String canonicalRequest = signatureRequest.httpMethod + "\n" + signatureRequest.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
            System.out.println("canonicalRequest=========>\n" + canonicalRequest);

            // 步骤 2：拼接待签名字符串
            String hashedCanonicalRequest = sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8)); // 计算规范化请求的哈希值
            String stringToSign = ALGORITHM + "\n" + hashedCanonicalRequest;
            System.out.println("stringToSign=========>\n" + stringToSign);

            // 步骤 3：计算签名
            String signature = DatatypeConverter.printHexBinary(hmac256(ACCESS_KEY_SECRET.getBytes(StandardCharsets.UTF_8), stringToSign)).toLowerCase();
            System.out.println("signature=========>" + signature);

            // 步骤 4：拼接 Authorization
            String authorization = ALGORITHM + " " + "Credential=" + ACCESS_KEY_ID + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature;
            System.out.println("authorization=========>" + authorization);
            signatureRequest.headers.put("Authorization", authorization);
        } catch (Exception e) {
            // 异常处理
            System.out.println("Failed to get authorization");
            e.printStackTrace();
        }
    }

    /**
     * 处理请求参数类型为formData的参数。
     *
     * @param formData formData类型参数
     * @return String
     */
    private static String formDataToString(Map<String, Object> formData) {
        Map<String, Object> tileMap = new HashMap<>();
        processObject(tileMap, "", formData);
        StringBuilder result = new StringBuilder();
        boolean first = true;
        String symbol = "&";
        for (Map.Entry<String, Object> entry : tileMap.entrySet()) {
            String value = String.valueOf(entry.getValue());
            if (value != null && !value.isEmpty()) {
                if (first) {
                    first = false;
                } else {
                    result.append(symbol);
                }
                result.append(percentCode(entry.getKey()));
                result.append("=");
                result.append(percentCode(value));
            }
        }

        return result.toString();
    }

    /**
     * 递归处理对象，将复杂对象（如Map和List）展开为平面的键值对
     *
     * @param map   原始的键值对集合，将被递归地更新
     * @param key   当前处理的键，随着递归的深入，键会带有嵌套路径信息
     * @param value 对应于键的值，可以是嵌套的Map、List或其他类型
     */
    private static void processObject(Map<String, Object> map, String key, Object value) {
        // 如果值为空，则无需进一步处理
        if (value == null) {
            return;
        }
        if (key == null) {
            key = "";
        }
        // 当值为List类型时，遍历List中的每个元素，并递归处理
        if (value instanceof List<?>) {
            List<?> list = (List<?>) value;
            for (int i = 0; i < list.size(); ++i) {
                processObject(map, key + "." + (i + 1), list.get(i));
            }
        } else if (value instanceof Map<?, ?>) {
            // 当值为Map类型时，遍历Map中的每个键值对，并递归处理
            Map<?, ?> subMap = (Map<?, ?>) value;
            for (Map.Entry<?, ?> entry : subMap.entrySet()) {
                processObject(map, key + "." + entry.getKey().toString(), entry.getValue());
            }
        } else {
            // 对于以"."开头的键，移除开头的"."以保持键的连续性
            if (key.startsWith(".")) {
                key = key.substring(1);
            }
            // 对于byte[]类型的值，将其转换为UTF-8编码的字符串
            if (value instanceof byte[]) {
                map.put(key, new String((byte[]) value, StandardCharsets.UTF_8));
            } else {
                // 对于其他类型的值，直接转换为字符串
                map.put(key, String.valueOf(value));
            }
        }
    }

    /**
     * 使用HmacSHA256算法生成消息认证码（MAC）。
     *
     * @param secretKey 密钥，用于生成MAC的密钥，必须保密。
     * @param str       需要进行MAC认证的消息。
     * @return 返回使用HmacSHA256算法计算出的消息认证码。
     * @throws Exception 如果初始化MAC或计算MAC过程中遇到错误，则抛出异常。
     */
    public static byte[] hmac256(byte[] secretKey, String str) throws Exception {
        // 实例化HmacSHA256消息认证码生成器
        Mac mac = Mac.getInstance("HmacSHA256");
        // 创建密钥规范，用于初始化MAC生成器
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, mac.getAlgorithm());
        // 初始化MAC生成器
        mac.init(secretKeySpec);
        // 计算消息认证码并返回
        return mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 使用SHA-256算法计算字符串的哈希值并以十六进制字符串形式返回。
     *
     * @param input 需要进行SHA-256哈希计算的字节数组。
     * @return 计算结果为小写十六进制字符串。
     * @throws Exception 如果在获取SHA-256消息摘要实例时发生错误。
     */
    public static String sha256Hex(byte[] input) throws Exception {
        // 获取SHA-256消息摘要实例
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        // 计算字符串s的SHA-256哈希值
        byte[] d = md.digest(input);
        // 将哈希值转换为小写十六进制字符串并返回
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    /**
     * 对指定的字符串进行URL编码。
     * 使用UTF-8编码字符集对字符串进行编码，并对特定的字符进行替换，以符合URL编码规范。
     *
     * @param str 需要进行URL编码的字符串。
     * @return 编码后的字符串。其中，加号"+"被替换为"%20"，星号"*"被替换为"%2A"，波浪号"%7E"被替换为"~"。
     */
    public static String percentCode(String str) {
        if (str == null) {
            throw new IllegalArgumentException("输入字符串不可为null");
        }
        try {
            return URLEncoder.encode(str, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8编码不被支持", e);
        }
    }
}