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 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 androidx.core.util.Consumer
import com.tencent.qian.webview.prewarm.WebViewPool
import java.net.InetAddress
import java.net.URI
import java.util.concurrent.Executors

/**
 * **WebView 预热统一入口**（三端对齐）。
 *
 * 两个正交维度：
 * - 网络预热（L1 DNS / L4 warmup.html）——省网络加载时间。
 * - 实例池——省 WebView 创建时间（[MutableContextWrapper][android.content.MutableContextWrapper] 技术）。
 *
 * **Android 平台说明**：MINIMAL = 纯 DNS；LIGHT = 离屏预建空实例（触发 Chromium 内核加载 +
 * 省创建）；HEAVY = 同一池实例加载 `warmup.html`(L4) 热网络缓存。池为 **borrow/recycle 借还池**
 * （关闭归还不销毁、客户主动 [releaseAll] 才销毁）。L2 拦截接管代理复杂度过高（Non-goal）。
 *
 * **典型用法**：
 * ```kotlin
 * // App 启动
 * TencentQianWebViewPrewarmer.configure()
 * TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.MINIMAL)
 * // 列表曝光 / 已知业务
 * TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.LIGHT, "single-sign", context)
 * // 强意图
 * TencentQianWebViewPrewarmer.prewarm(PrewarmLevel.HEAVY, "single-sign", context)
 * ```
 *
 * **跨端等价**：HarmonyOS / iOS `TencentQianWebViewPrewarmer`。
 */
public object TencentQianWebViewPrewarmer {

    private const val TAG = "QianPrewarmer"

    private val store = ManifestStore()
    private val dedup = PrewarmDedup()

    @Volatile private var config: PrewarmConfig = PrewarmConfig.DEFAULT
    @Volatile private var configured: Boolean = false

    private val dnsExecutor = Executors.newSingleThreadExecutor { r ->
        Thread(r, "qian-prewarm-dns").apply { isDaemon = true }
    }

    /** 内存回调是否已注册（幂等守卫，避免多次开池重复 registerComponentCallbacks）。 */
    @Volatile private var memoryCallbacksRegistered: Boolean = false

    /**
     * 系统内存压力监听：后台内存告警时回收**空闲**池实例（借出中不动，保签署不被打断），
     * 释放后重置去重状态，使后续 [prewarm] 可重新预热。
     *
     * **阈值策略**：仅 [ComponentCallbacks2.TRIM_MEMORY_BACKGROUND] 及以上（即进入后台后的内存压力）
     * 才回收；前台档位（`RUNNING_*`）不动池，以保住预热命中率。
     */
    private val memoryCallbacks = object : ComponentCallbacks2 {
        override fun onTrimMemory(level: Int) {
            if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
                WebViewPool.releaseIfIdle { dedup.reset() }
            }
        }

        override fun onLowMemory() {
            WebViewPool.releaseIfIdle { dedup.reset() }
        }

        override fun onConfigurationChanged(newConfig: Configuration) {
            // no-op：池实例不随配置变更处理
        }
    }

    /**
     * 全局配置。App 启动时调用；触发 manifest 后台异步拉取（不阻塞）。
     *
     * 可省——未调用时首次 [prewarm] / [getMenuBizTypes] 会以默认配置（PROD）懒初始化。
     */
    @JvmStatic
    @JvmOverloads
    public fun configure(config: PrewarmConfig = PrewarmConfig.DEFAULT) {
        val cfg = if (config.poolSize > 1) {
            Log.w(TAG, "poolSize > 1 not supported in v1, clamped to 1")
            config.copy(poolSize = 1)
        } else {
            config
        }
        this.config = cfg
        this.configured = true
        WebViewPool.setEnabled(cfg.enablePool)

        val url = cfg.manifestUrl?.takeIf { it.isNotEmpty() }
            ?: PrewarmEnvironment.manifestUrlOf(cfg.environment)
        store.setManifestUrl(url)
        store.onVersionChanged = { dedup.reset() }
        // 后台异步拉取 manifest
        store.ensureFresh(null)
    }

    /**
     * 按意图档位触发预热，可多次调用（内部单调去重）。
     *
     * @param level 预热档位
     * @param bizType 业务类型；[PrewarmLevel.MINIMAL] 可省，[PrewarmLevel.LIGHT]/[PrewarmLevel.HEAVY] 必填
     * @param context Android Context（LIGHT/HEAVY 建池需要）；MINIMAL 可省
     */
    @JvmStatic
    @JvmOverloads
    public fun prewarm(level: PrewarmLevel, bizType: String? = null, context: Context? = null) {
        if (!configured) {
            lazyInit()
        }
        // bizType 允许为空：空时预热公共资源（warmup.html 加载公共部分），去重按空 key 记账
        val bt = bizType ?: ""
        // 先用缓存（可能略旧）立即预热；缺失则排队 replay；过期则后台重拉（SWR）
        store.ensureFresh { manifest ->
            if (!dedup.shouldRun(level, bt)) return@ensureFresh
            execute(level, bt, manifest, context)
            dedup.markDone(level, bt)
        }
    }

    /** 释放池实例。 */
    @JvmStatic
    public fun release() {
        WebViewPool.release()
    }

    /** 释放所有预热资源并重置去重状态。 */
    @JvmStatic
    public fun releaseAll() {
        WebViewPool.release()
        dedup.reset()
    }

    /**
     * `open(url)` 环境错配检测（best-effort，仅告警）。
     *
     * 仅当 url host 命中 3 个已知 `quick.*` 环境之一、且与配置环境不一致时才 log 警告；
     * 非 `quick.*` 域名（客户自有/其他）不告警——`open()` 本就支持任意域名。
     */
    @JvmStatic
    public fun warnIfEnvironmentMismatch(url: String?) {
        if (!configured) return
        val env = PrewarmEnvironment.environmentOfUrl(url) ?: return
        if (env != config.environment) {
            Log.w(TAG, "environment mismatch: configured=${config.environment} " +
                "but open() host indicates $env — prewarm cache may miss")
        }
    }

    /**
     * 一次性获取 manifest 中的 bizType 列表。
     *
     * - manifest 已缓存 → 立即回调
     * - manifest 拉取中 → 排队，拉取完成后回调一次
     * - 拉取失败 → 不回调（调用方需自行兜底）
     *
     * 未调用 [configure] 时会以默认配置（PROD）懒初始化，避免 callback 滞留。
     */
    @JvmStatic
    public fun getMenuBizTypes(callback: Consumer<List<String>>) {
        if (!configured) {
            lazyInit()
        }
        store.ensureFresh { manifest ->
            callback.accept(manifest.menuBizTypes ?: manifest.bizTypes.keys.toList())
        }
    }

    /**
     * 供容器 Activity 借出池实例（借还池，module 内部使用）。必须主线程调用。
     * 仅在 UA 配置与预热一致时返回；否则 `null`（容器自建）。
     */
    @JvmStatic
    internal fun obtainPooledWebView(activity: Context, injectUA: Boolean, uaExtra: String): WebView? =
        WebViewPool.obtain(activity, injectUA, uaExtra)

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

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

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

    /**
     * 幂等注册内存压力回调。用 **application context** 注册（避免 Activity 泄漏）。
     *
     * 注册时机放在真正开池处（LIGHT/HEAVY），而非 [configure]（其无 Context 参数）——
     * 既覆盖 configure/lazyInit 两条路径，又保证只有真正持有池实例时才监听内存。
     */
    private fun registerMemoryCallbacksOnce(context: Context) {
        if (memoryCallbacksRegistered) return
        synchronized(this) {
            if (memoryCallbacksRegistered) return
            try {
                context.applicationContext.registerComponentCallbacks(memoryCallbacks)
                memoryCallbacksRegistered = true
            } catch (t: Throwable) {
                Log.w(TAG, "registerComponentCallbacks failed: ${t.message}")
            }
        }
    }

    private fun lazyInit() {
        configured = true
        val url = PrewarmEnvironment.manifestUrlOf(config.environment)
        store.setManifestUrl(url)
        store.onVersionChanged = { dedup.reset() }
    }

    private fun execute(level: PrewarmLevel, bizType: String, manifest: WarmupManifest, context: Context?) {
        // L1：DNS 预解析全局 hosts（所有档位地基）
        prefetchDns(manifest.hosts)

        if (level == PrewarmLevel.MINIMAL) {
            return
        }

        val biz = manifest.bizTypes[bizType]
        val ctx = context?.applicationContext
        val injectUA = config.injectUserAgent
        val uaExtra = config.userAgentExtra
        when (level) {
            PrewarmLevel.LIGHT -> {
                // 池离屏预建空实例（触发内核加载 + 省创建时间），按 UA 配置建；Android 无轻量 L3
                if (ctx != null) {
                    registerMemoryCallbacksOnce(ctx)
                    WebViewPool.preCreate(ctx, injectUA, uaExtra)
                }
            }
            PrewarmLevel.HEAVY -> {
                // 池实例加载 warmup.html（L4 补连接 + 资源）；ensureInstance 内部会补建 LIGHT 未做的实例
                if (ctx != null && biz != null) {
                    registerMemoryCallbacksOnce(ctx)
                    WebViewPool.warm(
                        ctx,
                        appendLang(biz.warmupUrl, config.preferredLanguage),
                        bizType,
                        injectUA,
                        uaExtra
                    )
                }
            }
            else -> { /* unreachable */ }
        }
    }

    /** 将语言偏好拼进 warmup.html URL（`?lang=`，warmup.html 内优先级最高）；空则原样返回。 */
    internal fun appendLang(url: String, lang: String?): String {
        if (lang.isNullOrEmpty()) return url
        val sep = if (url.contains("?")) "&" else "?"
        val encoded = try {
            java.net.URLEncoder.encode(lang, "UTF-8")
        } catch (_: Throwable) {
            lang
        }
        return "$url${sep}lang=$encoded"
    }

    private fun prefetchDns(hosts: List<String>) {
        dnsExecutor.execute {
            hosts.forEach { h ->
                val host = hostOf(h)
                if (host.isNotEmpty()) {
                    try {
                        InetAddress.getAllByName(host)
                    } catch (_: Throwable) {
                        // ignore
                    }
                }
            }
        }
    }

    private fun hostOf(urlOrHost: String): String {
        return try {
            if (urlOrHost.contains("://")) {
                URI(urlOrHost).host ?: ""
            } else {
                urlOrHost.substringBefore("/").substringBefore(":")
            }
        } catch (_: Throwable) {
            ""
        }
    }
}
