package com.tencent.qian.webview

import android.app.Activity
import android.webkit.WebView
import com.tencent.qian.webview.chrome.QianWebChromeClient
import com.tencent.qian.webview.client.QianWebViewClient
import com.tencent.qian.webview.filechooser.FileChooserLauncher
import com.tencent.qian.webview.internal.DownloadListenerInstaller
import com.tencent.qian.webview.internal.WebSettingsApplier
import com.tencent.qian.webview.model.SignCallback
import com.tencent.qian.webview.model.TencentQianWebViewConfig
import com.tencent.qian.webview.permission.RuntimePermissionRequester
import com.tencent.qian.webview.scheme.ExternalSchemeHandler
import com.tencent.qian.webview.scheme.SchemeInterceptor

/**
 * **配置模式**:让客户已有 WebView 获得 SDK 全部能力,无需替换容器。
 *
 * **API**:
 * ```kotlin
 * val handle = TencentQianWebViewConfigurator.apply(
 *     activity = this,
 *     webView = myExistingWebView,
 *     config = TencentQianWebViewConfig(...),
 *     callback = object : SignCallback { ... }
 * )
 * webView.loadUrl(signUrl)
 * // 退出页面时:
 * handle.detach()
 * ```
 *
 * **幂等性**:对同一个 [WebView] 二次 `apply` 会替换前次注入的 client/listener,**不堆叠**。
 *
 * **chained delegate**:本期暂未保留客户原 client。客户应在 `apply` 之前**不要**自行 setWebViewClient
 * / setWebChromeClient;若已设,会被本 SDK 替换。
 */
public object TencentQianWebViewConfigurator {

    /**
     * 在 [webView] 上套用 SDK 全部能力。
     *
     * **重要**:必须在 Activity 的 `onCreate()` 中调用。
     * 原因:Android `registerForActivityResult` 要求在 Activity `CREATED` 之前注册,
     * 否则会抛 `IllegalStateException`。如果需要在按钮点击等时机动态切换 config,
     * 请使用 [Handle] 的 `updateCallback()` 方法而非重新调用 `apply()`。
     *
     * @param activity 必需 [androidx.activity.ComponentActivity],用于 ActivityResult / 权限申请;
     *                 非 ComponentActivity(如老的 [android.app.Activity])会抛 [IllegalArgumentException]
     * @param permissionRequester 可选。传入**已在 Activity `onCreate` 前注册**的实例则复用,不再内部注册;
     *                            为 `null`(默认,配置模式客户用法)时内部调 [RuntimePermissionRequester.register]。
     *                            **崩溃重建等 Activity 已 STARTED/RESUMED 后二次 `apply` 的场景必须传入,**
     *                            否则内部 `register` 会抛 `IllegalStateException`("must call register before STARTED")。
     * @param fileChooserLauncher 可选。语义同 [permissionRequester];为 `null` 时内部注册。
     */
    @JvmStatic
    @JvmOverloads
    public fun apply(
        activity: Activity,
        webView: WebView,
        config: TencentQianWebViewConfig = TencentQianWebViewConfig.DEFAULT,
        callback: SignCallback? = null,
        progressListener: ((Int) -> Unit)? = null,
        permissionRequester: RuntimePermissionRequester? = null,
        fileChooserLauncher: FileChooserLauncher? = null
    ): Handle {
        val componentActivity = activity as? androidx.activity.ComponentActivity
            ?: throw IllegalArgumentException(
                "TencentQianWebViewConfigurator.apply requires an androidx.activity.ComponentActivity " +
                    "(e.g. AppCompatActivity or FragmentActivity); got ${activity.javaClass.name}"
            )

        // 卸载之前的 SDK 注入(幂等)
        detachIfTagged(webView)

        // ── 1. WebSettings ──
        WebSettingsApplier.apply(webView, config)

        // ── 2. 回调 holder(WeakReference 防止 Activity 泄漏)──
        val holder = CallbackHolder(callback)

        // ── 3. 拦截器 ──
        val schemeInterceptor = SchemeInterceptor(holder::get)
        val externalSchemeHandler = ExternalSchemeHandler(config.externalSchemes, holder::get)

        // ── 4. ChromeClient 依赖(权限 / 文件选择)──
        // registerForActivityResult 必须在 Activity STARTED 之前注册;崩溃重建时 Activity 已 RESUMED,
        // 只能复用调用方在 onCreate 期间预注册好的实例,不能在此内部再 register(否则抛 IllegalStateException)。
        val resolvedPermissionRequester = permissionRequester
            ?: RuntimePermissionRequester.register(componentActivity)
        val resolvedFileChooserLauncher = fileChooserLauncher
            ?: FileChooserLauncher.register(
                activity = componentActivity,
                permissionRequester = resolvedPermissionRequester,
                activityProvider = { activity }
            )

        val chromeClient = QianWebChromeClient(
            config = config,
            permissionRequester = resolvedPermissionRequester,
            fileChooserLauncher = resolvedFileChooserLauncher,
            callbackProvider = holder::get,
            activityProvider = { activity },
            progressListener = progressListener
        )
        val webViewClient = QianWebViewClient(
            schemeInterceptor = schemeInterceptor,
            externalSchemeHandler = externalSchemeHandler,
            callbackProvider = holder::get,
            contextProvider = { activity }
        )

        webView.webChromeClient = chromeClient
        webView.webViewClient = webViewClient

        // ── 5. 下载监听 ──
        DownloadListenerInstaller.install(webView, activity, resolvedPermissionRequester, { activity }, holder::get, config)

        // ── 6. 标记此 WebView 已注入,以便 detach / 二次 apply 识别 ──
        webView.setTag(R.id.qian_sdk_injected, holder)

        return Handle(webView, holder)
    }

    /**
     * SDK 注入句柄。
     *
     * - [detach]: 仅解绑 SDK 注入的 client/listener/callback，WebView 实例保留可继续使用。
     * - [detachAndDestroy]: 解绑 SDK 并销毁 WebView，释放全部资源。
     */
    public class Handle internal constructor(
        private val webView: WebView,
        private val holder: CallbackHolder
    ) {
        /**
         * 解绑 SDK 注入的 client / listener / callback，但**不销毁** WebView。
         *
         * 适用于客户需要复用同一个 WebView 实例的场景（例如解绑后加载其他非签署页面）。
         * 调用后 WebView 仍可正常使用，只是不再具备 SDK 能力。
         */
        public fun detach() {
            holder.clear()
            try {
                webView.webChromeClient = null
                webView.webViewClient = QianFallbackWebViewClient
                webView.setDownloadListener(null)
                webView.setTag(R.id.qian_sdk_injected, null)
            } catch (_: Throwable) {
                // best-effort
            }
        }

        /**
         * 解绑 SDK 并销毁 WebView，释放全部资源。
         *
         * 等价于 [detach] + 标准 WebView 销毁流程：
         * `removeView → stopLoading → loadUrl("about:blank") → destroy()`。
         *
         * 适用于页面退出时一并释放 WebView 资源的场景（最常见用法）。
         * 调用后 WebView 实例不可再使用。
         */
        public fun detachAndDestroy() {
            detach()
            try {
                (webView.parent as? android.view.ViewGroup)?.removeView(webView)
                webView.stopLoading()
                // 先加载空白页清空 JS 上下文，避免部分 OEM（华为 EMUI 11 / OPPO ColorOS 12）
                // 在 destroy() 时因未完成的 JS 回调而抛 IllegalStateException / NPE
                webView.loadUrl("about:blank")
                webView.destroy()
            } catch (_: Throwable) {
                // best-effort
            }
        }
    }

    public class CallbackHolder internal constructor(initial: SignCallback?) {
        @Volatile private var ref: SignCallback? = initial

        public fun get(): SignCallback? = ref
        public fun set(callback: SignCallback?) { ref = callback }
        public fun clear() { ref = null }
    }

    private fun detachIfTagged(webView: WebView) {
        val prev = webView.getTag(R.id.qian_sdk_injected) as? CallbackHolder ?: return
        prev.clear()
        webView.setTag(R.id.qian_sdk_injected, null)
    }

    /**
     * detach 后的兜底 client（复用实例归还态 / 客户 detach 后继续使用的实例）。
     *
     * **关键**：必须处理 [onRenderProcessGone] 返回 `true`——否则走系统默认 `false`，
     * 归还态池实例的渲染进程一旦被系统回收，会把整个进程的 WebView 子系统拖垮
     * （退出重进、release 都救不回，唯有杀进程），表现为持久白屏。
     * 同时作废可能命中的池实例，避免僵尸实例被后续 open 复用。
     */
    private object QianFallbackWebViewClient : android.webkit.WebViewClient() {
        @androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.O)
        override fun onRenderProcessGone(
            view: android.webkit.WebView?,
            detail: android.webkit.RenderProcessGoneDetail?
        ): Boolean {
            com.tencent.qian.webview.prewarm.WebViewPool.invalidateIfPooled(view)
            return true
        }
    }
}
