package kr.co.hanwha.hifive.network.hanwha;

import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import com.google.gson.Gson;

import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

import kr.co.hanwha.hifive.BuildConfig;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Created by sanghoonkwon on 2018. 1. 11..
 */

public class HanwhaNetworkClient<T> {
    final int connectTimeout;
    final int readTimeout;
    final int writeTimeout;

    public enum REST {
        GET, POST, DELETE, PUT, POST_URL
    }

    OkHttpClient client;

    public HanwhaNetworkClient() {
        this(new Builder());

        initOkHttpClient();
    }

    HanwhaNetworkClient(Builder builder) {
        this.connectTimeout = builder.connectTimeout;
        this.readTimeout = builder.readTimeout;
        this.writeTimeout = builder.writeTimeout;

        initOkHttpClient();
    }

    private void initOkHttpClient() {
        client = new OkHttpClient.Builder()
                .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .build();
    }

    public void request(@NonNull REST rest, @NonNull final String url, @NonNull final Class<T> object, @Nullable final HanwhaNetworkCallback callback) {
        request(rest, url, null, null, object, callback);
    }

    public void request(@NonNull REST rest, @NonNull final String url, @Nullable HashMap<String, Object> map, @NonNull final Class<T> object, @Nullable final HanwhaNetworkCallback callback) {
        request(rest, url, null, map, object, callback);
    }

    /**
     * @param rest     GET, POST, DELETE, PUT
     * @param url      request URL
     * @param header   headers
     * @param map      parameters
     * @param object   return class
     * @param callback callback
     */
    public void request(@NonNull REST rest, @NonNull final String url, @NonNull HashMap<String, String> header,
                        @Nullable HashMap<String, Object> map, @NonNull final Class<T> object, @Nullable final HanwhaNetworkCallback callback) {
        if (BuildConfig.DEBUG) {
            Log.i(getClass().getSimpleName(), "########################################");
            if (rest != null) Log.i(getClass().getSimpleName(), "REST: " + rest.name());
            if (url != null) Log.i(getClass().getSimpleName(), "URL: " + url);
            if (header != null) Log.i(getClass().getSimpleName(), "HEADER: " + header.toString());
//            if (map != null) Log.i(getClass().getSimpleName(), "PARAM: " + map.toString());
        }
        Request request = null;

        Headers headers = null;
        if (header != null) headers = Headers.of(header);

        String param = "";

        try {
            if (rest == REST.GET) {
                if (map != null) {
//                    "client_timezone" : "+09:00",//TODO - ADD...
//                    map.put("client_timezone", "+09:00");

                    for (String key : map.keySet()) {
                        String value = "";
                        if (map.get(key) instanceof String) {
                            value = (String) map.get(key);
                        }
                        if (param.length() == 0)
                            param += HanwhaNetworkUtil.getQuestionmarkWithParamURLEncoding(key, value);
                        else
                            param += HanwhaNetworkUtil.getAmpersandWithParamURLEncoding(key, value);
                    }
                }
                request = get(url, headers, param);
            } else if (rest == REST.POST) {
                if (map != null) {
                    HanwhaJsonMaker jsonMaker = new HanwhaJsonMaker();
                    for (String key : map.keySet()) {
                        jsonMaker.addParam(key, map.get(key));
                    }
                    param = jsonMaker.toString();
                    if (BuildConfig.DEBUG) {
                        if (param != null)
                            Log.i(getClass().getSimpleName(), "JSON: " + param.toString());
                        Log.i(getClass().getSimpleName(), "########################################");
                    }
                }
                request = post(url, headers, param);
            } else if (rest == REST.POST_URL) {
                if (map != null) {
                    HanwhaJsonMaker jsonMaker = new HanwhaJsonMaker();
                    for (String key : map.keySet()) {
                        jsonMaker.addParam(key, map.get(key));
                    }
                    param = jsonMaker.toString();
                }
                request = postUrl(url, headers, param);
            } else if (rest == REST.DELETE) {
                if (map != null) {
                    HanwhaJsonMaker jsonMaker = new HanwhaJsonMaker();
                    for (String key : map.keySet()) {
                        jsonMaker.addParam(key, map.get(key));
                    }
                    param = jsonMaker.toString();
                }
                request = delete(url, headers, param);
            } else if (rest == REST.PUT) {
                if (map != null) {
                    HanwhaJsonMaker jsonMaker = new HanwhaJsonMaker();
                    for (String key : map.keySet()) {
                        jsonMaker.addParam(key, map.get(key));
                    }
                    param = jsonMaker.toString();
                }
                request = put(url, headers, param);
            }
            if (request != null) {
                call(request, object, callback);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (callback != null) callback.onFailure(new HanwhaNetworkError(e));
        }


//        throw new RuntimeException("############################################################\n" +
//                "There is no type that matches the type!! make sure your using types correctly\n" +
//                "############################################################");
    }

    public Request get(@NonNull final String url, @Nullable String param) {
        return get(url, null, param);
    }

    public Request get(@NonNull final String url, @NonNull Headers headers, @Nullable String param) {
        Request request = null;
        if (param == null) {
            if (headers != null)
                request = new Request.Builder().url(url).headers(headers).get().build();
            else
                request = new Request.Builder().url(url).get().build();
        } else {
            if (headers != null)
                request = new Request.Builder().url(url + param).headers(headers).get().build();
            else
                request = new Request.Builder().url(url + param).get().build();
        }

        return request;
    }

    public Request post(@NonNull final String url, @NonNull String param) throws Exception {
        return post(url, null, param);
    }

    public Request post(@NonNull final String url, @NonNull Headers headers, @NonNull String param) throws Exception {
        Request request = null;
        if (headers != null)
            request = new Request.Builder().url(url).headers(headers).post(RequestBody.create(MediaType.parse("application/json"), param)).build();
        else
            request = new Request.Builder().url(url).post(RequestBody.create(MediaType.parse("application/json"), param)).build();
        return request;
    }

    public Request postUrl(@NonNull final String url, @NonNull Headers headers, @NonNull String param) {
        Request request = null;
        String encUrl = url + param.toString();
        if (headers != null)
            request = new Request.Builder().url(encUrl).headers(headers).post(RequestBody.create(MediaType.parse("application/json"), "")).build();
        else
            request = new Request.Builder().url(encUrl).post(RequestBody.create(MediaType.parse("application/json"), "")).build();
        return request;
    }

    public Request delete(@NonNull final String url, @NonNull String param) {
        return delete(url, null, param);
    }

    public Request delete(@NonNull final String url, @NonNull Headers headers, @NonNull String param) {
        Request request = null;
        if (headers != null)
            request = new Request.Builder().url(url).headers(headers).delete(RequestBody.create(MediaType.parse("application/json"), param)).build();
        else
            request = new Request.Builder().url(url).delete(RequestBody.create(MediaType.parse("application/json"), param)).build();
        return request;
    }

    public Request put(@NonNull final String url, @NonNull String param) {
        return put(url, null, param);
    }

    public Request put(@NonNull final String url, @NonNull Headers headers, @NonNull String param) {
        Request request = null;
        if (headers != null)
            request = new Request.Builder().url(url).headers(headers).put(RequestBody.create(MediaType.parse("application/json"), param)).build();
        else
            request = new Request.Builder().url(url).put(RequestBody.create(MediaType.parse("application/json"), param)).build();
        return request;
    }

    private void call(@NonNull Request request, @NonNull final Class<T> object, @Nullable final HanwhaNetworkCallback callback) {
        callback.onPreExecute();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                if (BuildConfig.DEBUG) {
                    Log.i(getClass().getSimpleName(), "onFailure");
                    Log.i(getClass().getSimpleName(), e.toString());
                    Log.i(getClass().getSimpleName(), "########################################");
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        callback.onFailure(new HanwhaNetworkError(e));
                        callback.onPostExecute();
                    }
                });
            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {
                if (BuildConfig.DEBUG) {
                    Log.i(getClass().getSimpleName(), "onResponse");
                    Log.i(getClass().getSimpleName(), response.toString());
                    Log.i(getClass().getSimpleName(), "########################################");
                }
                if (response.code() == HanwhaNetworkError.CODE_SUCCESS) {
                    try {
                        String result = response.body().string();

                        final Object data = new Gson().fromJson(result, object);
                        if (BuildConfig.DEBUG) {
                            Log.i(getClass().getSimpleName(), "HanwhaNetworkClient = response:" + result);
                            Log.i(getClass().getSimpleName(), "HanwhaNetworkClient = JSON: " + data.toString());
                        }
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onSuccess(data);
                                callback.onPostExecute();
                            }
                        });

                    } catch (final IOException e) {
                        e.printStackTrace();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onFailure(new HanwhaNetworkError(e));
                                callback.onPostExecute();
                            }
                        });
                    } catch (final IllegalArgumentException e2) {
                        e2.printStackTrace();
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                callback.onFailure(new HanwhaNetworkError(e2));
                                callback.onPostExecute();
                            }
                        });
                    }
                } else {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            callback.onFailure(new HanwhaNetworkError(response.code(), response.message(), response));
                            callback.onPostExecute();
                        }
                    });
                }
            }
        });
    }

    private void runOnUiThread(Runnable task) {
        new Handler(Looper.getMainLooper()).post(task);
    }

    public int getReadTimeout() {
        return readTimeout;
    }


    /**************************************************
     * HanwhaNetworkClient Builder
     **************************************************/
    public static final class Builder {
        int connectTimeout;
        int readTimeout;
        int writeTimeout;

        public Builder() {
            connectTimeout = 10_000;
            readTimeout = 10_000;
            writeTimeout = 10_000;
        }

        Builder(HanwhaNetworkClient client) {
            this.connectTimeout = client.connectTimeout;
            this.readTimeout = client.readTimeout;
            this.writeTimeout = client.writeTimeout;
        }

        public HanwhaNetworkClient.Builder connectTimeout(long timeout, TimeUnit unit) {
            connectTimeout = checkDuration("timeout", timeout, unit);
            return this;
        }

        public HanwhaNetworkClient.Builder readTimeout(long timeout, TimeUnit unit) {
            readTimeout = checkDuration("timeout", timeout, unit);
            return this;
        }

        public HanwhaNetworkClient.Builder writeTimeout(long timeout, TimeUnit unit) {
            writeTimeout = checkDuration("timeout", timeout, unit);
            return this;
        }

        private static int checkDuration(String name, long duration, TimeUnit unit) {
            if (duration < 0) throw new IllegalArgumentException(name + " < 0");
            if (unit == null) throw new NullPointerException("unit == null");
            long millis = unit.toMillis(duration);
            if (millis > Integer.MAX_VALUE)
                throw new IllegalArgumentException(name + " too large.");
            if (millis == 0 && duration > 0)
                throw new IllegalArgumentException(name + " too small.");
            return (int) millis;
        }

        public HanwhaNetworkClient build() {
            return new HanwhaNetworkClient(this);
        }
    }
}
