package com.orlandovald.twitter;

import com.twitter.joauth.Normalizer;
import com.twitter.joauth.OAuthParams;
import com.twitter.joauth.Request;
import com.twitter.joauth.Signer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.web.util.UriUtils;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Creates the Authorization header signature to authenticate to the Twitter API. Good for this demo, terrible
 * for any other purpose. Highly inspired (ahem copied) from {@see com.twitter.hbc.httpclient.auth.OAuth1}
 *
 * @author ovaldez
 */
public class TwitterOAuth {

    public static final String OAUTH1_HEADER_AUTHTYPE = "OAuth ";
    public static final String OAUTH_TOKEN = "oauth_token";
    public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
    public static final String OAUTH_SIGNATURE = "oauth_signature";
    public static final String OAUTH_NONCE = "oauth_nonce";
    public static final String OAUTH_TIMESTAMP = "oauth_timestamp";
    public static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
    public static final String OAUTH_VERSION = "oauth_version";
    public static final String HMAC_SHA1 = "HMAC-SHA1";
    public static final String ONE_DOT_OH = "1.0";

    private final String consumerKey;
    private final String consumerSecret;
    private final String token;
    private final String tokenSecret;
    private final Normalizer normalizer;
    private final Signer signer;
    private final SecureRandom secureRandom;

    @Autowired
    public TwitterOAuth(TwitterProperties twitterProps) {
        this.consumerKey = twitterProps.getConsumerKey();
        this.consumerSecret = twitterProps.getConsumerSecret();
        this.token = twitterProps.getToken();
        this.tokenSecret = twitterProps.getSecret();
        this.normalizer = Normalizer.getStandardNormalizer();
        this.signer = Signer.getStandardSigner();
        this.secureRandom = new SecureRandom();
    }

    public String oAuth1Header(URI requestUri, HttpMethod httpMethod, Map<String, String> bodyParams) {
        List<Request.Pair> requestParams = new ArrayList(bodyParams.size());
        for (Map.Entry<String, String> entry : bodyParams.entrySet()) {
            requestParams.add(new Request.Pair(urlEncode(entry.getKey()), urlEncode(entry.getValue())));
        }

        long timestampSecs = this.generateTimestamp();
        String nonce = this.generateNonce();
        OAuthParams.OAuth1Params oAuth1Params = new OAuthParams.OAuth1Params(this.token, this.consumerKey, nonce, Long.valueOf(timestampSecs),
                Long.toString(timestampSecs), "", HMAC_SHA1, ONE_DOT_OH);

        int port = requestUri.getPort();

        if(port <= 0) {
            if(requestUri.getScheme().equalsIgnoreCase("http")) {
                port = 80;
            } else {
                if(!requestUri.getScheme().equalsIgnoreCase("https")) {
                    throw new IllegalStateException("Bad URI scheme: " + requestUri.getScheme());
                }
                port = 443;
            }
        }

        String normalized = this.normalizer.normalize(requestUri.getScheme(), requestUri.getHost(), port, httpMethod.name().toUpperCase(),
                requestUri.getPath(), requestParams, oAuth1Params);

        String signature;
        try {
            signature = this.signer.getString(normalized, this.tokenSecret, this.consumerSecret);
        } catch (InvalidKeyException invalidKeyEx) {
            throw new RuntimeException(invalidKeyEx);
        } catch (NoSuchAlgorithmException noSuchAlgoEx) {
            throw new RuntimeException(noSuchAlgoEx);
        }

        Map<String, String> oauthHeaders = new HashMap();
        oauthHeaders.put(OAUTH_CONSUMER_KEY, this.quoted(this.consumerKey));
        oauthHeaders.put(OAUTH_TOKEN, this.quoted(this.token));
        oauthHeaders.put(OAUTH_SIGNATURE, this.quoted(signature));
        oauthHeaders.put(OAUTH_SIGNATURE_METHOD, this.quoted(HMAC_SHA1));
        oauthHeaders.put(OAUTH_TIMESTAMP, this.quoted(Long.toString(timestampSecs)));
        oauthHeaders.put(OAUTH_NONCE, this.quoted(nonce));
        oauthHeaders.put(OAUTH_VERSION, this.quoted(ONE_DOT_OH));

        return OAUTH1_HEADER_AUTHTYPE
                + oauthHeaders.entrySet().stream().map(Map.Entry::toString).collect(Collectors.joining(", "));
    }

    private String quoted(String str) {
        return "\"" + str + "\"";
    }

    private long generateTimestamp() {
        long timestamp = System.currentTimeMillis();
        return timestamp / 1000L;
    }

    private String generateNonce() {
        return Long.toString(Math.abs(this.secureRandom.nextLong())) + System.currentTimeMillis();
    }

    public String urlEncode(String source) {
        return UriUtils.encode(source, StandardCharsets.UTF_8);
    }

}