package org.imsglobal.lti.launch;

import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthException;
import net.oauth.OAuthMessage;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.http.HttpParameters;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

/**
 * This class <b>signs</b> LTI requests according to the Oauth 1.0 spec
 * @author  Paul Gray
 * @since   1.1
 */
public class LtiOauthSigner implements LtiSigner {

    private MessageDigest md;

    public LtiOauthSigner() {
        try{
            md = MessageDigest.getInstance("SHA1");
        } catch(NoSuchAlgorithmException e) {
            throw new RuntimeException("Could not construct new instance of LtiOauthSigner", e);
        }
    }

    public LtiOauthSigner(MessageDigest md) {
        this.md = md;
    }

    @Override
    public HttpRequest sign(HttpRequest request, String key, String secret) throws LtiSigningException {
        CommonsHttpOAuthConsumer signer = new CommonsHttpOAuthConsumer(key, secret);
        try {
            String body = getRequestBody(request);
            String bodyHash = new String(Base64.encodeBase64(md.digest(body.getBytes())));

            HttpParameters params = new HttpParameters();
            params.put("oauth_body_hash", URLEncoder.encode(bodyHash, "UTF-8"));
            signer.setAdditionalParameters(params);

            signer.sign(request);
        } catch (OAuthMessageSignerException|OAuthExpectationFailedException|OAuthCommunicationException|IOException e) {
            throw new LtiSigningException("Exception encountered while singing Lti request...", e);
        }
        return request;
    }

    @Override
    public Map<String, String> signParameters(Map<String, String> parameters, String key, String secret, String url, String method) throws LtiSigningException {
        OAuthMessage oam = new OAuthMessage(method, url, parameters.entrySet());
        OAuthConsumer cons = new OAuthConsumer(null, key, secret, null);
        OAuthAccessor acc = new OAuthAccessor(cons);
        try {
            oam.addRequiredParameters(acc);

            Map<String, String> signedParameters = new HashMap<>();
            for(Map.Entry<String, String> param : oam.getParameters()){
                signedParameters.put(param.getKey(), param.getValue());
            }
            return signedParameters;
        } catch (OAuthException |IOException |URISyntaxException e) {
            throw new LtiSigningException("Error signing LTI request.", e);
        }
    }

    private String getRequestBody(HttpRequest req) throws IOException {
        if(req instanceof HttpEntityEnclosingRequest){
            HttpEntity body = ((HttpEntityEnclosingRequest) req).getEntity();
            if(body == null) {
                return "";
            } else {
                return IOUtils.toString(body.getContent());
            }
        } else {
            // requests with no entity have an empty string as the body
            return "";
        }
    }

}