package com.tencent.qian.webview

import android.animation.ObjectAnimator
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.webkit.WebView
import android.widget.ImageButton
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tencent.qian.webview.internal.IntentBridge
import com.tencent.qian.webview.model.SdkEvent
import com.tencent.qian.webview.model.SignCallback
import com.tencent.qian.webview.model.SignResult
import com.tencent.qian.webview.model.TencentQianWebViewConfig

/**
 * SDK 内置容器 Activity。
 *
 * **不要由客户直接 startActivity**;走 [TencentQianWebView.open] 入口,
 * 通过 [IntentBridge] 携带 callback / config。
 *
 * 内部复用 [TencentQianWebViewConfigurator.apply] 注入 SDK 能力,
 * 避免与配置模式重复维护 WebView 初始化逻辑。
 */
public class TencentQianWebViewActivity : AppCompatActivity() {

    private lateinit var webView: WebView
    private lateinit var topBar: View
    private lateinit var titleView: TextView
    private lateinit var backBtn: ImageButton
    private lateinit var closeBtn: ImageButton
    private lateinit var progressBar: ProgressBar

    private lateinit var config: TencentQianWebViewConfig
    private var externalCallback: SignCallback? = null

    /** Configurator 注入句柄,onDestroy 时用于解绑并销毁 WebView。 */
    private var handle: TencentQianWebViewConfigurator.Handle? = null

    /**
     * 权限申请器 / 文件选择器:**必须在 Activity `onCreate`(STARTED 之前)注册一次并持有**,
     * 供首次 [wireWebView] 与崩溃重建 [recoverFromRenderProcessGone] 时的二次 [wireWebView] 复用。
     *
     * 二者底层是 `registerForActivityResult`,AndroidX 要求在 STARTED 之前注册,否则抛
     * `IllegalStateException`。崩溃重建发生时 Activity 已 RESUMED,若届时再 `register` 必崩,
     * 因此这里预注册后复用,避免重建路径退化为"直接关闭容器"。
     */
    private var permissionRequester: com.tencent.qian.webview.permission.RuntimePermissionRequester? = null
    private var fileChooserLauncher: com.tencent.qian.webview.filechooser.FileChooserLauncher? = null

    /** 本容器 WebView 是否借自预热池（决定关闭时归还还是销毁）。 */
    private var fromPool: Boolean = false

    /** 进度条动画(ObjectAnimator 精细控制,对应 iOS KVO estimatedProgress) */
    private var progressAnimator: ObjectAnimator? = null
    private var fadeOutAnimator: ObjectAnimator? = null

    /** 当前是否处于页面加载周期(PageStarted 后、PageFinished/hideProgress 前) */
    private var isPageLoading = false

    /**
     * 池复用（HEAVY 预热）后首个 [SdkEvent.PageFinished] 时清一次历史。
     *
     * 预热池实例已 `loadUrl(warmupUrl)`（历史栈残留），借出后容器再 `loadUrl(signingUrl)`，
     * 历史栈变成 `[warmupUrl, signingUrl]` → 第一次返回会 `goBack()` 退回预热页而非退出。
     * 首个 PageFinished（signingUrl 成为当前页）时 `clearHistory()` 铲掉 warmup/about:blank 残留，
     * 使 signingUrl 成为唯一历史项。仅执行一次，不影响 H5 页内多级前进/后退。自建实例（首页即
     * signingUrl，无残留）不置位。
     */
    private var clearPrewarmHistoryOnce: Boolean = false

    /**
     * 渲染进程崩溃连续恢复计数，防止"必崩页面"死循环重建。
     *
     * 每次崩溃恢复 +1，页面成功加载完成（PageFinished）时清零。连续崩溃达
     * [MAX_RENDER_RECOVERY] 次仍未成功加载 → 判定为不可恢复，停止重建并关闭容器，
     * 避免无限新建 WebView 造成 CPU/耗电/日志风暴。
     */
    private var renderRecoveryCount: Int = 0

    /**
     * 上次渲染进程崩溃恢复的时间戳（毫秒）。用于冷却窗口重置：距上次崩溃超过
     * [RENDER_RECOVERY_RESET_WINDOW_MS] 视为页面已稳定运行过，本次按全新问题处理并
     * 重置 [renderRecoveryCount]，避免长时间运行中的偶发（非连续）崩溃累计触顶被误判为不可恢复。
     */
    private var lastRenderTerminateAtMs: Long = 0L

    /**
     * 最近一次主框架导航的真实业务 URL，用于渲染进程崩溃恢复时精准回到崩溃前页面
     * （而非退回初始首页）。由 [SdkEvent.PageStarted] 记录，过滤 about:blank / 非 http(s)。
     * 对齐 iOS `lastProvisionalURL` / HarmonyOS `lastVisitedUrl`。
     */
    private var lastVisitedUrl: String = ""

    /**
     * 渲染进程在**后台**崩溃时的待恢复标记。后台崩溃不立即重建（用户看不到，浪费资源且
     * 内存压力下易再次崩溃），改为标记，等 [onResume] 回到前台再恢复（官方推荐做法）。
     */
    private var pendingRenderRecovery: Boolean = false

    // ────────── 桥接:把外部 callback + 内部容器事件合并 ──────────

    private val bridgedCallback = object : SignCallback {
        override fun onSignResult(result: SignResult) {
            externalCallback?.onSignResult(result)
            // 协议回调拿到后,容器自动 finish
            finish()
        }

        override fun onEvent(event: SdkEvent) {
            externalCallback?.onEvent(event)
            if (event is SdkEvent.PageStarted) {
                showProgress()
                // 记录真实业务页 URL（过滤 about:blank / 非 http(s)），供崩溃恢复精准回退。
                val startedUrl = event.url
                if (startedUrl.startsWith("http://") || startedUrl.startsWith("https://")) {
                    lastVisitedUrl = startedUrl
                }
            }
            if (event is SdkEvent.PageFinished) {
                hideProgress()
                // 页面成功加载完成 → 崩溃恢复计数清零，但**必须门控稳定运行窗口**：
                // OOM 型必崩页面每次都是"加载完成 → 运行几秒 → 再崩"，若无条件清零，则 PageFinished
                // 会把刚累加的计数抹掉，renderRecoveryCount 永远停在 1，MAX_RENDER_RECOVERY 上限形同
                // 虚设、陷入无限重建。仅当距上次崩溃超过 RENDER_RECOVERY_RESET_WINDOW_MS（真正稳定运行过）
                // 才清零；否则保留计数，让连续崩溃能累加到上限并放弃恢复。
                // 从未崩溃过的正常页面：lastRenderTerminateAtMs 初始 0，差值远超窗口，照常清零（本就是 0，no-op）。
                if (System.currentTimeMillis() - lastRenderTerminateAtMs > RENDER_RECOVERY_RESET_WINDOW_MS) {
                    renderRecoveryCount = 0
                }
                // 池复用后的首个页面加载完成：清掉预热残留历史（warmupUrl / about:blank），
                // 使 signingUrl 成为唯一历史项，避免第一次返回退回预热页。仅执行一次。
                if (clearPrewarmHistoryOnce) {
                    clearPrewarmHistoryOnce = false
                    try {
                        webView.clearHistory()
                    } catch (_: Throwable) {
                        // ignore
                    }
                }
            }
            // 渲染进程崩溃后恢复页面,避免白屏
            if (event is SdkEvent.RenderProcessGone) {
                // 前台立即恢复；后台仅标记，等回前台再恢复（避免后台无谓重建，官方推荐）。
                if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
                    recoverFromRenderProcessGone()
                } else {
                    pendingRenderRecovery = true
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 取出从 TencentQianWebView.open 传入的回调与配置
        externalCallback = IntentBridge.takeCallback()
        config = IntentBridge.takeConfig()

        // 系统因内存不足杀死 Activity 后重建(savedInstanceState != null),
        // IntentBridge 中的 callback 已丢失,签署完成后无法通知客户 → 业务逻辑断裂。
        // 此时提示用户页面已过期,引导重新发起。
        if (savedInstanceState != null && externalCallback == null) {
            Log.w(TAG, "Activity recreated after process death, callback lost. Finishing.")
            Toast.makeText(this, getString(R.string.qian_session_expired), Toast.LENGTH_LONG).show()
            finish()
            return
        }

        applyStatusBarStyle()

        setContentView(R.layout.qian_activity_webview)
        topBar = findViewById(R.id.qian_top_bar)
        titleView = findViewById(R.id.qian_title)
        backBtn = findViewById(R.id.qian_btn_back)
        closeBtn = findViewById(R.id.qian_btn_close)
        progressBar = findViewById(R.id.qian_progress)
        val webViewContainer = findViewById<android.widget.FrameLayout>(R.id.qian_webview_container)

        // WebView 一律离屏创建 / 从预热池借出后 addView（杜绝界面容器自动创建）。
        // 池有匹配实例（UA 配置一致、未借出）→ 借出复用（省创建 + 命中 warmup 缓存）；否则自建自管。
        webView = obtainOrCreateWebView()
        webViewContainer.addView(
            webView,
            0,
            android.widget.FrameLayout.LayoutParams(
                android.view.ViewGroup.LayoutParams.MATCH_PARENT,
                android.view.ViewGroup.LayoutParams.MATCH_PARENT
            )
        )

        applyTopBarStyle()
        wireWebView()
        wireBackPress()

        val url = intent?.getStringExtra(EXTRA_URL).orEmpty()
        if (url.isEmpty()) {
            finish()
            return
        }
        // 池复用实例已加载过 warmupUrl（历史栈残留）；标记首个 PageFinished 清历史，
        // 使 signingUrl 成为唯一历史项，避免第一次返回退回预热页。
        if (fromPool) {
            clearPrewarmHistoryOnce = true
        }
        webView.loadUrl(url)
    }

    /**
     * 离屏创建或从预热池借出 WebView（借还池）。
     *
     * - 池有匹配实例（UA 配置一致、未借出）→ 借出复用，[fromPool] = true，
     *   池内实例已 rebind 到本 Activity（[android.content.MutableContextWrapper]）。
     * - 否则自建（Activity context），[fromPool] = false，关闭时销毁。
     */
    private fun obtainOrCreateWebView(): WebView {
        val pooled = TencentQianWebViewPrewarmer.obtainPooledWebView(
            this, config.injectUserAgent, config.userAgentExtra
        )
        if (pooled != null) {
            fromPool = true
            return pooled
        }
        fromPool = false
        return WebView(this)
    }

    private fun applyStatusBarStyle() {
        // 使用传统模式：系统自动处理 insets，adjustPan 正常生效（兼容 Android 7~16+）
        WindowCompat.setDecorFitsSystemWindows(window, true)
        WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
            config.statusBarStyle == TencentQianWebViewConfig.StatusBarStyle.DARK_CONTENT
    }

    private fun applyTopBarStyle() {
        topBar.visibility = if (config.showTopBar) View.VISIBLE else View.GONE
        titleView.text = config.topBarTitle.ifBlank { getString(R.string.qian_default_title) }
        config.topBarBackgroundColor?.let { topBar.setBackgroundColor(it) }
        config.topBarTextColor?.let {
            titleView.setTextColor(it)
            backBtn.setColorFilter(it)
            closeBtn.setColorFilter(it)
        }
        closeBtn.visibility = if (config.showCloseButton) View.VISIBLE else View.GONE

        backBtn.setOnClickListener {
            if (webView.canGoBack()) webView.goBack() else attemptCloseWithConfirm()
        }
        closeBtn.setOnClickListener { attemptCloseWithConfirm() }
    }

    /**
     * 复用 [TencentQianWebViewConfigurator.apply] 注入 SDK 全部能力。
     *
     * 与 iOS 容器模式架构一致:容器不重复实现 WebView 初始化逻辑,
     * 而是委托给 Configurator 统一管理。
     */
    private fun wireWebView() {
        // launcher 仅在首次(onCreate,STARTED 之前)注册一次并缓存;崩溃重建的二次 wireWebView
        // 会复用缓存实例,绝不在 RESUMED 后再 register(否则 registerForActivityResult 抛 IllegalStateException)。
        if (permissionRequester == null) {
            permissionRequester =
                com.tencent.qian.webview.permission.RuntimePermissionRequester.register(this)
        }
        if (fileChooserLauncher == null) {
            fileChooserLauncher = com.tencent.qian.webview.filechooser.FileChooserLauncher.register(
                activity = this,
                permissionRequester = permissionRequester,
                activityProvider = { this }
            )
        }
        handle = TencentQianWebViewConfigurator.apply(
            activity = this,
            webView = webView,
            config = config,
            callback = bridgedCallback,
            progressListener = { newProgress -> animateProgressTo(newProgress) },
            permissionRequester = permissionRequester,
            fileChooserLauncher = fileChooserLauncher
        )
    }

    /**
     * 渲染进程崩溃恢复：**销毁失效实例 + 新建实例重载**（而非 reload）。
     *
     * 渲染进程 gone 后，原 WebView 已永久失效，`reload()` / `loadUrl()` 均无效
     * （表现为白屏、无 `onPageStarted`、无进度条）。必须销毁旧实例并新建，
     * 才能拉起新的渲染进程。
     *
     * 崩溃实例若借自预热池，一并作废池实例（[TencentQianWebViewPrewarmer.invalidatePooledWebView]），
     * 避免"渲染进程已死的僵尸实例"被后续 `open` 反复复用导致持久白屏；新建实例改为自建，关闭时销毁。
     */
    private fun recoverFromRenderProcessGone() {
        // 冷却重置：距上次崩溃超过 RENDER_RECOVERY_RESET_WINDOW_MS 视为页面已稳定运行过，
        // 本次按全新问题处理，重置连续计数，避免偶发崩溃被误判为"不可恢复"。
        val now = System.currentTimeMillis()
        if (now - lastRenderTerminateAtMs > RENDER_RECOVERY_RESET_WINDOW_MS) {
            renderRecoveryCount = 0
        }
        lastRenderTerminateAtMs = now
        // 连续崩溃达上限 → 判定不可恢复，停止重建并关闭容器，避免死循环重建风暴。
        renderRecoveryCount++
        if (renderRecoveryCount > MAX_RENDER_RECOVERY) {
            Log.w(TAG, "render process crashed $renderRecoveryCount times, giving up recovery")
            finish()
            return
        }
        val reloadUrl = resolveRecoveryUrl(
            currentUrl = webView.url,
            lastVisitedUrl = lastVisitedUrl,
            initialUrl = intent?.getStringExtra(EXTRA_URL)
        )
        if (reloadUrl.isEmpty()) {
            finish()
            return
        }
        try {
            val container = findViewById<android.widget.FrameLayout>(R.id.qian_webview_container)
            val crashed = webView

            // 1. 解绑 SDK 注入
            handle?.detach()
            handle = null

            // 2. 崩溃实例若来自预热池 → 作废池（避免僵尸复用），后续改为自建
            if (fromPool) {
                TencentQianWebViewPrewarmer.invalidatePooledWebView(crashed)
                fromPool = false
            }

            // 3. 从容器移除并销毁崩溃实例
            (crashed.parent as? android.view.ViewGroup)?.removeView(crashed)
            try {
                crashed.stopLoading()
                crashed.loadUrl("about:blank")
                crashed.destroy()
            } catch (_: Throwable) {
                // best-effort
            }

            // 4. 新建实例（自建）并重新注入 SDK 能力
            webView = WebView(this)
            container?.addView(
                webView,
                0,
                android.widget.FrameLayout.LayoutParams(
                    android.view.ViewGroup.LayoutParams.MATCH_PARENT,
                    android.view.ViewGroup.LayoutParams.MATCH_PARENT
                )
            )
            wireWebView()

            // 5. 重载页面（新建实例无预热历史残留，无需清历史）
            clearPrewarmHistoryOnce = false
            webView.loadUrl(reloadUrl)
        } catch (_: Throwable) {
            // 恢复失败,关闭容器
            finish()
        }
    }

    // ────────── 进度条动画(叠加层 + ObjectAnimator,消除跳动) ──────────

    /**
     * 页面开始加载:取消淡出动画,重置进度,淡入显示。
     */
    private fun showProgress() {
        fadeOutAnimator?.cancel()
        progressBar.progress = 0
        progressBar.alpha = 1f
        isPageLoading = true
    }

    /**
     * 页面加载完成:延迟淡出,让用户看到 100% 后再消失。
     */
    private fun hideProgress() {
        isPageLoading = false
        progressAnimator?.cancel()
        progressBar.progress = 100
        fadeOutAnimator?.cancel()
        fadeOutAnimator = ObjectAnimator.ofFloat(progressBar, "alpha", 1f, 0f).apply {
            startDelay = 200L  // 让用户看到 100% 后再消失
            duration = 300L
            addListener(object : android.animation.AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: android.animation.Animator) {
                    progressBar.progress = 0
                }
            })
            start()
        }
    }

    /**
     * 平滑动画过渡到目标进度值(对应 iOS 的 KVO estimatedProgress 更新)。
     * 使用 DecelerateInterpolator 模拟自然减速效果。
     *
     * **重要**:只有当前处于页面加载周期(isPageLoading=true)时才更新进度;
     * 避免 SPA 路由切换时子资源加载触发的 onProgressChanged 意外打开进度条。
     */
    private fun animateProgressTo(target: Int) {
        if (!isPageLoading) return
        progressAnimator?.cancel()
        progressAnimator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.progress, target).apply {
            duration = 200L
            interpolator = DecelerateInterpolator()
            start()
        }
    }

    private fun wireBackPress() {
        onBackPressedDispatcher.addCallback(this) {
            if (webView.canGoBack()) {
                webView.goBack()
            } else {
                attemptCloseWithConfirm()
            }
        }
    }

    private fun attemptCloseWithConfirm() {
        if (!config.confirmBeforeClose) {
            finish()
            return
        }
        MaterialAlertDialogBuilder(this)
            .setTitle(config.confirmCloseTitle.ifBlank { getString(R.string.qian_confirm_close_title) })
            .setMessage(config.confirmCloseMessage.ifBlank { getString(R.string.qian_confirm_close_message) })
            .setPositiveButton(R.string.qian_confirm_leave) { d, _ -> d.dismiss(); finish() }
            .setNegativeButton(R.string.qian_confirm_stay) { d, _ -> d.dismiss() }
            .show()
    }

    override fun onResume() {
        super.onResume()
        // 后台崩溃的待恢复：回到前台（用户可见）时才重建 WebView。
        if (pendingRenderRecovery) {
            pendingRenderRecovery = false
            recoverFromRenderProcessGone()
        }
    }

    override fun onDestroy() {
        progressAnimator?.cancel()
        fadeOutAnimator?.cancel()
        if (fromPool) {
            // 借还池：解绑 SDK 注入（不销毁）→ 归还池（recycle 内部 removeView + about:blank + clearHistory + rebind appContext）
            handle?.detach()
            TencentQianWebViewPrewarmer.recyclePooledWebView(webView)
        } else {
            // 自建：解绑并销毁
            handle?.detachAndDestroy()
        }
        handle = null
        externalCallback = null
        super.onDestroy()
    }

    public companion object {
        private const val TAG = "QianWebViewActivity"
        public const val EXTRA_URL: String = "com.tencent.qian.webview.EXTRA_URL"

        /** 渲染进程连续崩溃恢复次数上限，超过则判定不可恢复并关闭容器。 */
        private const val MAX_RENDER_RECOVERY = 3

        /**
         * 崩溃恢复计数的冷却窗口（毫秒）。距上次崩溃超过此窗口视为页面已稳定运行过，
         * 本次崩溃按全新问题处理并重置连续计数，避免长时间运行中的偶发（非连续）崩溃
         * 累计触顶被误判为"不可恢复"。对齐 iOS renderRecoveryResetWindow。
         */
        private const val RENDER_RECOVERY_RESET_WINDOW_MS = 30_000L

        /**
         * 解析渲染进程崩溃恢复时应重载的 URL，优先级：
         * 1. 崩溃后 WebView 仍有效的当前 URL（`currentUrl`，非空且非 about:blank）
         * 2. 崩溃前记录的最近业务页（`lastVisitedUrl`，恢复到崩溃前当前页而非首页）
         * 3. 容器初始 URL（`initialUrl`，最终兜底）
         * 全为空 → 返回空串（调用方据此关闭容器）。
         */
        internal fun resolveRecoveryUrl(
            currentUrl: String?,
            lastVisitedUrl: String,
            initialUrl: String?
        ): String {
            currentUrl?.takeIf { it.isNotBlank() && it != "about:blank" }?.let { return it }
            if (lastVisitedUrl.isNotBlank() && lastVisitedUrl != "about:blank") return lastVisitedUrl
            return initialUrl.orEmpty()
        }
    }
}
