package com.tencent.qian.webview;

import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import android.util.Log;
import android.webkit.WebView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;

import com.tencent.qian.webview.model.PrewarmConfig;
import com.tencent.qian.webview.model.PrewarmEnvironment;
import com.tencent.qian.webview.model.PrewarmLevel;
import com.tencent.qian.webview.prewarm.ManifestStore;
import com.tencent.qian.webview.prewarm.PrewarmDedup;
import com.tencent.qian.webview.prewarm.WarmupManifest;
import com.tencent.qian.webview.prewarm.WebViewPool;

import java.net.InetAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <b>WebView 预热统一入口</b>（三端对齐，与 Kotlin 版 1:1）。
 *
 * <p>Android：MINIMAL = 纯 DNS；LIGHT = 离屏预建空实例（触发内核加载 + 省创建）；
 * HEAVY = 同一池实例加载 warmup.html(L4) 热网络缓存。池为 borrow/recycle 借还池（关闭归还不销毁、
 * 客户主动 {@link #releaseAll} 才销毁）。L2 拦截接管为 Non-goal。
 *
 * <pre>{@code
 * TencentQianWebViewPrewarmer.configure(PrewarmConfig.DEFAULT);
 * TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.MINIMAL, null);
 * TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.HEAVY, "single-sign", context);
 * }</pre>
 */
public final class TencentQianWebViewPrewarmer {

    private static final String TAG = "QianPrewarmer";

    private static final ManifestStore STORE = new ManifestStore();
    private static final PrewarmDedup DEDUP = new PrewarmDedup();

    private static volatile PrewarmConfig config = PrewarmConfig.DEFAULT;
    private static volatile boolean configured = false;

    private static final ExecutorService DNS_EXECUTOR = Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r, "qian-prewarm-dns");
        t.setDaemon(true);
        return t;
    });

    /** 内存回调是否已注册（幂等守卫，避免多次开池重复 registerComponentCallbacks）。 */
    private static volatile boolean memoryCallbacksRegistered = false;

    /**
     * 系统内存压力监听：后台内存告警时回收<b>空闲</b>池实例（借出中不动，保签署不被打断），
     * 释放后重置去重状态，使后续 {@link #prewarm} 可重新预热。
     *
     * <p><b>阈值策略</b>：仅 {@link ComponentCallbacks2#TRIM_MEMORY_BACKGROUND} 及以上（即进入后台后的
     * 内存压力）才回收；前台档位（{@code RUNNING_*}）不动池，以保住预热命中率。
     */
    private static final ComponentCallbacks2 MEMORY_CALLBACKS = new ComponentCallbacks2() {
        @Override
        public void onTrimMemory(int level) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                WebViewPool.releaseIfIdle(DEDUP::reset);
            }
        }

        @Override
        public void onLowMemory() {
            WebViewPool.releaseIfIdle(DEDUP::reset);
        }

        @Override
        public void onConfigurationChanged(@NonNull Configuration newConfig) {
            // no-op：池实例不随配置变更处理
        }
    };

    private TencentQianWebViewPrewarmer() {}

    /** 全局配置。App 启动时调用；触发 manifest 后台异步拉取（不阻塞）。 */
    public static void configure(@NonNull PrewarmConfig cfg) {
        PrewarmConfig effective = cfg;
        if (cfg.getPoolSize() > 1) {
            Log.w(TAG, "poolSize > 1 not supported in v1, clamped to 1");
            effective = PrewarmConfig.builder()
                    .environment(cfg.getEnvironment())
                    .manifestUrl(cfg.getManifestUrl())
                    .poolSize(1)
                    .enablePool(cfg.isEnablePool())
                    .preferredLanguage(cfg.getPreferredLanguage())
                    .injectUserAgent(cfg.isInjectUserAgent())
                    .userAgentExtra(cfg.getUserAgentExtra())
                    .build();
        }
        config = effective;
        configured = true;
        WebViewPool.setEnabled(effective.isEnablePool());

        String url = (effective.getManifestUrl() != null && !effective.getManifestUrl().isEmpty())
                ? effective.getManifestUrl()
                : PrewarmEnvironment.manifestUrlOf(effective.getEnvironment());
        STORE.setManifestUrl(url);
        STORE.setOnVersionChanged(DEDUP::reset);
        STORE.ensureFresh(null);
    }

    /** 全局配置（默认配置）。 */
    public static void configure() {
        configure(PrewarmConfig.DEFAULT);
    }

    /**
     * 按意图档位触发预热，可多次调用（内部单调去重）。
     *
     * @param level   预热档位
     * @param bizType 业务类型；MINIMAL 可传 null，LIGHT/HEAVY 必填
     * @param context Android Context（LIGHT/HEAVY 建池需要）；MINIMAL 可传 null
     */
    public static void prewarm(@NonNull PrewarmLevel level, @Nullable String bizType, @Nullable Context context) {
        if (!configured) {
            lazyInit();
        }
        // bizType 允许为空：空时预热公共资源（warmup.html 加载公共部分），去重按空 key 记账
        final String bt = bizType != null ? bizType : "";
        STORE.ensureFresh(manifest -> {
            if (!DEDUP.shouldRun(level, bt)) {
                return;
            }
            execute(level, bt, manifest, context);
            DEDUP.markDone(level, bt);
        });
    }

    /** 按意图档位触发预热（无 context，适用于 MINIMAL）。 */
    public static void prewarm(@NonNull PrewarmLevel level, @Nullable String bizType) {
        prewarm(level, bizType, null);
    }

    /** 按意图档位触发预热（无 bizType 和 context，适用于 MINIMAL）。 */
    public static void prewarm(@NonNull PrewarmLevel level) {
        prewarm(level, null, null);
    }

    /** 释放池实例。 */
    public static void release() {
        WebViewPool.release();
    }

    /** 释放所有预热资源并重置去重状态。 */
    public static void releaseAll() {
        WebViewPool.release();
        DEDUP.reset();
    }

    /**
     * open(url) 环境错配检测（best-effort，仅告警）。仅命中 3 个已知 {@code quick.*} 环境之一
     * 且与配置不符才 log 警告；非 quick 域名不告警。
     */
    public static void warnIfEnvironmentMismatch(@Nullable String url) {
        if (!configured) {
            return;
        }
        PrewarmEnvironment env = PrewarmEnvironment.environmentOfUrl(url);
        if (env == null) {
            return;
        }
        if (env != config.getEnvironment()) {
            Log.w(TAG, "environment mismatch: configured=" + config.getEnvironment()
                    + " but open() host indicates " + env + " — prewarm cache may miss");
        }
    }

    /**
     * 一次性获取 manifest 中的 bizType 列表。
     *
     * <ul>
     *   <li>manifest 已缓存 → 立即回调</li>
     *   <li>manifest 拉取中 → 排队，拉取完成后回调一次</li>
     *   <li>拉取失败 → 不回调（调用方需自行兜底）</li>
     * </ul>
     *
     * 未调用 {@link #configure} 时会以默认配置（PROD）懒初始化，避免 callback 滞留。
     */
    public static void getMenuBizTypes(@NonNull Consumer<List<String>> callback) {
        if (!configured) {
            lazyInit();
        }
        STORE.ensureFresh(manifest -> {
            List<String> menu = manifest.menuBizTypes;
            callback.accept(menu != null ? menu : new ArrayList<>(manifest.bizTypes.keySet()));
        });
    }

    /**
     * 供容器 Activity 借出池实例（借还池，module 内部使用）。必须主线程调用。
     * 仅在 UA 配置与预热一致时返回；否则 {@code null}（容器自建）。
     */
    @Nullable
    static WebView obtainPooledWebView(@NonNull Context activity, boolean injectUA, @NonNull String uaExtra) {
        return WebViewPool.obtain(activity, injectUA, uaExtra);
    }

    /** 供容器 Activity 归还借出的池实例（借还池，不销毁）。必须主线程调用。 */
    static void recyclePooledWebView(@NonNull WebView webView) {
        WebViewPool.recycle(webView);
    }

    /**
     * 供容器 Activity 作废借出的池实例（渲染进程崩溃时）。必须主线程调用。
     * 仅当传入实例正是当前池实例时才销毁并置空，避免僵尸实例被后续 open 复用。
     */
    static void invalidatePooledWebView(@NonNull WebView webView) {
        WebViewPool.invalidateIfPooled(webView);
    }

    // ────────── 内部 ──────────

    private static void lazyInit() {
        configured = true;
        STORE.setManifestUrl(PrewarmEnvironment.manifestUrlOf(config.getEnvironment()));
        STORE.setOnVersionChanged(DEDUP::reset);
    }

    private static void execute(@NonNull PrewarmLevel level, @NonNull String bizType,
                                @NonNull WarmupManifest manifest, @Nullable Context context) {
        prefetchDns(manifest.hosts);
        if (level == PrewarmLevel.MINIMAL) {
            return;
        }
        WarmupManifest.BizType biz = manifest.bizTypes.get(bizType);
        Context ctx = context != null ? context.getApplicationContext() : null;
        boolean injectUA = config.isInjectUserAgent();
        String uaExtra = config.getUserAgentExtra();
        if (level == PrewarmLevel.LIGHT) {
            // 池离屏预建空实例（触发内核加载 + 省创建），按 UA 配置建
            if (ctx != null) {
                registerMemoryCallbacksOnce(ctx);
                WebViewPool.preCreate(ctx, injectUA, uaExtra);
            }
        } else if (level == PrewarmLevel.HEAVY) {
            // ensureInstance 内部会补建 LIGHT 未做的实例
            if (ctx != null && biz != null) {
                registerMemoryCallbacksOnce(ctx);
                WebViewPool.warm(ctx, appendLang(biz.warmupUrl, config.getPreferredLanguage()),
                        bizType, injectUA, uaExtra);
            }
        }
    }

    /**
     * 幂等注册内存压力回调。用 <b>application context</b> 注册（避免 Activity 泄漏）。
     *
     * <p>注册时机放在真正开池处（LIGHT/HEAVY），而非 {@link #configure}（其无 Context 参数）——
     * 既覆盖 configure/lazyInit 两条路径，又保证只有真正持有池实例时才监听内存。
     */
    private static void registerMemoryCallbacksOnce(@NonNull Context context) {
        if (memoryCallbacksRegistered) {
            return;
        }
        synchronized (TencentQianWebViewPrewarmer.class) {
            if (memoryCallbacksRegistered) {
                return;
            }
            try {
                context.getApplicationContext().registerComponentCallbacks(MEMORY_CALLBACKS);
                memoryCallbacksRegistered = true;
            } catch (Throwable t) {
                Log.w(TAG, "registerComponentCallbacks failed: " + t.getMessage());
            }
        }
    }

    /** 将语言偏好拼进 warmup.html URL（{@code ?lang=}，warmup.html 内优先级最高）；空则原样返回。 */
    @NonNull
    static String appendLang(@NonNull String url, @Nullable String lang) {
        if (lang == null || lang.isEmpty()) {
            return url;
        }
        String sep = url.contains("?") ? "&" : "?";
        String encoded;
        try {
            encoded = java.net.URLEncoder.encode(lang, "UTF-8");
        } catch (Throwable t) {
            encoded = lang;
        }
        return url + sep + "lang=" + encoded;
    }

    private static void prefetchDns(@NonNull List<String> hosts) {
        DNS_EXECUTOR.execute(() -> {
            for (String h : hosts) {
                String host = hostOf(h);
                if (!host.isEmpty()) {
                    try {
                        InetAddress.getAllByName(host);
                    } catch (Throwable ignored) {
                    }
                }
            }
        });
    }

    @NonNull
    private static String hostOf(@NonNull String urlOrHost) {
        try {
            if (urlOrHost.contains("://")) {
                String h = new URI(urlOrHost).getHost();
                return h != null ? h : "";
            }
            String s = urlOrHost;
            int slash = s.indexOf('/');
            if (slash >= 0) {
                s = s.substring(0, slash);
            }
            int colon = s.indexOf(':');
            if (colon >= 0) {
                s = s.substring(0, colon);
            }
            return s;
        } catch (Throwable t) {
            return "";
        }
    }
}
