package com.qmth.boot.core.fss.utils;

import com.qmth.boot.tools.codec.CodecUtils;
import com.qmth.boot.tools.models.ByteArray;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
import java.util.Locale;
import java.util.SimpleTimeZone;

/**
 * OSS签名计算工具
 */
public class OssSigner {

    private static final String[] ENCODED_CHARACTERS_WITH_SLASHES = new String[] { "+", "*", "%7E", "%2F" };

    private static final String[] ENCODED_CHARACTERS_WITH_SLASHES_REPLACEMENTS = new String[] { "%20", "%2A", "~",
            "/" };

    private static final String[] ENCODED_CHARACTERS_WITHOUT_SLASHES = new String[] { "+", "*", "%7E" };

    private static final String[] ENCODED_CHARACTERS_WITHOUT_SLASHES_REPLACEMENTS = new String[] { "%20", "%2A", "~" };

    public static String buildHeader(OssConfig config, String method, String path, OssApiParam param) {
        String dateString = getIso8601Date(param.getDate());
        String dateTime = getIso8601DateTime(param.getDate());
        String uri = urlEncode("/" + config.getBucket() + "/" + path, true);
        // 步骤1：构造CanonicalRequest
        String canonicalRequest = method.toUpperCase() + "\n" + uri + "\n\n" + (param.getContentMd5() != null ?
                "content-md5:" + param.getContentMd5() + "\n" :
                "") + (param.getContentType() != null ? "content-type:" + param.getContentType() + "\n" : "") + "host:"
                + config.getHost() + "\n" + "x-oss-content-sha256:UNSIGNED-PAYLOAD\n" + (param.getCopySource() != null ?
                "x-oss-copy-source:" + param.getCopySource() + "\n" :
                "") + "x-oss-date:" + dateTime + "\n" + "\nhost\nUNSIGNED-PAYLOAD";
        String canonicalDigest = ByteArray.sha256(canonicalRequest).toHexString().toLowerCase();
        //System.out.println("canonicalRequest:" + canonicalRequest);
        //System.out.println("canonicalDigest:" + canonicalDigest);
        // 步骤2：构造待签名字符串（StringToSign）
        String stringToSign = "OSS4-HMAC-SHA256\n" + dateTime + "\n" + dateString + "/" + config.getRegion()
                + "/oss/aliyun_v4_request\n" + canonicalDigest;
        //System.out.println("stringToSign:" + stringToSign);
        // 步骤3：计算Signature。
        // "accesskeysecret"填入用户AK，data参数填入实际日期如"20231203”
        byte[] dateKey = CodecUtils
                .hmacsha256(("aliyun_v4" + config.getAccessSecret()).getBytes(StandardCharsets.UTF_8),
                        dateString.getBytes(StandardCharsets.UTF_8));
        // 参数填入所在地区，如所在地域为杭州则填入"cn-hangzhou”
        byte[] dateRegionKey = CodecUtils.hmacsha256(dateKey, config.getRegion().getBytes(StandardCharsets.UTF_8));
        byte[] dateRegionServiceKey = CodecUtils.hmacsha256(dateRegionKey, "oss".getBytes(StandardCharsets.UTF_8));
        byte[] signingKey = CodecUtils
                .hmacsha256(dateRegionServiceKey, "aliyun_v4_request".getBytes(StandardCharsets.UTF_8));
        byte[] result = CodecUtils.hmacsha256(signingKey, stringToSign.getBytes(StandardCharsets.UTF_8));
        String signature = CodecUtils.toHexString(result).toLowerCase();
        //System.out.println("signature:" + signature);

        return "OSS4-HMAC-SHA256 Credential=" + config.getAccessKey() + "/" + dateString + "/" + config.getRegion()
                + "/oss/aliyun_v4_request,AdditionalHeaders=host,Signature=" + signature;
    }

    /**
     * 签名计算工具
     *
     * @return url
     */
    public static String buildQuery(OssConfig config, String method, String path, Duration expireDuration) {
        Date date = new Date();
        String dateString = getIso8601Date(date);
        String dateTime = getIso8601DateTime(date);
        long expire = expireDuration.getSeconds();
        String uri = urlEncode("/" + config.getBucket() + "/" + path, true);
        // 步骤1：构造CanonicalRequest。
        String canonicalRequest =
                method.toUpperCase() + "\n" + uri + "\n" + "x-oss-additional-headers=host&x-oss-credential=" + config
                        .getAccessKey() + "%2F" + dateString + "%2F" + config.getRegion()
                        + "%2Foss%2Faliyun_v4_request&x-oss-date=" + dateTime + "&x-oss-expires=" + expire
                        + "&x-oss-signature-version=OSS4-HMAC-SHA256\n" + "host:" + config.getPortalHost() + "\n" + "\n"
                        + "host\n" + "UNSIGNED-PAYLOAD";
        String canonicalDigest = ByteArray.sha256(canonicalRequest).toHexString().toLowerCase();
        // 步骤2：构造待签名字符串（StringToSign）。
        String stringToSign = "OSS4-HMAC-SHA256\n" + dateTime + "\n" + dateString + "/" + config.getRegion()
                + "/oss/aliyun_v4_request\n" + canonicalDigest;

        // 步骤3：计算Signature。
        byte[] dateKey = CodecUtils
                .hmacsha256(("aliyun_v4" + config.getAccessSecret()).getBytes(StandardCharsets.UTF_8),
                        dateString.getBytes(StandardCharsets.UTF_8));
        byte[] dateRegionKey = CodecUtils.hmacsha256(dateKey, config.getRegion().getBytes(StandardCharsets.UTF_8));
        byte[] dateRegionServiceKey = CodecUtils.hmacsha256(dateRegionKey, "oss".getBytes(StandardCharsets.UTF_8));
        byte[] signingKey = CodecUtils
                .hmacsha256(dateRegionServiceKey, "aliyun_v4_request".getBytes(StandardCharsets.UTF_8));
        byte[] result = CodecUtils.hmacsha256(signingKey, stringToSign.getBytes(StandardCharsets.UTF_8));
        String signature = CodecUtils.toHexString(result).toLowerCase();
        //System.out.println("signature:" + signature);

        // 步骤4：在URL中加入签名。
        String queryString =
                "x-oss-additional-headers=host&" + "x-oss-credential=" + config.getAccessKey() + "%2F" + dateString
                        + "%2F" + config.getRegion() + "%2Foss%2Faliyun_v4_request&" + "x-oss-date=" + dateTime + "&"
                        + "x-oss-expires=" + expire + "&" + "x-oss-signature=" + signature + "&"
                        + "x-oss-signature-version=OSS4-HMAC-SHA256";
        //System.out.println("queryString:" + queryString);
        return queryString;
    }

    public static String urlEncode(String value, boolean ignoreSlashes) {
        if (value == null) {
            return "";
        }
        try {
            String encoded = URLEncoder.encode(value, StandardCharsets.UTF_8.name());
            if (!ignoreSlashes) {
                return StringUtils.replaceEach(encoded, ENCODED_CHARACTERS_WITHOUT_SLASHES,
                        ENCODED_CHARACTERS_WITHOUT_SLASHES_REPLACEMENTS);
            }
            return StringUtils.replaceEach(encoded, ENCODED_CHARACTERS_WITH_SLASHES,
                    ENCODED_CHARACTERS_WITH_SLASHES_REPLACEMENTS);
        } catch (UnsupportedEncodingException e) {
            throw new IllegalArgumentException("FailedToEncodeUri", e);
        }
    }

    public static String getIso8601Date(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd", Locale.US);
        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
        return df.format(date);
    }

    public static String getIso8601DateTime(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US);
        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
        return df.format(date);
    }

    public static String getGmtDateTime(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
        return df.format(date);
    }
}
