package com.wecloud.im.sdk.internal;

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

import cn.hutool.core.lang.Assert;

import com.wecloud.im.sdk.ClientException;
import com.wecloud.im.sdk.common.RequestMessage;
import com.wecloud.im.sdk.common.auth.HmacSHA256Signature;
import com.wecloud.im.sdk.utils.HttpHeaders;

import static com.wecloud.im.sdk.internal.ImHeaders.IM_PREFIX;
import static com.wecloud.im.sdk.internal.SignParameters.AUTHORIZATION_ACCESS_KEY_ID;
import static com.wecloud.im.sdk.internal.SignParameters.AUTHORIZATION_PREFIX;
import static com.wecloud.im.sdk.internal.SignParameters.AUTHORIZATION_SIGNATURE;

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

    public static String composeRequestAuthorization(String accessKeyId, String signature) {
        StringBuilder sb = new StringBuilder();
        sb.append(AUTHORIZATION_PREFIX + AUTHORIZATION_ACCESS_KEY_ID).append(":").append(accessKeyId).append(", ");
        sb.append(AUTHORIZATION_SIGNATURE).append(":").append(signature);

        return sb.toString();
    }

    public static String buildCanonicalString(String method, String resourcePath, RequestMessage request) {
        StringBuilder canonicalString = new StringBuilder();
        canonicalString.append(method).append(SignParameters.NEW_LINE);
        Map<String, String> headers = request.getHeaders();
        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(HttpHeaders.CONTENT_TYPE.toLowerCase())
                            || lowerKey.equals(HttpHeaders.DATE.toLowerCase())) {
                        fixedHeadersToSign.put(lowerKey, header.getValue().trim());
                    } else if (lowerKey.startsWith(IM_PREFIX)) {
                        canonicalizedImHeadersToSign.put(lowerKey, header.getValue().trim());
                    }
                }
            }
        }

        if (!fixedHeadersToSign.containsKey(HttpHeaders.CONTENT_TYPE.toLowerCase())) {
            fixedHeadersToSign.put(HttpHeaders.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(SignParameters.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(SignParameters.NEW_LINE);
        }

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

        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 ClientException(e);
        }
        return result;
    }

    public static String buildSignature(String secretAccessKey, String httpMethod, String resourcePath, RequestMessage request) {
        String canonicalString = buildCanonicalString(httpMethod, resourcePath, request);
        return new HmacSHA256Signature().computeSignature(secretAccessKey, canonicalString);
    }

}
