package ru.yandex.webmaster3.core.security.tvm;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import org.apache.commons.io.IOUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.passport.tvmauth.deprecated.ServiceContext;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.metrics.externals.AbstractExternalAPIService;
import ru.yandex.webmaster3.core.metrics.externals.ExternalDependencyMethod;
import ru.yandex.webmaster3.core.util.JavaMethodWitness;
import ru.yandex.webmaster3.core.util.RetryUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author avhaliullin
 */
public class TVMTokenService extends AbstractExternalAPIService {
    private final static Logger log = LoggerFactory.getLogger(TVMTokenService.class);

    public static final String TVM2_TICKET_HEADER = "X-Ya-Service-Ticket";
    public static final String TVM2_USER_TICKET_HEADER = "X-Ya-User-Ticket";

    private static final String GRANT_TYPE = "client_credentials";
    private static final long TOKEN_TTL_SECONDS = 290;

    private int clientId;
    private String secret;
    private String tvmApiAddress;
    private String destinationClientId;
    private TVM2RefreshKeyService tvm2RefreshKeyService;
    private ServiceContext serviceContext;

    private CloseableHttpClient httpClient = HttpClients.createDefault();
    private Supplier<String> tokenCache = Suppliers.memoizeWithExpiration(this::getTokenFromTvmWithRetry, TOKEN_TTL_SECONDS, TimeUnit.SECONDS);

    public void init() {
        serviceContext = new ServiceContext(clientId, secret, tvm2RefreshKeyService.getTvmKeys());
    }


    public String getToken() {
        return tokenCache.get();
    }

    private String getTokenFromTvmWithRetry() {
        try {
            return RetryUtils.query(RetryUtils.instantRetry(5), this::getTokenFromTvm);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    @ExternalDependencyMethod("ticket")
    private String getTokenFromTvm() {
        return trackQuery(new JavaMethodWitness() {}, ALL_ERRORS_INTERNAL, () -> {
            HttpPost postRequest = new HttpPost(tvmApiAddress + "/2/ticket");
            List<NameValuePair> postParameters = new ArrayList<>();
            postParameters.add(new BasicNameValuePair("src", String.valueOf(clientId)));
            postParameters.add(new BasicNameValuePair("dst", destinationClientId));
            postParameters.add(new BasicNameValuePair("grant_type", GRANT_TYPE));
            long ts = System.currentTimeMillis() / 1000;
            postParameters.add(new BasicNameValuePair("ts", String.valueOf(ts)));
            postParameters.add(new BasicNameValuePair("sign",
                    serviceContext.signCgiParamsForTvm(String.valueOf(ts), destinationClientId)));
            postRequest.setEntity(new UrlEncodedFormEntity(postParameters, StandardCharsets.UTF_8));

            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                String jsonString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
                JSONObject obj = new JSONObject(jsonString);
                return obj.getJSONObject(destinationClientId).getString("ticket");
            } catch (IOException | JSONException e) {
                throw new WebmasterException("Failed to get tvm response", new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null), e);
            }
        });
    }

    @Required
    public void setClientId(int clientId) {
        this.clientId = clientId;
    }

    @Required
    public void setSecret(String secret) {
        this.secret = secret;
    }

    @Required
    public void setTvmApiAddress(String tvmApiAddress) {
        this.tvmApiAddress = tvmApiAddress;
    }

    @Required
    public void setTvm2RefreshKeyService(TVM2RefreshKeyService tvm2RefreshKeyService) {
        this.tvm2RefreshKeyService = tvm2RefreshKeyService;
    }

    @Required
    public void setDestinationClientId(String destinationClientId) {
        this.destinationClientId = destinationClientId;
    }
}
