package io.geekidea.springbootplus.framework.shiro.signature;

import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.TreeMap;

import cn.hutool.core.lang.Assert;


/**
 * 签名工具类
 * @Author luozh
 * @Date 2022年04月14日 11:32o
 * @Version 1.0
 */
public class SignUtils {

    public static final String IM_PREFIX = "x-im-";

    public static final String CONTENT_TYPE = "Content-Type";

    public static final String DATE = "Date";

    public static final String NEW_LINE = "\n";

    public static String buildCanonicalString(String method, String resourcePath, Map<String, String> headers, Map<String, String> parameters) {
        StringBuilder canonicalString = new StringBuilder();
        canonicalString.append(method).append(NEW_LINE);

        TreeMap<String, String> fixedHeadersToSign = new TreeMap<>();
        TreeMap<String, String> canonicalizedImHeadersToSign = new TreeMap<>();

        if (headers != null) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                if (header.getKey() != null) {
                    String lowerKey = header.getKey().toLowerCase();
                    if (lowerKey.equals(CONTENT_TYPE.toLowerCase())
                            || lowerKey.equals(DATE.toLowerCase())) {
                        fixedHeadersToSign.put(lowerKey, header.getValue().trim());
                    } else if (lowerKey.startsWith(IM_PREFIX)) {
                        canonicalizedImHeadersToSign.put(lowerKey, header.getValue().trim());
                    }
                }
            }
        }

        if (!fixedHeadersToSign.containsKey(CONTENT_TYPE.toLowerCase())) {
            fixedHeadersToSign.put(CONTENT_TYPE.toLowerCase(), "");
        }

        // Append fixed headers to sign to canonical string
        for (Map.Entry<String, String> entry : fixedHeadersToSign.entrySet()) {
            Object value = entry.getValue();

            canonicalString.append(value);
            canonicalString.append(NEW_LINE);
        }

        // Append canonicalized im headers to sign to canonical string
        for (Map.Entry<String, String> entry : canonicalizedImHeadersToSign.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            canonicalString.append(key).append(':').append(value).append(NEW_LINE);
        }

        // Append canonical resource to canonical string
        canonicalString.append(buildCanonicalizedResource(resourcePath, parameters));

        return canonicalString.toString();
    }

    private static String buildCanonicalizedResource(String resourcePath, Map<String, String> parameters) {
        Assert.isTrue(resourcePath.startsWith("/"), "Resource path should start with slash character");

        StringBuilder builder = new StringBuilder();
        builder.append(uriEncoding(resourcePath));

        if (parameters != null) {
            TreeMap<String, String> canonicalizedParams = new TreeMap<String, String>();
            for (Map.Entry<String, String> param : parameters.entrySet()) {
                if (param.getValue() != null) {
                    canonicalizedParams.put(uriEncoding(param.getKey()), uriEncoding(param.getValue()));
                } else {
                    canonicalizedParams.put(uriEncoding(param.getKey()), null);
                }
            }

            char separator = '?';
            for (Map.Entry<String, String> entry : canonicalizedParams.entrySet()) {
                builder.append(separator);
                builder.append(entry.getKey());
                if (entry.getValue() != null && !entry.getValue().isEmpty()) {
                    builder.append("=").append(entry.getValue());
                }
                separator = '&';
            }
        }

        return builder.toString();
    }

    public static String uriEncoding(String uri) {
        String result = "";

        try {
            for (char c : uri.toCharArray()) {
                if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
                        || (c >= '0' && c <= '9') || c == '_' || c == '-'
                        || c == '~' || c == '.') {
                    result += c;
                } else if (c == '/') {
                    result += "%2F";
                } else {
                    byte[] b;
                    b = Character.toString(c).getBytes("utf-8");

                    for (int i = 0; i < b.length; i++) {
                        int k = b[i];

                        if (k < 0) {
                            k += 256;
                        }
                        result += "%" + Integer.toHexString(k).toUpperCase();
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException(e);
        }
        return result;
    }

    public static String buildSignature(String secretAccessKey, String httpMethod, String resourcePath, Map<String, String> headers, Map<String, String> parameters) {
        String canonicalString = buildCanonicalString(httpMethod, resourcePath, headers, parameters);
        return new HmacSHA256Signature().computeSignature(secretAccessKey, canonicalString);
    }

}
