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

import java.io.IOException;
import java.io.InputStream;
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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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 AbstractTVM2TicketService implements TVM2TicketService {

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

    private final ObjectMapper objectMapper;

    public AbstractTVM2TicketService(final ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    protected static HttpPost buildRequest(final String clientId, final List<String> dstIds,
                                           final String sign, final String timestamp) {
        final StringBuilder dst;
        if (dstIds.size() > 0) {
            dst = new StringBuilder(dstIds.get(0));
            for (int i = 1; i < dstIds.size(); i++) {
                dst.append(",");
                dst.append(dstIds.get(i));
            }
        } else {
            throw new IllegalArgumentException("list of distances is empty");
        }
        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("src", clientId),
                    new BasicNameValuePair("dst", dst.toString()),
                    new BasicNameValuePair("ts", timestamp),
                    new BasicNameValuePair("sign", sign)
            );
            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 add params to request", e);
        }
    }

    protected String sign(final String destinationId, final String secret, final String timestamp) {
        final String param = timestamp + "|" + destinationId + "||";
        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);
        }
        String sign = Base64.getUrlEncoder().encodeToString(mac.doFinal(param.getBytes()));
        return sign.substring(0, sign.length() - 1);
    }

    protected String parseResponse(final HttpResponse response, final String destinationId) {
        LOG.debug("Response: {}", response.getStatusLine());
        try (InputStream responseEntity = response.getEntity().getContent()) {
            final ObjectNode node = objectMapper.readValue(responseEntity, ObjectNode.class);
            final JsonNode ticketNode = node.get(destinationId);
            if (ticketNode.has("error")) {
                throw new RuntimeException("can't get ticket from TVM. Error msg from TVM: "
                        + ticketNode.get("error").asText());
            }
            return ticketNode.get("ticket").asText();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}
