package com.uservoice.uservoicesdk.rest;

import android.net.Uri;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;

import com.uservoice.uservoicesdk.Session;
import com.uservoice.uservoicesdk.UserVoice;
import com.uservoice.uservoicesdk.model.AccessToken;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
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.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import oauth.signpost.OAuthConsumer;

public class RestTask extends AsyncTask<String, String, RestResult> {
    private String urlPath;
    private RestMethod method;
    private List<BasicNameValuePair> params;
    private RestTaskCallback callback;
    private HttpUriRequest request;

    public RestTask(RestMethod method, String urlPath, Map<String, String> params, RestTaskCallback callback) {
        this(method, urlPath, params == null ? null : paramsToList(params), callback);
    }

    public RestTask(RestMethod method, String urlPath, List<BasicNameValuePair> params, RestTaskCallback callback) {
        this.method = method;
        this.urlPath = urlPath;
        this.callback = callback;
        this.params = params;
    }

    public static List<BasicNameValuePair> paramsToList(Map<String, String> params) {
        ArrayList<BasicNameValuePair> formList = new ArrayList<BasicNameValuePair>(params.size());
        for (String key : params.keySet()) {
            String value = params.get(key);
            if (value != null)
                formList.add(new BasicNameValuePair(key, value));
        }
        return formList;
    }

    @Override
    protected RestResult doInBackground(String... args) {
        try {
            request = createRequest();
            if (isCancelled())
                throw new InterruptedException();
            OAuthConsumer consumer = Session.getInstance().getOAuthConsumer();
            if (consumer != null) {
                AccessToken accessToken = Session.getInstance().getAccessToken();
                if (accessToken != null) {
                    consumer.setTokenWithSecret(accessToken.getKey(), accessToken.getSecret());
                }
                consumer.sign(request);
            }
            request.setHeader("Accept-Language", Locale.getDefault().getLanguage());
            request.setHeader("API-Client", String.format("uservoice-android-%s", UserVoice.getVersion()));
            AndroidHttpClient client = AndroidHttpClient.newInstance(String.format("uservoice-android-%s", UserVoice.getVersion()), Session.getInstance().getContext());
            if (isCancelled())
                throw new InterruptedException();
            // TODO it would be nice to find a way to abort the request on cancellation
            HttpResponse response = client.execute(request);
            if (isCancelled())
                throw new InterruptedException();
            HttpEntity responseEntity = response.getEntity();
            StatusLine responseStatus = response.getStatusLine();
            int statusCode = responseStatus != null ? responseStatus.getStatusCode() : 0;
            String body = responseEntity != null ? EntityUtils.toString(responseEntity) : null;
            client.close();
            if (isCancelled())
                throw new InterruptedException();
            return new RestResult(statusCode, new JSONObject(body));
        } catch (Exception e) {
            return new RestResult(e);
        }
    }

    private HttpUriRequest createRequest() throws URISyntaxException, UnsupportedEncodingException {
        String host = Session.getInstance().getConfig().getSite();
        Uri.Builder uriBuilder = new Uri.Builder();
        uriBuilder.scheme(host.contains(".us.com") ? "http" : "https");
        uriBuilder.encodedAuthority(host);
        uriBuilder.path(urlPath);
        if (method == RestMethod.GET)
            return requestWithQueryString(new HttpGet(), uriBuilder);
        else if (method == RestMethod.DELETE)
            return requestWithQueryString(new HttpDelete(), uriBuilder);
        else if (method == RestMethod.POST)
            return requestWithEntity(new HttpPost(), uriBuilder);
        else if (method == RestMethod.PUT)
            return requestWithEntity(new HttpPut(), uriBuilder);
        else
            throw new IllegalArgumentException("Method must be one of [GET, POST, PUT, DELETE], but was " + method);
    }

    @Override
    protected void onPostExecute(RestResult result) {
        if (result.isError()) {
            callback.onError(result);
        } else {
            try {
                callback.onComplete(result.getObject());
            } catch (JSONException e) {
                callback.onError(new RestResult(e, result.getStatusCode(), result.getObject()));
            }
        }
        super.onPostExecute(result);
    }

    private HttpUriRequest requestWithQueryString(HttpRequestBase request, Uri.Builder uriBuilder) throws URISyntaxException {
        if (params != null) {
            for (BasicNameValuePair param : params) {
                uriBuilder.appendQueryParameter(param.getName(), param.getValue());
            }
        }
        request.setURI(new URI(uriBuilder.build().toString()));
        return request;
    }

    private HttpUriRequest requestWithEntity(HttpEntityEnclosingRequestBase request, Uri.Builder uriBuilder) throws UnsupportedEncodingException, URISyntaxException {
        if (params != null) {
            request.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
        }
        request.setURI(new URI(uriBuilder.build().toString()));
        return request;
    }
}