package com.sobot.network.http;

import static okhttp3.internal.Util.assertionError;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;

import com.sobot.network.http.builder.GetBuilder;
import com.sobot.network.http.builder.HeadBuilder;
import com.sobot.network.http.builder.OtherRequestBuilder;
import com.sobot.network.http.builder.PostFileBuilder;
import com.sobot.network.http.builder.PostFormBuilder;
import com.sobot.network.http.builder.PostMultipartFormBuilder;
import com.sobot.network.http.builder.PostStringBuilder;
import com.sobot.network.http.callback.Callback;
import com.sobot.network.http.callback.FileCallBack;
import com.sobot.network.http.log.LoggerInterceptor;
import com.sobot.network.http.request.RequestCall;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Response;

public class SobotOkHttpUtils {

    public static final long DEFAULT_MILLISECONDS = 10000;
    private static SobotOkHttpUtils mInstance;
    private OkHttpClient mOkHttpClient;
    private Handler mDelivery;

    public SobotOkHttpUtils(OkHttpClient okHttpClient) {
        if (okHttpClient == null) {
            OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
            okHttpClientBuilder.retryOnConnectionFailure(false);//网络问题导致请求失败时，禁止自动重试
            okHttpClientBuilder.addInterceptor(new SobotInternetPermissionExceptionInterceptor());
            mDelivery = new Handler(Looper.getMainLooper());
            try {
                X509TrustManager manager =platformTrustManager();
                okHttpClientBuilder.sslSocketFactory(createSSLSocketFactory(manager), manager);
                mOkHttpClient = okHttpClientBuilder.build();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            mOkHttpClient = okHttpClient;
        }
    }


    public SobotOkHttpUtils debug(String tag) {
        mOkHttpClient = getOkHttpClient().newBuilder().addInterceptor(new LoggerInterceptor(tag, false)).build();
        return this;
    }

    /**
     * showResponse may cause error, but you can try .
     *
     * @param tag
     * @param showResponse
     * @return
     */
    public SobotOkHttpUtils debug(String tag, boolean showResponse) {
        mOkHttpClient = getOkHttpClient().newBuilder().addInterceptor(new LoggerInterceptor(tag, showResponse)).build();
        return this;
    }

    public static SobotOkHttpUtils getInstance(OkHttpClient okHttpClient) {
        if (mInstance == null) {
            synchronized (SobotOkHttpUtils.class) {
                if (mInstance == null) {
                    mInstance = new SobotOkHttpUtils(okHttpClient);
                }
            }
        }
        return mInstance;
    }

    public static SobotOkHttpUtils getInstance() {
        if (mInstance == null) {
            synchronized (SobotOkHttpUtils.class) {
                if (mInstance == null) {
                    mInstance = new SobotOkHttpUtils(null);
                }
            }
        }
        return mInstance;
    }

    public void setOkHttpClient(OkHttpClient client) {
        mOkHttpClient = client;
    }

    public Handler getDelivery() {
        return mDelivery;
    }

    public OkHttpClient getOkHttpClient() {
        return mOkHttpClient;
    }

    public static GetBuilder get() {
        return new GetBuilder();
    }

    public static PostStringBuilder postString() {
        return new PostStringBuilder();
    }

    public static PostFileBuilder postFile() {
        return new PostFileBuilder();
    }

    public static PostFormBuilder post() {
        return new PostFormBuilder();
    }

    public static PostMultipartFormBuilder postMultipart() {
        return new PostMultipartFormBuilder();
    }

    public static OtherRequestBuilder put() {
        return new OtherRequestBuilder(METHOD.PUT);
    }

    public static HeadBuilder head() {
        return new HeadBuilder();
    }

    public static OtherRequestBuilder delete() {
        return new OtherRequestBuilder(METHOD.DELETE);
    }

    public static OtherRequestBuilder patch() {
        return new OtherRequestBuilder(METHOD.PATCH);
    }

    @SuppressWarnings("rawtypes")
    public void execute(final RequestCall requestCall, Callback callback) {
        if (callback == null)
            callback = Callback.CALLBACK_DEFAULT;
        final Callback finalCallback = callback;

        requestCall.getCall().enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                sendFailResultCallback(call, e, finalCallback);
            }

            @Override
            public void onResponse(final Call call, final Response response) {
                if (!response.isSuccessful()) {
                    try {
                        sendFailResultCallback(call, new RuntimeException(response.body().string()), finalCallback);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return;
                }

                try {
                    Object o = finalCallback.parseNetworkResponse(response);
                    sendSuccessResultCallback(o, finalCallback);
                } catch (Exception e) {
                    sendFailResultCallback(call, e, finalCallback);
                }

            }
        });
    }


    @SuppressWarnings("rawtypes")
    public void sendFailResultCallback(final Call call, final Exception e, final Callback callback) {
        if (callback == null || call.isCanceled()) return;

        mDelivery.post(new Runnable() {
            @Override
            public void run() {
                callback.onError(call, e);
                callback.onAfter();
            }
        });
    }

    @SuppressWarnings("rawtypes")
    public void sendSuccessResultCallback(final Object object, final Callback callback) {
        if (callback == null) return;
        mDelivery.post(new Runnable() {
            @SuppressWarnings("unchecked")
            @Override
            public void run() {
                callback.onResponse(object);
                callback.onAfter();
            }
        });
    }

    public static void runOnUiThread(Runnable runnable) {
        SobotOkHttpUtils.getInstance().mDelivery.post(runnable);
    }

    public void cancelTag(Object tag) {
        for (Call call : mOkHttpClient.dispatcher().queuedCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls()) {
            if (tag.equals(call.request().tag())) {
                call.cancel();
            }
        }
    }

    /*public void cancelAll()
    {
        for (Call call : mOkHttpClient.dispatcher().queuedCalls())
        {
            call.cancel();
        }
        for (Call call : mOkHttpClient.dispatcher().runningCalls())
        {
            call.cancel();
        }
    }*/

    public void setHostNameVerifier(HostnameVerifier hostNameVerifier) {
        mOkHttpClient = getOkHttpClient().newBuilder()
                .hostnameVerifier(hostNameVerifier)
                .build();
    }

    public void setConnectTimeout(int timeout, TimeUnit units) {
        mOkHttpClient = getOkHttpClient().newBuilder()
                .connectTimeout(timeout, units)
                .build();
    }

    public void setReadTimeout(int timeout, TimeUnit units) {
        mOkHttpClient = getOkHttpClient().newBuilder()
                .readTimeout(timeout, units)
                .build();
    }

    public void setWriteTimeout(int timeout, TimeUnit units) {
        mOkHttpClient = getOkHttpClient().newBuilder()
                .writeTimeout(timeout, units)
                .build();
    }

    public static void download(String url, FileCallBack fileCallBack) {
        SobotOkHttpUtils//
                .get()//
                .url(url)//
                .build()//
                .execute(fileCallBack);
    }

    public static class METHOD {
        public static final String HEAD = "HEAD";
        public static final String DELETE = "DELETE";
        public static final String PUT = "PUT";
        public static final String PATCH = "PATCH";
    }

    /**
     * 默认信任所有的证书
     *
     * @return
     */
    @SuppressLint("TrulyRandom")
    private static SSLSocketFactory createSSLSocketFactory(X509TrustManager trustManager) {

        SSLSocketFactory sSLSocketFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{trustManager},
                    new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return sSLSocketFactory;
    }

    public static X509TrustManager platformTrustManager() {
        try {
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            return (X509TrustManager) trustManagers[0];
        } catch (GeneralSecurityException e) {
            throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
        }
    }

}