/**
 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com).
 * <p>
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.gnu.org/licenses/lgpl-3.0.txt
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.easyagents.flow.core.util;

import okhttp3.OkHttpClient;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Utility class for creating and configuring OkHttpClient instances.
 * <p>
 * By default, it uses secure TLS settings. Insecure HTTPS (trust-all) can be enabled
 * via system property {@code tinyflow.okhttp.insecure=true}, but it is strongly
 * discouraged in production environments.
 * </p>
 */
public final class OkHttpClientUtil {

    private static final Logger LOGGER = Logger.getLogger(OkHttpClientUtil.class.getName());

    private static volatile OkHttpClient.Builder customBuilder;
    private static volatile OkHttpClient sharedClient;
    private static volatile OkHttpClient sharedNoRetryClient;
    private static final Object LOCK = new Object();

    // Prevent instantiation
    private OkHttpClientUtil() {
        throw new UnsupportedOperationException("Utility class");
    }

    /**
     * Sets a custom OkHttpClient.Builder to be used by {@link #buildDefaultClient()}.
     * This should be called during application initialization.
     */
    public static void setCustomBuilder(OkHttpClient.Builder builder) {
        if (builder == null) {
            throw new IllegalArgumentException("Builder must not be null");
        }
        synchronized (LOCK) {
            customBuilder = builder;
            sharedClient = null;
            sharedNoRetryClient = null;
        }
    }

    /**
     * Returns a shared default OkHttpClient instance with reasonable timeouts and optional proxy.
     * If a custom builder was set via {@link #setCustomBuilder}, it will be used.
     * <p>
     * SSL is secure by default. Insecure mode (trust-all) can be enabled via system property:
     * {@code -Dtinyflow.okhttp.insecure=true}
     * </p>
     */
    public static OkHttpClient buildDefaultClient() {
        OkHttpClient client = sharedClient;
        if (client != null) {
            return client;
        }

        synchronized (LOCK) {
            client = sharedClient;
            if (client != null) {
                return client;
            }

            OkHttpClient.Builder builder = customBuilder;
            if (builder == null) {
                builder = new OkHttpClient.Builder()
                        .connectTimeout(1, TimeUnit.MINUTES)
                        .readTimeout(5, TimeUnit.MINUTES);

                // Optional insecure mode (for development/testing only)
                if (isInsecureModeEnabled()) {
                    LOGGER.warning("OkHttpClient is running in INSECURE mode (trust-all SSL). " +
                            "This is dangerous and should not be used in production.");
                    enableInsecureSsl(builder);
                }

                configureProxy(builder);
            }
            configureIoBulkhead(builder);
            sharedClient = builder.build();
            return sharedClient;
        }
    }

    /**
     * 返回关闭 OkHttp 隐式连接重试的共享客户端。
     *
     * <p>该客户端与默认客户端复用连接池和调度器，用于可能产生业务副作用的请求。</p>
     *
     * @return 禁用连接失败自动重试的共享客户端
     */
    public static OkHttpClient buildNoRetryClient() {
        OkHttpClient client = sharedNoRetryClient;
        if (client != null) {
            return client;
        }
        synchronized (LOCK) {
            client = sharedNoRetryClient;
            if (client == null) {
                client = buildDefaultClient()
                        .newBuilder()
                        .retryOnConnectionFailure(false)
                        .build();
                sharedNoRetryClient = client;
            }
            return client;
        }
    }

    /**
     * 为共享客户端安装一次工作流 I/O 隔离拦截器。
     *
     * @param builder HTTP 客户端构建器
     */
    private static void configureIoBulkhead(OkHttpClient.Builder builder) {
        boolean configured = builder.interceptors().stream()
                .anyMatch(interceptor -> interceptor instanceof IoBulkheadInterceptor);
        if (!configured) {
            builder.addInterceptor(new IoBulkheadInterceptor());
        }
    }

    private static boolean isInsecureModeEnabled() {
        return Boolean.parseBoolean(System.getProperty("tinyflow.okhttp.insecure", "false"));
    }


    private static void enableInsecureSsl(OkHttpClient.Builder builder) {
        try {
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            builder.sslSocketFactory(sslSocketFactory, trustManager)
                    .hostnameVerifier((hostname, session) -> true);
        } catch (Exception e) {
            throw new IllegalStateException("Failed to configure insecure SSL for OkHttpClient", e);
        }
    }

    private static void configureProxy(OkHttpClient.Builder builder) {
        String proxyHost = getProxyHost();
        String proxyPort = getProxyPort();

        if (StringUtil.hasText(proxyHost) && StringUtil.hasText(proxyPort)) {
            try {
                int port = Integer.parseInt(proxyPort.trim());
                InetSocketAddress addr = new InetSocketAddress(proxyHost.trim(), port);
                builder.proxy(new Proxy(Proxy.Type.HTTP, addr));
                LOGGER.fine("Configured HTTP proxy: " + proxyHost + ":" + port);
            } catch (NumberFormatException e) {
                LOGGER.log(Level.WARNING, "Invalid proxy port: " + proxyPort, e);
            }
        }
    }

    private static String getProxyHost() {
        String host = System.getProperty("https.proxyHost");
        if (!StringUtil.hasText(host)) {
            host = System.getProperty("http.proxyHost");
        }
        return host;
    }

    private static String getProxyPort() {
        String port = System.getProperty("https.proxyPort");
        if (!StringUtil.hasText(port)) {
            port = System.getProperty("http.proxyPort");
        }
        return port;
    }

    /**
     * 在 HTTP 响应体关闭前持有 I/O 许可的拦截器。
     */
    private static final class IoBulkheadInterceptor implements Interceptor {

        /**
         * 对单个 HTTP 目标施加并发隔离。
         *
         * @param chain OkHttp 拦截链
         * @return HTTP 响应
         * @throws java.io.IOException 网络请求失败时抛出
         */
        @Override
        public Response intercept(Chain chain) throws java.io.IOException {
            IoBulkhead.Permit permit = IoBulkhead.shared()
                    .acquire(IoBulkhead.targetForUrl(chain.request().url().toString()));
            boolean transferred = false;
            try {
                Response response = chain.proceed(chain.request());
                ResponseBody body = response.body();
                if (body == null) {
                    return response;
                }
                Response wrapped = response.newBuilder()
                        .body(new PermitReleasingResponseBody(body, permit))
                        .build();
                transferred = true;
                return wrapped;
            } finally {
                if (!transferred) {
                    permit.close();
                }
            }
        }
    }

    /**
     * 在响应体关闭时释放 I/O 许可的响应体包装器。
     */
    private static final class PermitReleasingResponseBody extends ResponseBody {

        private final ResponseBody delegate;
        private final BufferedSource source;

        /**
         * 创建响应体包装器。
         *
         * @param delegate 原始响应体
         * @param permit 待释放的 I/O 许可
         */
        private PermitReleasingResponseBody(ResponseBody delegate, IoBulkhead.Permit permit) {
            this.delegate = delegate;
            this.source = Okio.buffer(new ForwardingSource(delegate.source()) {
                @Override
                public void close() throws java.io.IOException {
                    try {
                        super.close();
                    } finally {
                        permit.close();
                    }
                }
            });
        }

        /**
         * 获取响应媒体类型。
         *
         * @return 响应媒体类型
         */
        @Override
        public MediaType contentType() {
            return delegate.contentType();
        }

        /**
         * 获取响应声明长度。
         *
         * @return 响应声明长度
         */
        @Override
        public long contentLength() {
            return delegate.contentLength();
        }

        /**
         * 获取带许可释放逻辑的响应源。
         *
         * @return 响应源
         */
        @Override
        public BufferedSource source() {
            return source;
        }
    }
}
