package com.tencent.qian.webview.prewarm

import android.annotation.SuppressLint
import android.content.Context
import android.content.MutableContextWrapper
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.webkit.RenderProcessGoneDetail
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.annotation.RequiresApi
import com.tencent.qian.webview.internal.WebSettingsApplier

/**
 * WebView 复用池（容量 = 1，**借还池**，与鸿蒙规格一致）。
 *
 * 关键技术：池实例用 [MutableContextWrapper] 承载 —— 预热阶段以 ApplicationContext 创建
 * （避免 Activity 泄漏），[obtain] 时把 baseContext 换成真实 Activity（恢复文件选择 / JS 弹窗 /
 * 权限申请等能力），[recycle] 归还时再换回 ApplicationContext（避免归还后仍持有 Activity 泄漏）。
 *
 * **生命周期（借还池）**：
 * - LIGHT [preCreate]：离屏预建空实例（触发 Chromium 内核加载），按 UA 配置建。
 * - HEAVY [warm]：同一实例 `loadUrl(warmupUrl)` 热网络缓存；多次 prewarm 复用同实例，绝不重复建。
 * - `open()` → [obtain]：借出（UA 配置匹配才复用），容器 addView 挂载后 loadUrl 真实页。
 * - 容器关闭 → [recycle]：**不销毁**，`about:blank` + `clearHistory` + rebind 回 appContext 后放回池，待复用。
 * - 客户主动 [release]（或内存告警）：才真正 `destroy` 销毁。
 *
 * **UA 门控**：预热按 [PrewarmConfig][com.tencent.qian.webview.model.PrewarmConfig] 的
 * `injectUserAgent`/`userAgentExtra` 建实例并热身 `warmupUrl`；[obtain] 仅在与 `open()` config UA
 * 一致时才返回实例，否则 `null`（容器自建），避免 warmupUrl 缓存 key 不一致导致 miss。
 *
 * 所有 WebView 操作均在主线程执行（WebView 线程约束）。
 */
internal object WebViewPool {

    private const val TAG = "QianWebViewPool"

    private var pooled: WebView? = null
    private var appContextRef: Context? = null
    private var warmedForBizType: String? = null
    private var warmedUrl: String = ""
    /** 是否已被容器借出（借出中不可再 obtain）。 */
    private var inUse: Boolean = false
    /** 预热时的 UA 签名，供复用门控比对。 */
    private var uaSig: String = ""

    @Volatile private var enabled: Boolean = true

    private val main = Handler(Looper.getMainLooper())

    fun setEnabled(value: Boolean) {
        enabled = value
        if (!value) release()
    }

    /** LIGHT：离屏预建空实例（省创建时间 + 触发内核加载），按 UA 配置建。 */
    fun preCreate(appContext: Context, injectUA: Boolean, uaExtra: String) {
        if (!enabled) return
        runOnMain {
            ensureInstance(appContext, injectUA, uaExtra)
        }
    }

    /** HEAVY：确保实例存在（按 UA 配置建）并加载 warmup.html（L4 网络预热）。 */
    @SuppressLint("SetJavaScriptEnabled")
    fun warm(appContext: Context, warmupUrl: String, bizType: String, injectUA: Boolean, uaExtra: String) {
        if (!enabled || warmupUrl.isEmpty()) return
        runOnMain {
            val wv = ensureInstance(appContext, injectUA, uaExtra) ?: return@runOnMain
            // 借出中不切换 URL（避免打断正在使用的页面）
            if (inUse) return@runOnMain
            if (warmupUrl != warmedUrl) {
                try {
                    wv.loadUrl(warmupUrl)
                } catch (_: Throwable) {
                    // ignore
                }
            }
            warmedForBizType = bizType
            warmedUrl = warmupUrl
        }
    }

    /**
     * 借出池中预热态实例供 Activity 使用（借还池）。
     * 会把 baseContext 换成传入的 Activity；未就绪 / 借出中 / UA 配置不匹配时返回 `null`（容器自建）。
     *
     * **必须在主线程调用**（Activity.onCreate 天然主线程）。
     */
    fun obtain(activity: Context, injectUA: Boolean, uaExtra: String): WebView? {
        if (!enabled || inUse) return null
        val wv = pooled ?: return null
        if (uaSig != uaSignatureOf(injectUA, uaExtra)) return null
        return try {
            val base = wv.context
            if (base is MutableContextWrapper) {
                base.baseContext = activity
                inUse = true
                wv
            } else {
                null
            }
        } catch (_: Throwable) {
            null
        }
    }

    /**
     * 归还（借还池，**不销毁**）：从父容器移除 + `about:blank` + `clearHistory`，
     * 并 rebind baseContext 回 appContext（避免归还后仍持有 Activity 泄漏），保留实例待下次复用。
     *
     * **必须在主线程调用**。
     */
    fun recycle(wv: WebView) {
        runOnMain {
            try {
                (wv.parent as? android.view.ViewGroup)?.removeView(wv)
                wv.stopLoading()
                wv.loadUrl("about:blank")
                wv.clearHistory()
                val base = wv.context
                val app = appContextRef
                if (base is MutableContextWrapper && app != null) {
                    base.baseContext = app
                }
            } catch (_: Throwable) {
                // ignore
            }
            warmedUrl = "about:blank"
            warmedForBizType = null
            inUse = false
        }
    }

    /** 客户主动释放池实例（销毁）。签署流程结束、不再需要时调用。 */
    fun release() {
        runOnMain {
            destroyPooledLocked()
        }
    }

    /**
     * 内存压力回收：**仅在实例未被借出（`!inUse`）且存在时**才销毁，避免打断正在使用的签署容器。
     *
     * 借出中（`inUse == true`）则跳过——等容器关闭 [recycle] 归还回空闲态后，下次内存告警再回收。
     *
     * @param onReleased 真正销毁池实例后在主线程回调（供上层重置去重状态等，使后续 prewarm 可重新预热）；
     *                   未销毁（借出中 / 无实例）时不回调。
     */
    fun releaseIfIdle(onReleased: (() -> Unit)? = null) {
        runOnMain {
            if (!inUse && pooled != null) {
                destroyPooledLocked()
                onReleased?.invoke()
            }
        }
    }

    /**
     * 作废池实例（渲染进程崩溃等场景）：仅当传入实例正是当前池实例时才销毁并置空，
     * 避免"渲染进程已死的僵尸实例"被后续 [obtain] 反复借出导致持久白屏。
     *
     * 借出态 / 归还态 / 预热态均适用（三态的实例都是同一个 [pooled] 引用）。
     * **必须在主线程调用**（内部 [runOnMain] 兜底）。
     */
    fun invalidateIfPooled(view: WebView?) {
        runOnMain {
            if (view != null && view === pooled) {
                destroyPooledLocked()
            }
        }
    }

    /** 销毁池实例并清空全部池状态（主线程内调用）。 */
    private fun destroyPooledLocked() {
        try {
            pooled?.let {
                (it.parent as? android.view.ViewGroup)?.removeView(it)
                it.stopLoading()
                it.loadUrl("about:blank")
                it.destroy()
            }
        } catch (_: Throwable) {
            // ignore
        }
        pooled = null
        warmedForBizType = null
        warmedUrl = ""
        uaSig = ""
        inUse = false
    }

    @SuppressLint("SetJavaScriptEnabled")
    private fun ensureInstance(appContext: Context, injectUA: Boolean, uaExtra: String): WebView? {
        if (pooled == null) {
            try {
                val app = appContext.applicationContext
                appContextRef = app
                val wv = WebView(MutableContextWrapper(app))
                wv.settings.javaScriptEnabled = true
                wv.settings.domStorageEnabled = true
                // 预热态守卫：渲染进程崩溃时作废池实例（避免僵尸实例被后续 obtain 复用），
                // 并返回 true 阻止系统杀进程。借出态会被业务 client 覆盖，归还态被 fallback 覆盖，
                // 三态均处理 onRenderProcessGone。
                wv.webViewClient = PoolGuardClient
                // 按 UA 配置注入，保证 warmupUrl 请求 UA 与 open() 一致（缓存命中 + 复用门控）
                if (injectUA) {
                    try {
                        wv.settings.userAgentString =
                            WebSettingsApplier.buildUserAgent(wv.settings.userAgentString.orEmpty(), uaExtra)
                    } catch (_: Throwable) {
                        // best-effort
                    }
                }
                pooled = wv
                uaSig = uaSignatureOf(injectUA, uaExtra)
            } catch (t: Throwable) {
                Log.w(TAG, "pool pre-create failed: ${t.message}")
                pooled = null
            }
        }
        return pooled
    }

    /** UA 签名：复用门控——预热 UA 配置需与 `open()` config UA 一致，否则不复用。 */
    private fun uaSignatureOf(injectUA: Boolean, uaExtra: String): String =
        (if (injectUA) "1" else "0") + "|" + uaExtra

    /**
     * 池实例预热态 / 空闲态守卫 client。
     *
     * 渲染进程崩溃时：作废池实例（[invalidateIfPooled]）+ 返回 `true` 阻止系统杀死进程。
     * 若不处理（走系统默认 `return false`），共享渲染进程崩溃会把整个进程的 WebView 子系统
     * 拖入不可用状态，表现为"退出重进、release 都救不回，唯有杀进程"的持久白屏。
     */
    private object PoolGuardClient : WebViewClient() {
        @RequiresApi(Build.VERSION_CODES.O)
        override fun onRenderProcessGone(view: WebView?, detail: RenderProcessGoneDetail?): Boolean {
            WebViewPool.invalidateIfPooled(view)
            return true
        }
    }

    private fun runOnMain(block: () -> Unit) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            block()
        } else {
            main.post(block)
        }
    }
}
