package quant.rich.emoney.client;

import java.io.IOException;
import java.net.Proxy;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.function.Consumer;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import okio.GzipSource;
import okio.Okio;
import quant.rich.emoney.entity.sqlite.ProxySetting;
import quant.rich.emoney.service.sqlite.ProxySettingService;
import quant.rich.emoney.util.SpringContextHolder;

/**
 * OkHttpClient 提供器
 * <p>
 * 此处提供的 OkHttpClient 方便使用平台配置的代理，方便是否启用 HTTPS 证书认证等
 * @see quant.rich.emoney.entity.config.ProxyConfig
 * @see okhttp3.internal.http.BridgeInterceptor
 */
public class OkHttpClientProvider {

    private static volatile ProxySettingService proxySettingService;

    private static ProxySetting getDefaultProxySetting() {
        if (proxySettingService == null) {
            synchronized (OkHttpClientProvider.class) {
                proxySettingService = SpringContextHolder.getBean(ProxySettingService.class);
            }
        }
        if (proxySettingService == null) {
            return null;
        }
        return proxySettingService.getDefaultProxySetting();
    }
    
    /**
     * 根据 ProxyConfig 获取一个 OkHttpClient 实例
     * @return
     */
    public static OkHttpClient getInstance() {
        return getInstance(null);
    }
    
    /**
     * 根据 ProxyConfig 获取一个 OkHttpClient 实例
     * @param builderConsumer 可根据该 consumer 自定义 builder 其他参数，注意 proxy、https 校验等最终仍会根据 proxyConfig 情况覆盖
     * @return
     */
    public static OkHttpClient getInstance(Consumer<OkHttpClient.Builder> builderConsumer) {
        ProxySetting proxySetting = getDefaultProxySetting();
        return getInstance(
                proxySetting.getProxy(),
                proxySetting.getIgnoreHttpsVerification(),
                builderConsumer);
    }
    
    /**
     * 根据指定代理和是否忽略 https 证书获取一个 OkHttpClient 实例
     * @param proxy 指定代理
     * @param ignoreHttpsVerification 是否忽略 https 证书
     * @return
     */
    public static OkHttpClient getInstance(Proxy proxy, boolean ignoreHttpsVerification) {
        return getInstance(proxy, ignoreHttpsVerification, null);
    }

    /**
     * 根据指定代理、是否忽略 https 证书和额外 builder 设置获取一个 OkHttpClient 实例
     * @param proxy 指定代理
     * @param ignoreHttpsVerification 是否忽略 https 证书
     * @param builderConsumer 可根据该 consumer 自定义 builder 其他参数，注意 proxy、https 校验等最终仍会根据其他参数覆盖
     * @return
     */
    public static OkHttpClient getInstance(
            Proxy proxy,
            boolean ignoreHttpsVerification,
            Consumer<OkHttpClient.Builder> builderConsumer) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (builderConsumer != null) {
            builderConsumer.accept(builder);
        }
        builder.proxy(proxy);
        if (ignoreHttpsVerification) {
            builder
                .sslSocketFactory(getSSLSocketFactory(),  getX509TrustManager())
                .hostnameVerifier(getHostnameVerifier());
        }
        builder.addNetworkInterceptor(new GzipResponseInterceptor());
        return builder.build();
    }
    
    private static HostnameVerifier getHostnameVerifier() {
        HostnameVerifier hostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        };
        return hostnameVerifier;
    }
    
    private static SSLSocketFactory getSSLSocketFactory() {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, getTrustManager(), new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
 
    private static X509TrustManager getX509TrustManager() {
        X509TrustManager trustManager = null;
        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));
            }
            trustManager = (X509TrustManager) trustManagers[0];
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        return trustManager;
    }
 
    private static TrustManager[] getTrustManager() {
        TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) {}
 
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) {}
 
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[]{};
                }
            }
        };
        return trustAllCerts;
    }
    
    public static class GzipResponseInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
          Request request = chain.request();
          Response response = chain.proceed(request);

          // 只有服务器返回了 gzip 编码才处理
          if ("gzip".equalsIgnoreCase(response.header("Content-Encoding"))) {
            // 原始响应体
            ResponseBody body = response.body();
            if (body == null) return response;

            // 用 GzipSource 包装原始流，并缓冲
            GzipSource gzippedResponseBody = new GzipSource(body.source());
            BufferedSource unzippedSource = Okio.buffer(gzippedResponseBody);

            // 构造一个新的 ResponseBody，不再带 Content-Encoding/Length
            ResponseBody newBody = ResponseBody.create(
              unzippedSource,
              body.contentType(),
              -1L
            );

            // 去掉 Content-Encoding/Length，让后续调用 body().string() 时拿到解压后的内容
            return response.newBuilder()
              .removeHeader("Content-Encoding")
              .removeHeader("Content-Length")
              .body(newBody)
              .build();
          }

          return response;
        }
    }
}
