package ru.yandex.webmaster3.storage.util.yt;

import java.io.IOException;
import java.net.URI;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.util.UriComponentsBuilder;

/**
 * @author aherman
 */
public abstract class YtCommandImpl<T, HttpRequestType extends HttpUriRequest> implements YtCommand<T> {
    protected static String API_V3 = "/api/v3";

    protected static final String PARAMETER_PATH = "path";
    protected static final String PARAMETER_SOURCE_PATH = "source_path";
    protected static final String PARAMETER_DESTINATION_PATH = "destination_path";
    protected static final String PARAMETER_TYPE = "type";
    protected static final String PARAMETER_RECURSIVE = "recursive";
    protected static final String PARAMETER_FORCE = "force";
    protected static final String PARAMETER_TIMEOUT = "timeout";
    protected static final String PARAMETER_TARGET_PATH = "target_path";
    protected static final String PARAMETER_LINK_PATH = "link_path";
    protected static final String PARAMETER_TRANSACTION_ID = "transaction_id";
    protected static final String PARAMETER_IGNORE_EXISTING = "ignore_existing";

    protected static String VALUE_TRUE = "true";

    private URI proxyUri;
    private YtTransaction transactionId;
    private String authToken;

    @Override
    public void setProxyUri(URI proxyUri) {
        this.proxyUri = proxyUri;
    }

    @Override
    public void setTransactionId(YtTransaction transactionId) {
        this.transactionId = transactionId;
    }

    @Override
    public void setAuthToken(String authToken) {
        this.authToken = authToken;
    }

    @Override
    public HttpUriRequest createRequest() throws YtException, JsonProcessingException {
        UriComponentsBuilder uriBuilder = createUri(proxyUri);
        if (transactionId != null) {
            uriBuilder.queryParam(PARAMETER_TRANSACTION_ID, transactionId.getId());
        }
        URI uri = uriBuilder.build().encode().toUri();

        HttpRequestType request = createHttpUriRequest(uri);
        addHeaders(request);

        request.setHeader(HttpHeaders.AUTHORIZATION, "OAuth " + authToken);
        Object parameters = getYtParameters();
        if (parameters != null) {
            request.setHeader("X-YT-Header-Format", "json");
            request.setHeader("X-YT-Parameters", YtService.OM.writeValueAsString(parameters));
        }
        return request;

    }

    protected abstract UriComponentsBuilder createUri(URI proxyUri);

    @NotNull
    protected abstract HttpRequestType createHttpUriRequest(URI uri) throws YtException;
    protected void addHeaders(HttpRequestType request) {}
    protected Object getYtParameters() {
        return null;
    }

    @Override
    public YtResult<T> processResponse(HttpResponse httpResponse) throws YtCommandException {
        YtStatus status = YtStatus.fromResponse(httpResponse);
        if (status == YtStatus.YT_200_OK || status == YtStatus.YT_202_SUCCESS) {
            return onSuccess(status, httpResponse);
        } else {
            return onError(status, httpResponse);
        }
    }

    protected YtResult<T> onSuccess(YtStatus status, HttpResponse httpResponse) throws YtCommandException {
        HttpEntity entity = httpResponse.getEntity();
        if (entity != null) {
            try {
                EntityUtils.consume(entity);
            } catch (IOException e) {
                return YtResult.createError(status, "Unable read response", e);
            }
        }
        return YtResult.createResult(status, null);
    }

    protected YtResult<T> onError(YtStatus status, HttpResponse response) throws YtCommandException {
        HttpEntity entity = response.getEntity();
        String error = null;
        if (entity != null) {
            try {
                error = EntityUtils.toString(entity);
            } catch (IOException e) {
                return YtResult.createError(status, "Unable read response", e);
            }
        }
        throw new YtCommandException(toString(), status, error);
    }

    public abstract static class GetCommand<T> extends YtCommandImpl<T, HttpGet> {
        @NotNull
        @Override
        protected HttpGet createHttpUriRequest(URI uri) throws YtException {
            HttpGet request = new HttpGet(uri);
            return request;
        }
    }

    public abstract static class PostCommand<T> extends YtCommandImpl<T, HttpPost> {
        @NotNull
        @Override
        protected HttpPost createHttpUriRequest(URI uri) throws YtException {
            HttpPost httpPost = new HttpPost(uri);
            HttpEntity requestBody = createRequestBody();
            if (requestBody != null) {
                httpPost.setEntity(requestBody);
            }
            return httpPost;
        }

        protected HttpEntity createRequestBody() {
            return null;
        }
    }

    abstract static class PutCommand<T> extends YtCommandImpl<T, HttpPut> {
        @NotNull
        @Override
        protected HttpPut createHttpUriRequest(URI uri) throws YtException {
            HttpPut httpPut = new HttpPut(uri);
            HttpEntity putBody = createRequestBody();
            if (putBody != null) {
                httpPut.setEntity(putBody);
            }

            return httpPut;
        }

        protected HttpEntity createRequestBody() {
            return null;
        }
    }
}
