package com.emarsys.escher;

import org.apache.http.client.utils.URLEncodedUtils;

import javax.xml.bind.DatatypeConverter;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BinaryOperator;

class Helper {

    private static final char NEW_LINE = '\n';

    private final Config config;


    public Helper (Config config) {
        this.config = config;
    }


    public String canonicalize(Request request) throws EscherException {
        try {
            return request.getHttpMethod() + NEW_LINE +
                    request.getURI().toURL().getPath() + NEW_LINE +
                    canonicalizeQueryParameters(request) + NEW_LINE +
                    canonicalizeHeaders(request.getRequestHeaders()) + NEW_LINE +
                    NEW_LINE +
                    signedHeaders(request.getRequestHeaders()) + NEW_LINE +
                    Hmac.hash(request.getBody());
        } catch (MalformedURLException e) {
            throw new EscherException(e);
        }
    }


    private String canonicalizeQueryParameters(Request request) {
        return URLEncodedUtils.parse(request.getURI(), "utf-8")
                .stream()
                .map(entry -> entry.getName() + "=" + URLEncoder.encode(entry.getValue()))
                .sorted()
                .reduce(byJoiningWith('&'))
                .orElseGet(() -> "");
    }


    private String canonicalizeHeaders(List<Request.Header> headers) {
        return headers
                .stream()
                .map(header -> header.getFieldName().toLowerCase() + ":" + header.getFieldValue().trim())
                .sorted()
                .reduce(byJoiningWith(NEW_LINE))
                .orElseGet(() -> "");
    }


    private String signedHeaders(List<Request.Header> headers) {
        return headers
                .stream()
                .map(header -> header.getFieldName().toLowerCase())
                .sorted()
                .reduce(byJoiningWith(';'))
                .orElseGet(() -> "");
    }


    private BinaryOperator<String> byJoiningWith(char separator) {
        return (s1, s2) -> s1 + separator + s2;
    }


    public String calculateStringToSign(String credentialScope, String canonicalizedRequest) throws EscherException {
        return config.getFullAlgorithm() + NEW_LINE
                + config.getLongFormatDate() + NEW_LINE
                + config.getShortFormatDate() + "/" + credentialScope + NEW_LINE
                + Hmac.hash(canonicalizedRequest);
    }


    public byte[] calculateSigningKey(String secret, String credentialScope) throws EscherException {
        byte[] key = Hmac.sign(config.getHashAlgo(), (config.getAlgoPrefix() + secret), config.getShortFormatDate());

        for (String credentialPart : credentialScope.split("/")) {
            key = Hmac.sign(config.getHashAlgo(), key, credentialPart);
        }

        return key;
    }


    public String calculateAuthHeader(String accessKeyId, String credentialScope, List<String> signedHeaders, String signature) {
        return config.getFullAlgorithm() +
                " Credential=" + credentials(accessKeyId, credentialScope) +
                ", SignedHeaders=" + signedHeaders.stream().reduce((s1, s2) -> s1 + ";" + s2).get().toLowerCase() +
                ", Signature=" + signature;
    }


    public String calculateSignature(byte[] signingKey, String stringToSign) throws EscherException {
        return DatatypeConverter.printHexBinary(Hmac.sign(config.getHashAlgo(), signingKey, stringToSign)).toLowerCase();
    }


    private String credentials(String accessKeyId, String credentialScope) {
        return accessKeyId + "/" + config.getShortFormatDate() + "/" + credentialScope;
    }


    public Map<String, String> calculateSigningParams(String accessKeyId, String credentialScope, int expires) {
        Map<String, String> params = new TreeMap<>();
        params.put("SignedHeaders", "host");
        params.put("Expires", Integer.toString(expires));
        params.put("Algorithm", config.getFullAlgorithm());
        params.put("Credentials", credentials(accessKeyId, credentialScope));
        params.put("Date", config.getLongFormatDate());

        return params;
    }


    public void addDateHeader(Request request) {
        if (!request.hasHeader(config.getDateHeaderName())) {
            request.addHeader(config.getDateHeaderName(), config.getLongFormatDate());
        }
    }


    public void addAuthHeader(Request request, String header) {
        request.addHeader(config.getAuthHeaderName(), header);
    }

}

@@ -22,16 +22,54 @@
 scher;%0A%0A
+import org.apache.http.NameValuePair;%0A
 import o
@@ -151,24 +151,69 @@
 eConverter;%0A
+import java.io.UnsupportedEncodingException;%0A
 import java.
@@ -443,16 +443,67 @@
  = '%5Cn';
+%0A    private static final String CHARSET = %22UTF-8%22;
 %0A%0A    pr
@@ -1354,15 +1354,15 @@
 (), 
-%22utf-8%22
+CHARSET
 )%0A  
@@ -1410,76 +1410,36 @@
 map(
-entry -%3E entry.getName() + %22=%22 + URLEncoder.encode(entry.getValue())
+this::queryParameterToString
 )%0A  
@@ -1544,32 +1544,344 @@
 -%3E %22%22);%0A    %7D%0A%0A%0A
+    private String queryParameterToString(NameValuePair entry) %7B%0A        try %7B%0A            return entry.getName() + %22=%22 + URLEncoder.encode(entry.getValue(), CHARSET);%0A        %7D catch (UnsupportedEncodingException shouldNeverHappen) %7B%0A            throw new RuntimeException(shouldNeverHappen);%0A        %7D%0A    %7D%0A%0A%0A
     private Stri

remove deprecated url encoding