package com.tencent.qian.webview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
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.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.filechooser.FileChooserLauncher;
import com.tencent.qian.webview.internal.IntentBridge;
import com.tencent.qian.webview.permission.RuntimePermissionRequester;
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。
 *
 * <p>不要由客户直接 startActivity；走 {@link TencentQianWebView#open} 入口。
 */
public class TencentQianWebViewActivity extends AppCompatActivity {

    private static final String TAG = "QianWebViewActivity";
    public static final String EXTRA_URL = "com.tencent.qian.webview.EXTRA_URL";

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

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

    /**
     * 解析渲染进程崩溃恢复时应重载的 URL，优先级：
     * <ol>
     *   <li>崩溃后 WebView 仍有效的当前 URL（{@code currentUrl}，非空且非 about:blank）
     *   <li>崩溃前记录的最近业务页（{@code lastVisitedUrl}，恢复到崩溃前当前页而非首页）
     *   <li>容器初始 URL（{@code initialUrl}，最终兜底）
     * </ol>
     * 全为空 → 返回空串（调用方据此关闭容器）。
     */
    static String resolveRecoveryUrl(
            @Nullable String currentUrl, @NonNull String lastVisitedUrl, @Nullable String initialUrl) {
        if (currentUrl != null && !currentUrl.isEmpty() && !"about:blank".equals(currentUrl)) {
            return currentUrl;
        }
        if (!lastVisitedUrl.isEmpty() && !"about:blank".equals(lastVisitedUrl)) {
            return lastVisitedUrl;
        }
        return initialUrl != null ? initialUrl : "";
    }

    private WebView webView;
    private View topBar;
    private TextView titleView;
    private ImageButton backBtn;
    private ImageButton closeBtn;
    private ProgressBar progressBar;

    private TencentQianWebViewConfig config;
    @Nullable private SignCallback externalCallback;
    @Nullable private TencentQianWebViewConfigurator.Handle handle;

    /**
     * 权限申请器 / 文件选择器：<b>必须在 Activity {@code onCreate}（STARTED 之前）注册一次并持有</b>，
     * 供首次 {@link #wireWebView} 与崩溃重建 {@link #recoverFromRenderProcessGone} 时的二次
     * {@code wireWebView} 复用。
     *
     * <p>二者底层是 {@code registerForActivityResult}，AndroidX 要求在 STARTED 之前注册，否则抛
     * {@code IllegalStateException}。崩溃重建发生时 Activity 已 RESUMED，若届时再 register 必崩，
     * 因此这里预注册后复用，避免重建路径退化为"直接关闭容器"。
     */
    @Nullable private RuntimePermissionRequester permissionRequester;
    @Nullable private FileChooserLauncher fileChooserLauncher;

    @Nullable private ObjectAnimator progressAnimator;
    @Nullable private ObjectAnimator fadeOutAnimator;
    private boolean isPageLoading = false;
    /** 本容器 WebView 是否借自预热池（决定关闭时归还还是销毁）。 */
    private boolean fromPool = false;
    /**
     * 池复用（HEAVY 预热）后首个 PageFinished 时清一次历史。
     *
     * <p>预热池实例已 loadUrl(warmupUrl)（历史栈残留），借出后容器再 loadUrl(signingUrl)，
     * 历史栈变成 [warmupUrl, signingUrl] → 第一次返回会 goBack() 退回预热页而非退出。首个
     * PageFinished（signingUrl 成为当前页）时 clearHistory() 铲掉 warmup/about:blank 残留，
     * 使 signingUrl 成为唯一历史项。仅执行一次，不影响 H5 页内多级前进/后退。自建实例不置位。
     */
    private boolean clearPrewarmHistoryOnce = false;

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

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

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

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

    private final SignCallback bridgedCallback = new SignCallback() {
        @Override
        public void onSignResult(@NonNull SignResult result) {
            if (externalCallback != null) externalCallback.onSignResult(result);
            finish();
        }

        @Override
        public void onEvent(@NonNull SdkEvent event) {
            if (externalCallback != null) externalCallback.onEvent(event);
            if (event instanceof SdkEvent.PageStarted) {
                showProgress();
                // 记录真实业务页 URL（过滤 about:blank / 非 http(s)），供崩溃恢复精准回退。
                String startedUrl = ((SdkEvent.PageStarted) event).getUrl();
                if (startedUrl.startsWith("http://") || startedUrl.startsWith("https://")) {
                    lastVisitedUrl = startedUrl;
                }
            }
            if (event instanceof 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 ignored) {
                        // ignore
                    }
                }
            }
            if (event instanceof SdkEvent.RenderProcessGone) {
                // 前台立即恢复；后台仅标记，等回前台再恢复（避免后台无谓重建，官方推荐）。
                if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
                    recoverFromRenderProcessGone();
                } else {
                    pendingRenderRecovery = true;
                }
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        externalCallback = IntentBridge.takeCallback();
        config = IntentBridge.takeConfig();

        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);
        android.widget.FrameLayout webViewContainer = findViewById(R.id.qian_webview_container);

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

        applyTopBarStyle();
        wireWebView();
        wireBackPress();

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

    private void applyStatusBarStyle() {
        WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
        WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView())
                .setAppearanceLightStatusBars(config.getStatusBarStyle() == TencentQianWebViewConfig.StatusBarStyle.DARK_CONTENT);
    }

    /**
     * 离屏创建或从预热池借出 WebView（借还池）。
     *
     * <p>池有匹配实例（UA 配置一致、未借出）→ 借出复用，{@link #fromPool} = true，
     * 池内实例已 rebind 到本 Activity（MutableContextWrapper）；否则自建（Activity context），
     * {@link #fromPool} = false，关闭时销毁。
     */
    @NonNull
    private WebView obtainOrCreateWebView() {
        WebView pooled = TencentQianWebViewPrewarmer.obtainPooledWebView(
                this, config.isInjectUserAgent(), config.getUserAgentExtra());
        if (pooled != null) {
            fromPool = true;
            return pooled;
        }
        fromPool = false;
        return new WebView(this);
    }

    private void applyTopBarStyle() {
        topBar.setVisibility(config.isShowTopBar() ? View.VISIBLE : View.GONE);
        String title = config.getTopBarTitle();
        titleView.setText(title.isEmpty() ? getString(R.string.qian_default_title) : title);
        Integer bgColor = config.getTopBarBackgroundColor();
        if (bgColor != null) topBar.setBackgroundColor(bgColor);
        Integer textColor = config.getTopBarTextColor();
        if (textColor != null) {
            titleView.setTextColor(textColor);
            backBtn.setColorFilter(textColor);
            closeBtn.setColorFilter(textColor);
        }
        closeBtn.setVisibility(config.isShowCloseButton() ? View.VISIBLE : View.GONE);

        backBtn.setOnClickListener(v -> {
            if (webView.canGoBack()) webView.goBack();
            else attemptCloseWithConfirm();
        });
        closeBtn.setOnClickListener(v -> attemptCloseWithConfirm());
    }

    private void wireWebView() {
        // launcher 仅在首次(onCreate,STARTED 之前)注册一次并缓存; 崩溃重建的二次 wireWebView
        // 会复用缓存实例, 绝不在 RESUMED 后再 register(否则 registerForActivityResult 抛 IllegalStateException)。
        if (permissionRequester == null) {
            permissionRequester = RuntimePermissionRequester.register(this);
        }
        if (fileChooserLauncher == null) {
            fileChooserLauncher = FileChooserLauncher.register(this, permissionRequester, () -> this);
        }
        handle = TencentQianWebViewConfigurator.apply(
                this, webView, config, bridgedCallback,
                newProgress -> animateProgressTo(newProgress),
                permissionRequester, fileChooserLauncher);
    }

    /**
     * 渲染进程崩溃恢复：销毁失效实例 + 新建实例重载（而非 reload）。
     *
     * <p>渲染进程 gone 后，原 WebView 已永久失效，{@code reload()} / {@code loadUrl()} 均无效
     * （表现为白屏、无 {@code onPageStarted}、无进度条）。必须销毁旧实例并新建，
     * 才能拉起新的渲染进程。
     *
     * <p>崩溃实例若借自预热池，一并作废池实例
     * （{@link TencentQianWebViewPrewarmer#invalidatePooledWebView}），
     * 避免"渲染进程已死的僵尸实例"被后续 {@code open} 反复复用导致持久白屏；新建实例改为自建，关闭时销毁。
     */
    private void recoverFromRenderProcessGone() {
        // 冷却重置：距上次崩溃超过 RENDER_RECOVERY_RESET_WINDOW_MS 视为页面已稳定运行过，
        // 本次按全新问题处理，重置连续计数，避免偶发崩溃被误判为"不可恢复"。
        long 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;
        }
        String reloadUrl = resolveRecoveryUrl(
                webView.getUrl(),
                lastVisitedUrl,
                getIntent() != null ? getIntent().getStringExtra(EXTRA_URL) : null);
        if (reloadUrl.isEmpty()) {
            finish();
            return;
        }
        try {
            android.widget.FrameLayout container = findViewById(R.id.qian_webview_container);
            WebView crashed = webView;

            // 1. 解绑 SDK 注入
            if (handle != null) handle.detach();
            handle = null;

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

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

            // 4. 新建实例（自建）并重新注入 SDK 能力
            webView = new WebView(this);
            if (container != null) {
                container.addView(
                        webView, 0,
                        new 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 t) {
            // 恢复失败,关闭容器
            finish();
        }
    }

    private void showProgress() {
        if (fadeOutAnimator != null) fadeOutAnimator.cancel();
        progressBar.setProgress(0);
        progressBar.setAlpha(1f);
        isPageLoading = true;
    }

    private void hideProgress() {
        isPageLoading = false;
        if (progressAnimator != null) progressAnimator.cancel();
        progressBar.setProgress(100);
        if (fadeOutAnimator != null) fadeOutAnimator.cancel();
        fadeOutAnimator = ObjectAnimator.ofFloat(progressBar, "alpha", 1f, 0f);
        fadeOutAnimator.setStartDelay(200L);
        fadeOutAnimator.setDuration(300L);
        fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                progressBar.setProgress(0);
            }
        });
        fadeOutAnimator.start();
    }

    /**
     * 只有当前处于页面加载周期(isPageLoading=true)时才更新进度;
     * 避免 SPA 路由切换时子资源加载触发的 onProgressChanged 意外打开进度条。
     */
    private void animateProgressTo(int target) {
        if (!isPageLoading) return;
        if (progressAnimator != null) progressAnimator.cancel();
        progressAnimator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.getProgress(), target);
        progressAnimator.setDuration(200L);
        progressAnimator.setInterpolator(new DecelerateInterpolator());
        progressAnimator.start();
    }

    private void wireBackPress() {
        getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
            @Override
            public void handleOnBackPressed() {
                if (webView.canGoBack()) {
                    webView.goBack();
                } else {
                    attemptCloseWithConfirm();
                }
            }
        });
    }

    private void attemptCloseWithConfirm() {
        if (!config.isConfirmBeforeClose()) {
            finish();
            return;
        }
        String title = config.getConfirmCloseTitle();
        String message = config.getConfirmCloseMessage();
        new MaterialAlertDialogBuilder(this)
                .setTitle(title.isEmpty() ? getString(R.string.qian_confirm_close_title) : title)
                .setMessage(message.isEmpty() ? getString(R.string.qian_confirm_close_message) : message)
                .setPositiveButton(R.string.qian_confirm_leave, (d, w) -> { d.dismiss(); finish(); })
                .setNegativeButton(R.string.qian_confirm_stay, (d, w) -> d.dismiss())
                .show();
    }

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

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