package com.backendsys.SDK.Meitu;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import shade.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;

public class Signer {
    public static final String BasicDateFormat = "yyyyMMdd'T'HHmmss'Z'";
    public static final String Algorithm = "SDK-HMAC-SHA256";
    public static final String HeaderXDate = "X-Sdk-Date";
    public static final String HeaderHost = "Host";
    public static final String HeaderAuthorization = "Authorization";
    public static final String HeaderContentSha256 = "Content-SHA256";

    private String Key;
    private String Secret;

    public Signer(String key, String secret) {
        this.Key = key;
        this.Secret = secret;
    }

    public String signStringToSign(String stringToSign, String signingKey) {
        try {
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(signingKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKey);
            byte[] signedBytes = sha256Hmac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(signedBytes);
        } catch (Exception e) {
            throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e);
        }
    }

    public String authHeaderValue(String signature, String accessKey, List<String> signedHeaders) {
        StringBuilder signedHeadersStr = new StringBuilder();
        for (String header : signedHeaders) {
            if (signedHeadersStr.length() > 0) {
                signedHeadersStr.append(";");
            }
            signedHeadersStr.append(header);
        }

        String headerValue = Algorithm + " Access=" + accessKey + ", SignedHeaders=" + signedHeadersStr.toString() + ", Signature=" + signature;
        // return Base64.getEncoder().encodeToString(headerValue.getBytes(StandardCharsets.UTF_8));
        return "Bearer " + Base64.getEncoder().encodeToString(headerValue.getBytes(StandardCharsets.UTF_8));
    }

    protected String jsonToString(Object obj, ObjectMapper objectMapper) {
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
        }

        try {
            return objectMapper.writeValueAsString(obj);
        } catch (Exception ex) {
            throw new AIException(AIException.code_serialization_err, "Json exception", ex);
        }
    }

    public String canonicalRequest(String method, String url, Map<String, String> headers, String body, List<String> signedHeaders) throws URISyntaxException {
        URI uri = new URI(url);
        String canonicalURI = uri.getPath() + "/";

        String canonicalQueryString = uri.getRawQuery();
        if (canonicalQueryString == null) {
            canonicalQueryString = "";
        }
        String canonicalHeaders = canonicalHeaders(headers, signedHeaders);
        String signedHeadersStr = String.join(";", signedHeaders);
        String locBody = body;
        if (locBody == null) {
            locBody = "";
        }
        return method + "\n" +
                canonicalURI + "\n" +
                canonicalQueryString + "\n" +
                canonicalHeaders + "\n" +
                signedHeadersStr.toString() + "\n" +
                hash(locBody);
    }

    public String canonicalHeaders(Map<String, String> headers, List<String> signedHeaders) {
        Map<String, String> lowheaders = new HashMap<>();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            lowheaders.put(entry.getKey().toLowerCase(), entry.getValue().trim());
        }
        List<String> a = new ArrayList<>();
        for (String key : signedHeaders) {
            a.add(key + ':' + lowheaders.get(key));
        }
        return join("\n", a);
    }

    public List<String> signedHeaders(Map<String, String> headers) {
        List<String> signedHeaders = new ArrayList<>();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            signedHeaders.add(entry.getKey().toLowerCase());
        }
        Collections.sort(signedHeaders);
        return signedHeaders;
    }

    public Map<String,String> sign(String url, String method, Map<String,String> headers, String body) throws URISyntaxException {
        LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BasicDateFormat);

        if (!headers.containsKey(HeaderXDate)) {
            headers.put(HeaderXDate, now.format(formatter));
        } else {
            now = LocalDateTime.parse(headers.get(HeaderXDate), formatter);
        }
        List<String> signedHeaders = this.signedHeaders(headers);
        String canonicalRequest = this.canonicalRequest(method, url, headers, body, signedHeaders);
        String stringToSign = this.stringToSign(canonicalRequest, now.format(formatter));
        String signature = this.signStringToSign(stringToSign, this.Secret);
        headers.put(HeaderAuthorization, this.authHeaderValue(signature, this.Key, signedHeaders));
        return headers;
    }

    public String stringToSign(String canonicalRequest, String timeFormat) {
        return Algorithm + "\n" +
                timeFormat + "\n" +
                hash(canonicalRequest);
    }

    private static String join(String delimiter, List<String> list) {
        StringBuilder sb = new StringBuilder();
        for (String item : list) {
            if (sb.length() > 0) {
                sb.append(delimiter);
            }
            sb.append(item);
        }
        return sb.toString();
    }

    private static String hash(String text) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(text.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hash);
        } catch (Exception e) {
            throw new RuntimeException("Unable to calculate a SHA-256 hash: " + e.getMessage(), e);
        }
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
}
