package com.jfinal.weixin.sdk.utils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import com.jfinal.kit.StrKit;
import com.jfinal.weixin.sdk.api.MediaFile;

/**
 * JFinal-weixin Http请求工具类
 * @author L.cm
 */
public final class HttpUtils {

    private HttpUtils() {}

    public static String get(String url) {
        return delegate.get(url);
    }

    public static String get(String url, Map<String, String> queryParas) {
        return delegate.get(url, queryParas);
    }

    public static String post(String url, String data) {
        return delegate.post(url, data);
    }

    public static String postSSL(String url, String data, String certPath, String certPass) {
        return delegate.postSSL(url, data, certPath, certPass);
    }

    public static MediaFile download(String url) {
        return delegate.download(url);
    }
    public static InputStream download(String url, String params){
        return delegate.download(url, params);
    }

    public static String upload(String url, File file, String params) {
        return delegate.upload(url, file, params);
    }

    /**
     * http请求工具 委托
     * 优先使用OkHttp
     * 最后使用JFinal HttpKit
     */
    public interface HttpDelegate {
        String get(String url);
        String get(String url, Map<String, String> queryParas);

        String post(String url, String data);
        String postSSL(String url, String data, String certPath, String certPass);

        MediaFile download(String url);
        InputStream download(String url, String params);

        String upload(String url, File file, String params);
    }

    // http请求工具代理对象
    private static HttpDelegate delegate;

    public static void setHttpDelegate(HttpDelegate httpDelegate) {
    		delegate = httpDelegate;
    }

    static {
        HttpDelegate delegateToUse = null;
        // okhttp3.OkHttpClient?
        if (ClassUtils.isPresent("okhttp3.OkHttpClient", HttpUtils.class.getClassLoader())) {
            delegateToUse = new OkHttp3Delegate();
        }
        // com.squareup.okhttp.OkHttpClient?
        else if (ClassUtils.isPresent("com.squareup.okhttp.OkHttpClient", HttpUtils.class.getClassLoader())) {
            delegateToUse = new OkHttpDelegate();
        }
        // com.jfinal.kit.HttpKit
        else if (ClassUtils.isPresent("com.jfinal.kit.HttpKit", HttpUtils.class.getClassLoader())) {
            delegateToUse = new HttpKitDelegate();
        }
        delegate = delegateToUse;
    }

    /**
     * OkHttp2代理
     */
    public static class OkHttpDelegate implements HttpDelegate {
        private final com.squareup.okhttp.OkHttpClient httpClient;
        private final com.squareup.okhttp.OkHttpClient httpsClient;
        private final Lock lock = new ReentrantLock();

        public OkHttpDelegate() {
            httpClient = new com.squareup.okhttp.OkHttpClient();
            // 分别设置Http的连接,写入,读取的超时时间
            httpClient.setConnectTimeout(10, TimeUnit.SECONDS);
            httpClient.setWriteTimeout(10, TimeUnit.SECONDS);
            httpClient.setReadTimeout(30, TimeUnit.SECONDS);

            httpsClient = httpClient.clone();
        }

        private static final com.squareup.okhttp.MediaType CONTENT_TYPE_FORM =
                com.squareup.okhttp.MediaType.parse("application/x-www-form-urlencoded");

        private String exec(com.squareup.okhttp.Request request) {
            try {
                com.squareup.okhttp.Response response = httpClient.newCall(request).execute();

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                return response.body().string();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String get(String url) {
            com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder().url(url).get().build();
            return exec(request);
        }

        @Override
        public String get(String url, Map<String, String> queryParas) {
            com.squareup.okhttp.HttpUrl.Builder urlBuilder = com.squareup.okhttp.HttpUrl.parse(url).newBuilder();
            for (Entry<String, String> entry : queryParas.entrySet()) {
                urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
            }
            com.squareup.okhttp.HttpUrl httpUrl = urlBuilder.build();
            com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder().url(httpUrl).get().build();
            return exec(request);
        }

        @Override
        public String post(String url, String params) {
            com.squareup.okhttp.RequestBody body = com.squareup.okhttp.RequestBody.create(CONTENT_TYPE_FORM, params);
            com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder()
                .url(url)
                .post(body)
                .build();
            return exec(request);
        }

        @Override
        public String postSSL(String url, String data, String certPath, String certPass) {
            com.squareup.okhttp.RequestBody body = com.squareup.okhttp.RequestBody.create(CONTENT_TYPE_FORM, data);
            com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder()
                .url(url)
                .post(body)
                .build();

            InputStream inputStream = null;
            try {
                // 移动到最开始，certPath io异常unlock会报错
                lock.lock();

                KeyStore clientStore = KeyStore.getInstance("PKCS12");
                inputStream = new FileInputStream(certPath);
                clientStore.load(inputStream, certPass.toCharArray());

                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                kmf.init(clientStore, certPass.toCharArray());
                KeyManager[] kms = kmf.getKeyManagers();
                SSLContext sslContext = SSLContext.getInstance("TLS");

                sslContext.init(kms, null, new SecureRandom());

                httpsClient.setSslSocketFactory(sslContext.getSocketFactory());

                com.squareup.okhttp.Response response = httpsClient.newCall(request).execute();

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                return response.body().string();
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                IOUtils.closeQuietly(inputStream);
                lock.unlock();
            }
        }

        @Override
        public MediaFile download(String url) {
            com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder().url(url).get().build();
            try {
                com.squareup.okhttp.Response response = httpClient.newCall(request).execute();

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                com.squareup.okhttp.ResponseBody body = response.body();
                com.squareup.okhttp.MediaType mediaType = body.contentType();
                MediaFile mediaFile = new MediaFile();
                if ("text".equals(mediaType.type())) {
                    mediaFile.setError(body.string());
                } else {
                    BufferedInputStream bis = new BufferedInputStream(body.byteStream());

                    String ds = response.header("Content-disposition");
                    String fullName = ds.substring(ds.indexOf("filename=\"") + 10, ds.length() - 1);
                    String relName = fullName.substring(0, fullName.lastIndexOf("."));
                    String suffix = fullName.substring(relName.length()+1);

                    mediaFile.setFullName(fullName);
                    mediaFile.setFileName(relName);
                    mediaFile.setSuffix(suffix);
                    mediaFile.setContentLength(body.contentLength() + "");
                    mediaFile.setContentType(body.contentType().toString());
                    mediaFile.setFileStream(bis);
                }
                return mediaFile;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public InputStream download(String url, String params) {
            com.squareup.okhttp.Request request;
            if (StrKit.notBlank(params)) {
                com.squareup.okhttp.RequestBody body = com.squareup.okhttp.RequestBody.create(CONTENT_TYPE_FORM, params);
                request = new com.squareup.okhttp.Request.Builder().url(url).post(body).build();
            } else {
                request = new com.squareup.okhttp.Request.Builder().url(url).get().build();
            }
            try {
                com.squareup.okhttp.Response response = httpClient.newCall(request).execute();

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                return response.body().byteStream();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

        @Override
        public String upload(String url, File file, String params) {
            com.squareup.okhttp.RequestBody fileBody = com.squareup.okhttp.RequestBody
                    .create(com.squareup.okhttp.MediaType.parse("application/octet-stream"), file);

            com.squareup.okhttp.MultipartBuilder builder = new com.squareup.okhttp.MultipartBuilder()
                    .type(com.squareup.okhttp.MultipartBuilder.FORM)
                    .addFormDataPart("media", file.getName(), fileBody);

            if (StrKit.notBlank(params)) {
                builder.addFormDataPart("description", params);
            }

            com.squareup.okhttp.RequestBody requestBody = builder.build();
            com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();

            return exec(request);
        }

    }

    /**
     * OkHttp3代理
     */
    public static class OkHttp3Delegate implements HttpDelegate {
        private final okhttp3.OkHttpClient httpClient;

        public OkHttp3Delegate() {
            // 分别设置Http的连接,写入,读取的超时时间
            httpClient = new okhttp3.OkHttpClient().newBuilder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .writeTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .build();
        }

        private static final okhttp3.MediaType CONTENT_TYPE_FORM =
                okhttp3.MediaType.parse("application/x-www-form-urlencoded");

        private String exec(okhttp3.Request request) {
            try (okhttp3.Response response = httpClient.newCall(request).execute()) {

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                return response.body().string();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String get(String url) {
            okhttp3.Request request = new okhttp3.Request.Builder().url(url).get().build();
            return exec(request);
        }

        @Override
        public String get(String url, Map<String, String> queryParas) {
            okhttp3.HttpUrl.Builder urlBuilder = okhttp3.HttpUrl.parse(url).newBuilder();
            for (Entry<String, String> entry : queryParas.entrySet()) {
                urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
            }
            okhttp3.HttpUrl httpUrl = urlBuilder.build();
            okhttp3.Request request = new okhttp3.Request.Builder().url(httpUrl).get().build();
            return exec(request);
        }

        @Override
        public String post(String url, String params) {
            okhttp3.RequestBody body = okhttp3.RequestBody.create(CONTENT_TYPE_FORM, params);
            okhttp3.Request request = new okhttp3.Request.Builder()
                .url(url)
                .post(body)
                .build();
            return exec(request);
        }

        @Override
        public String postSSL(String url, String data, String certPath, String certPass) {
            okhttp3.RequestBody body = okhttp3.RequestBody.create(CONTENT_TYPE_FORM, data);
            okhttp3.Request request = new okhttp3.Request.Builder()
                .url(url)
                .post(body)
                .build();

            InputStream inputStream = null;
            try {
                KeyStore clientStore = KeyStore.getInstance("PKCS12");
                inputStream = new FileInputStream(certPath);
                char[] passArray = certPass.toCharArray();
                clientStore.load(inputStream, passArray);

                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                kmf.init(clientStore, passArray);
                KeyManager[] kms = kmf.getKeyManagers();
                SSLContext sslContext = SSLContext.getInstance("TLS");

                X509TrustManager trustManager = getTrustManager();

                sslContext.init(kms, new TrustManager[]{trustManager}, new SecureRandom());

                okhttp3.OkHttpClient httpsClient = new okhttp3.OkHttpClient()
                        .newBuilder()
                        .connectTimeout(10, TimeUnit.SECONDS)
                        .writeTimeout(10, TimeUnit.SECONDS)
                        .readTimeout(30, TimeUnit.SECONDS)
                        .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
                        .build();

                try (okhttp3.Response response = httpsClient.newCall(request).execute()) {

                	if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                	return response.body().string();
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                IOUtils.closeQuietly(inputStream);
            }
        }

        @Override
        public MediaFile download(String url) {
            okhttp3.Request request = new okhttp3.Request.Builder().url(url).get().build();
            try {
            	okhttp3.Response response = httpClient.newCall(request).execute();

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                okhttp3.ResponseBody body = response.body();
                okhttp3.MediaType mediaType = body.contentType();
                MediaFile mediaFile = new MediaFile();
                if ("text".equals(mediaType.type())) {
                    mediaFile.setError(body.string());
                } else {
                    BufferedInputStream bis = new BufferedInputStream(body.byteStream());

                    String ds = response.header("Content-disposition");
                    String fullName = ds.substring(ds.indexOf("filename=\"") + 10, ds.length() - 1);
                    String relName = fullName.substring(0, fullName.lastIndexOf("."));
                    String suffix = fullName.substring(relName.length()+1);

                    mediaFile.setFullName(fullName);
                    mediaFile.setFileName(relName);
                    mediaFile.setSuffix(suffix);
                    mediaFile.setContentLength(body.contentLength() + "");
                    mediaFile.setContentType(body.contentType().toString());
                    mediaFile.setFileStream(bis);
                }
                return mediaFile;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public InputStream download(String url, String params) {
            okhttp3.Request request;
            if (StrKit.notBlank(params)) {
                okhttp3.RequestBody body = okhttp3.RequestBody.create(CONTENT_TYPE_FORM, params);
                request = new okhttp3.Request.Builder().url(url).post(body).build();
            } else {
                request = new okhttp3.Request.Builder().url(url).get().build();
            }

            try {
            	okhttp3.Response response = httpClient.newCall(request).execute();

                if (!response.isSuccessful()) throw new RuntimeException("Unexpected code " + response);

                return response.body().byteStream();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }

        @Override
        public String upload(String url, File file, String params) {
            okhttp3.RequestBody fileBody = okhttp3.RequestBody
                    .create(okhttp3.MediaType.parse("application/octet-stream"), file);

            okhttp3.MultipartBody.Builder builder = new okhttp3.MultipartBody.Builder()
                    .setType(okhttp3.MultipartBody.FORM)
                    .addFormDataPart("media", file.getName(), fileBody);

            if (StrKit.notBlank(params)) {
                builder.addFormDataPart("description", params);
            }

            okhttp3.RequestBody requestBody = builder.build();
            okhttp3.Request request = new okhttp3.Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();

            return exec(request);
        }

    }

    private static X509TrustManager getTrustManager() {
		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:" + java.util.Arrays.toString(trustManagers));
				System.err.println("Unexpected default trust managers:" + java.util.Arrays.toString(trustManagers));
				return trustAnyTrustManager;
			}
			return (X509TrustManager) trustManagers[0];
		} catch (Exception e) {
			System.err.println(e.getMessage());
			return trustAnyTrustManager;
		}
    }

    static final TrustAnyTrustManager trustAnyTrustManager = new TrustAnyTrustManager();

    private static class TrustAnyTrustManager implements X509TrustManager {
		public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}
		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
	}

    /**
     * HttpKit代理
     */
    public static class HttpKitDelegate implements HttpDelegate {

        @Override
        public String get(String url) {
            return com.jfinal.kit.HttpKit.get(url);
        }

        @Override
        public String get(String url, Map<String, String> queryParas) {
            return com.jfinal.kit.HttpKit.get(url, queryParas);
        }

        @Override
        public String post(String url, String data) {
            return com.jfinal.kit.HttpKit.post(url, data);
        }

        @Override
        public String postSSL(String url, String data, String certPath, String certPass) {
            return HttpKitExt.postSSL(url, data, certPath, certPass);
        }

        @Override
        public MediaFile download(String url) {
            try {
                return HttpKitExt.download(url);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public InputStream download(String url, String params) {
            try {
                return HttpKitExt.downloadMaterial(url, params);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String upload(String url, File file, String params) {
            try {
                return HttpKitExt.uploadMedia(url, file, params);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}
