package be.iminds.ilabt.jfed.lowlevel.api;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.connection.HttpConnection;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.net.MalformedURLException;
import java.util.Collections;
import java.util.Map;

/**
 * RestApi
 */
public abstract class RestApi extends AbstractApi {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(RestApi.class);


    protected static final Map<String, Object> EMPTY_URL_PARAMS = Collections.emptyMap();

    /**
     * @param logger the logger to use. May be null if no logger is used.
     */
    public RestApi(@Nonnull Logger logger, ApiInfo.Api serverType, @Nonnull JFedPreferences jFedPreferences) {
        super(logger, serverType, jFedPreferences);
    }

    @Override
    protected boolean isBusyReply(XMLRPCCallDetails res) {
        return false;
    }


    public RestReply<String> internalGet(HttpConnection con, String javaName, String relUrl, Map<String, Object> methodParams, Map<String, Object> urlParams) throws JFedException {
        return internalGet(con, javaName, relUrl, methodParams, urlParams, null);
    }
    public RestReply<String> internalGet(HttpConnection con, String javaName, String relUrl, Map<String, Object> methodParams,
                                         Map<String, Object> urlParams, Map<String, String> extraHeaders) throws JFedException {
        LOG.trace(getName()+"." + javaName + " params: " + methodParams);
        assert con != null;

        String url = con.getServerUrl() + relUrl;

        HttpCallDetails reply;
        try {
            reply = executeHttpCall(con, relUrl, AbstractApi.HttpVerb.GET, url,
                    null/*requestContentType*/, null/*requestContent*/,
                    javaName, methodParams, urlParams, extraHeaders);
        } catch (MalformedURLException e) {
            throw new JFedException("Internal error constructing new URL. Invalid URL: \""+url+"\"", e);
        }

        RestReply<String> res = new RestReply<>(reply, reply.getResultHttpContent());
        logNonSfa(reply, res, javaName, relUrl, con, methodParams, null);
        return res;
    }


    public RestReply<String> internalPost(HttpConnection con, String javaName, String relUrl,
                                          Map<String, Object> methodParams, Map<String, Object> urlParams, HttpEntity content) throws JFedException {
        return internalPost(con, javaName, relUrl, methodParams, urlParams, content, null);
    }
    public RestReply<String> internalPost(HttpConnection con, String javaName, String relUrl,
                                          Map<String, Object> methodParams, Map<String, Object> urlParams, HttpEntity content,
                                          Map<String, String> extraHeaders) throws JFedException {
        LOG.trace(getName()+"." + javaName + " params: " + methodParams);
        assert con != null;

        String url = con.getServerUrl() + relUrl;

        HttpCallDetails reply;
        try {
            reply = executeHttpCall(con, relUrl, HttpVerb.POST, url,
                    content,
                    javaName, methodParams, urlParams, extraHeaders);
        } catch (MalformedURLException e) {
            throw new JFedException("Internal error constructing new URL. Invalid URL: \""+url+"\"", e);
        }

        RestReply<String> res = new RestReply<>(reply, reply.getResultHttpContent());
        logNonSfa(reply, res, javaName, relUrl, con, methodParams, null);
        return res;
    }
    public RestReply<String> internalPost(HttpConnection con, String javaName, String relUrl,
                                          Map<String, Object> methodParams, Map<String, Object> urlParams, ContentType contentType, String content) throws JFedException {
        return internalPost(con, javaName, relUrl, methodParams, urlParams, contentType, content, null);
    }
    public RestReply<String> internalPost(HttpConnection con, String javaName, String relUrl,
                                          Map<String, Object> methodParams, Map<String, Object> urlParams, ContentType contentType, String content,
                                          Map<String, String> extraHeaders) throws JFedException {
        LOG.trace(getName()+"." + javaName + " params: " + methodParams);
        assert con != null;

        String url = con.getServerUrl() + relUrl;

        HttpCallDetails reply;
        try {
            reply = executeHttpCall(con, relUrl, HttpVerb.POST, url,
                    contentType, content,
                    javaName, methodParams, urlParams, extraHeaders);
        } catch (MalformedURLException e) {
            throw new JFedException("Internal error constructing new URL. Invalid URL: \""+url+"\"", e);
        }

        RestReply<String> res = new RestReply<>(reply, reply.getResultHttpContent());
        logNonSfa(reply, res, javaName, relUrl, con, methodParams, null);
        return res;
    }

    public RestReply<String> internalPut(HttpConnection con, String javaName, String relUrl,
                                         Map<String, Object> methodParams, Map<String, Object> urlParams, ContentType contentType, String content) throws JFedException {
        return internalPut(con, javaName, relUrl, methodParams, urlParams, contentType, content, null);
    }
    public RestReply<String> internalPut(HttpConnection con, String javaName, String relUrl,
                                         Map<String, Object> methodParams, Map<String, Object> urlParams, ContentType contentType, String content,
                                         Map<String, String> extraHeaders) throws JFedException {
        LOG.trace(getName()+"." + javaName + " params: " + methodParams);
        assert con != null;

        String url = con.getServerUrl() + relUrl;

        HttpCallDetails reply;
        try {
            reply = executeHttpCall(con, relUrl, HttpVerb.PUT, url,
                    contentType, content,
                    javaName, methodParams, urlParams, extraHeaders);
        } catch (MalformedURLException e) {
            throw new JFedException("Internal error constructing new URL. Invalid URL: \""+url+"\"", e);
        }

        RestReply<String> res = new RestReply<>(reply, reply.getResultHttpContent());
        logNonSfa(reply, res, javaName, relUrl, con, methodParams, null);
        return res;
    }


    public RestReply<String> internalDelete(HttpConnection con, String javaName, String relUrl,
                                            Map<String, Object> methodParams, Map<String, Object> urlParams) throws JFedException {
        return internalDelete(con, javaName, relUrl, methodParams, urlParams, null);
    }
    public RestReply<String> internalDelete(HttpConnection con, String javaName, String relUrl,
                                            Map<String, Object> methodParams, Map<String, Object> urlParams,
                                            Map<String, String> extraHeaders) throws JFedException {
        LOG.trace(getName()+"." + javaName + " params: " + methodParams);
        assert con != null;

        String url = con.getServerUrl() + relUrl;

        HttpCallDetails reply;
        try {
            reply = executeHttpCall(con, relUrl, HttpVerb.DELETE, url,
                    null/*requestContentType*/, null/*requestContent*/,
                    javaName, methodParams, urlParams, extraHeaders);
        } catch (MalformedURLException e) {
            throw new JFedException("Internal error constructing new URL. Invalid URL: \""+url+"\"", e);
        }

        RestReply<String> res = new RestReply<>(reply, reply.getResultHttpContent());
        logNonSfa(reply, res, javaName, relUrl, con, methodParams, null);
        return res;
    }

    public RestReply<String> internalDelete(HttpConnection con, String javaName, String relUrl,
                                            Map<String, Object> methodParams, Map<String, Object> urlParams,
                                            HttpEntity content,
                                            Map<String, String> extraHeaders) throws JFedException {
        LOG.trace(getName()+"." + javaName + " params: " + methodParams);
        assert con != null;

        String url = con.getServerUrl() + relUrl;

        HttpCallDetails reply;
        try {
            reply = executeHttpCall(con, relUrl, HttpVerb.DELETE, url, content,
                    javaName, methodParams, urlParams, extraHeaders);
        } catch (MalformedURLException e) {
            throw new JFedException("Internal error constructing new URL. Invalid URL: \""+url+"\"", e);
        }

        RestReply<String> res = new RestReply<>(reply, reply.getResultHttpContent());
        logNonSfa(reply, res, javaName, relUrl, con, methodParams, null);
        return res;
    }

    public static class RestReply<T> implements ApiCallReply<T> {
        private final T value;
        private final HttpCallDetails httpCallDetails;
        private final int httpCode;
        private final String httpCodeDescription;

        public RestReply(HttpCallDetails httpCallDetails, T value) {
            this.httpCallDetails = httpCallDetails;
            this.httpCode = httpCallDetails.getResultHttpStatusCode();
            this.httpCodeDescription = httpCallDetails.getResultHttpStatusLine();
            this.value = value;
        }

        @Override
        public GeniResponseCode getGeniResponseCode() {
            return new GeniResponseCode() {
                @Override
                public boolean isSuccess() {
                    return httpCode >= 200 && httpCode < 300;
                }

                @Override
                public boolean isBusy() {
                    return httpCode == 500 || httpCode == 503;
                } //NOTE: These codes CAN be busy, but are not always!

                @Override
                public int getCode() {
                    return httpCode;
                }

                @Override
                public String getDescription() {
                    return httpCodeDescription;
                }
            };
        }

        @Override
        public T getValue() {
            return value;
        }


        /**
         * DO NOT USE.
         * Always returns null.
         */
        @Override
        public String getOutput() {
            return null;
        }

        /**
         * DO NOT USE.
         * Always returns null.
         */
        @Override
        public Object getRawResult() {
            return null;
        }

        @Override
        public Object getRawValue() {
            return getValue();
        }

        public HttpCallDetails getHttpCallDetails() {
            return httpCallDetails;
        }

        private boolean busy;

        @Override
        public String toString() {
            return "RestReply{" +
                    "value=" + value +
                    ", httpCallDetails=" + httpCallDetails +
                    ", httpCode=" + httpCode +
                    ", httpCodeDescription='" + httpCodeDescription + '\'' +
                    ", busy=" + busy +
                    '}';
        }
    }
}
