package com.armstrongconsulting.acprotasks.client;

import java.nio.charset.StandardCharsets;
import java.util.Date;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import com.armstrongconsulting.acprotasks.AcproRepository;

import feign.Feign;
import feign.Logger;
import feign.Response;
import feign.RetryableException;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import feign.httpclient.ApacheHttpClient;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import lombok.extern.slf4j.Slf4j;

public class AcproConnector
{
    private static AcproClient client;

    public static AcproClient getClient(@NotNull AcproRepository repository)
    {
        if (client != null)
        {
            return client;
        }

        client = Feign.builder()
            .client(new ApacheHttpClient())
            .encoder(new JacksonEncoder())
            .decoder((response, type) -> {
                if (type.equals(String.class))
                {
                    return IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8.name());
                }
                else
                {
                    return new JacksonDecoder().decode(response, type);
                }
            })
            .logger(new Logger()
            {
                @Override
                protected void log(String configKey, String format, Object... args)
                {
                    System.out.println(String.format(format, args));
                }
            })
            .logLevel(Logger.Level.FULL)
            .requestInterceptor(
                requestTemplate -> {
                    if (!StringUtils.isBlank(repository.getApiKey()))
                    {
                        requestTemplate.query("api_key", repository.getApiKey());
                    }
                })
            .errorDecoder(new CustomErrorDecoder())
            .retryer(new ReauthenticateRetryer(repository))
            .target(AcproClient.class, repository.getUrl());

        return client;
    }

    private AcproConnector()
    {
    }

    @Slf4j
    private static class CustomErrorDecoder implements ErrorDecoder
    {
        @Override
        public Exception decode(String methodKey, Response response)
        {
            if (response.status() == 401 && !response.request().url().contains("/api_key"))
            {
                log.warn("Login expired. Throwing retryable exception.");
                throw new RetryableException(response.status(), response.reason(), response.request().httpMethod(),
                    new Date(), response.request());
            }
            return new ErrorDecoder.Default().decode(methodKey, response);
        }
    }

    @Slf4j
    private static class ReauthenticateRetryer implements Retryer
    {
        private final AcproRepository repository;
        private boolean retried;

        ReauthenticateRetryer(AcproRepository repository)
        {
            this.repository = repository;
            this.retried = false;
        }

        ReauthenticateRetryer(ReauthenticateRetryer other)
        {
            this.repository = other.repository;
            this.retried = other.retried;
        }

        @Override
        public void continueOrPropagate(RetryableException e)
        {
            log.warn("Retrying");

            if (retried)
            {
                throw e;
            }

            repository.setApiKey(client.getApiKey(repository.getUsername(), repository.getPassword()));
            this.retried = true;
        }

        @Override
        public Retryer clone()
        {
            return new ReauthenticateRetryer(this);
        }
    }
}
