package ru.yandex.qe.hitman.tvm.v1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by akhvorov on 07.09.17.
 */
abstract class AbstractTVM1TicketService implements TVM1TicketService {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractTVM1TicketService.class);
    private static final String tvmApiUrl = "tvm-api.yandex.net";
    private static final String tvmSchema = "https";
    private static final String tvmPath = "/ticket";
    private static final String SIGN_ALGORITHM = "HmacSHA256";

    protected String tsSign(final String secret, final String timestamp) {
        final Mac mac;
        try {
            mac = Mac.getInstance(SIGN_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(
                    "FatalException: Failed to get instance of " + SIGN_ALGORITHM + " crypto algorithm", e);
        }

        final SecretKeySpec secretKey = new SecretKeySpec(Base64.getUrlDecoder().decode(secret), SIGN_ALGORITHM);
        try {
            mac.init(secretKey);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("NonFatalException: Failed to init secret key for " + SIGN_ALGORITHM, e);
        }
        final String sign = Base64.getUrlEncoder().encodeToString(mac.doFinal(timestamp.getBytes()));
        return sign.substring(0, sign.length() - 1);
    }

    protected HttpPost buildRequest(final String clientId, final String tsSign, final String timestamp) {
        try {
            final URIBuilder uriBuilder = new URIBuilder().setScheme(tvmSchema).setHost(tvmApiUrl).setPath(tvmPath);
            final HttpPost request = new HttpPost(uriBuilder.build());
            final List<NameValuePair> requestParams = Arrays.asList(
                    new BasicNameValuePair("grant_type", "client_credentials"),
                    new BasicNameValuePair("client_id", clientId),
                    new BasicNameValuePair("ts", timestamp),
                    new BasicNameValuePair("ts_sign", tsSign)
            );
            request.setEntity(new UrlEncodedFormEntity(requestParams));
            return request;
        } catch (URISyntaxException e) {
            throw new RuntimeException("URI is incorrect. Maybe some of params is missed", e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("can't set params to request", e);
        }
    }

    protected String parseResponse(final HttpResponse response) {
        LOG.debug("Response: {}", response.getStatusLine());
        try (InputStream responseEntity = response.getEntity().getContent()) {
            final StringBuilder ticketSB = new StringBuilder();
            new BufferedReader(new InputStreamReader(responseEntity))
                    .lines()
                    .forEach(ticketSB::append);
            final String ticket = ticketSB.toString();
            if (!ticket.contains("error")) {
                return ticket;
            } else {
                throw new RuntimeException("can't get ticket from TVM. TVM answer: " + ticket);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}
