package com.zhongqu.vrviewer.x5host;

import android.app.Application;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.tencent.smtt.export.external.TbsCoreSettings;
import com.tencent.smtt.sdk.ProgressListener;
import com.tencent.smtt.sdk.QbSdk;
import com.tencent.smtt.sdk.TbsFramework;
import com.tencent.smtt.sdk.WebView;
import com.tencent.smtt.sdk.core.dynamicinstall.DynamicInstallManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import androidx.annotation.Nullable;

/**
 * 与 publicDemo 流程一致：{@code initTbsSettings} + assets 中 {@code config/config.tbs}（由宿主放入，Demo 见
 * {@code embedded-bundles/tbs-assets}）+
 * {@link TbsFramework#setUp} + {@link DynamicInstallManager} 安装完成后 {@link QbSdk#preInit}，再创建
 * {@link com.tencent.smtt.sdk.WebView}。
 */
public final class TbsBootstrap {

    public static final String ASSET_CONFIG_PATH = "config/config.tbs";

    private static final String TAG = "TbsBootstrap";

    private static final AtomicBoolean started = new AtomicBoolean(false);
    private static final AtomicBoolean coreReady = new AtomicBoolean(false);

    private static final Handler mainHandler = new Handler(Looper.getMainLooper());

    /** 动态安装 / preInit 失败原因，便于 Demo 展示（无则 null）。 */
    private static volatile String sPipelineError;

    public interface ViewInitObserver {
        void onViewInitFinished(boolean isX5Code);
    }

    private static volatile ViewInitObserver viewInitObserver;

    private TbsBootstrap() {}

    public static void setViewInitObserver(ViewInitObserver observer) {
        viewInitObserver = observer;
    }

    public static boolean isCoreReady() {
        return coreReady.get();
    }

    @Nullable
    public static String getPipelineError() {
        return sPipelineError;
    }

    /**
     * 在 {@link Application#onCreate()} 中调用一次；内部为异步，创建 WebView 前应 {@link #isCoreReady()} 或阻塞至就绪。
     */
    public static void start(Application app) {
        if (!started.compareAndSet(false, true)) {
            return;
        }
        QbSdk.setDownloadWithoutWifi(true);

        Map<String, Object> map = new HashMap<>();
        map.put(TbsCoreSettings.MULTI_PROCESS_ENABLE, 1);
        QbSdk.initTbsSettings(map);

        File configFile = materializeConfigFromAssets(app);
        if (configFile == null || !configFile.exists()) {
            sPipelineError = "缺少 assets/" + ASSET_CONFIG_PATH;
            Log.e(TAG, sPipelineError + " ，无法按 publicDemo 流程加载 X5");
            return;
        }

        Log.i(TAG, "TbsFramework.setUp config=" + configFile.getAbsolutePath());
        TbsFramework.setUp(app, configFile);

        DynamicInstallManager manager = new DynamicInstallManager(app);
        manager.registerListener(
                new ProgressListener() {
                    @Override
                    public void onProgress(int progress) {
                        Log.i(TAG, "DynamicInstall progress=" + progress);
                    }

                    @Override
                    public void onFinished() {
                        Log.i(TAG, "DynamicInstall onFinished -> QbSdk.preInit (main thread)");
                        // TBS 要求 preInit 在主线程调用时更稳定；且部分机型 onViewInitFinished 需配合后续 WebView 创建
                        mainHandler.post(
                                () ->
                                        QbSdk.preInit(
                                                app,
                                                new QbSdk.PreInitCallback() {
                                                    @Override
                                                    public void onCoreInitFinished() {
                                                        Log.i(TAG, "QbSdk.onCoreInitFinished");
                                                        coreReady.set(true);
                                                        // 主动在主线程创建一个临时 WebView，促使 SDK 尽快走视图初始化并回调 onViewInitFinished
                                                        warmupSmttWebView(app);
                                                    }

                                    @Override
                                    public void onViewInitFinished(boolean isX5Code) {
                                        Log.i(TAG, "QbSdk.onViewInitFinished isX5Code=" + isX5Code);
                                        X5PublicDemoVerify.logPreInitViewFinished(isX5Code);
                                        ViewInitObserver o = viewInitObserver;
                                        if (o != null) {
                                            o.onViewInitFinished(isX5Code);
                                        }
                                    }
                                                }));
                    }

                    @Override
                    public void onFailed(int code, String msg) {
                        String detail = "DynamicInstall失败 code=" + code + " msg=" + msg;
                        if (code == -1012
                                || (msg != null && msg.toLowerCase().contains("sig not match"))) {
                            detail +=
                                    " | 说明:动态内核与 APK 签名不一致。x5Kernel 变体需与 publicDemo 相同使用"
                                        + " 仓库根目录 signing/x5static.jks 签名（各 Demo Gradle 已指向该文件）。"
                                        + " 若仍失败请确认 applicationId 为 com.zyn.x5.demo 且未改 jks。";
                        }
                        sPipelineError = detail;
                        Log.e(TAG, sPipelineError);
                        // 动态安装失败后，部分机型上仍可能通过 preInit 使用本机/存量 TBS 内核
                        //（否则 coreReady 永不置位，带看永远进不去）。
                        mainHandler.post(() -> preInitAfterPipelineFailure(app));
                    }
                });
        manager.startInstall();
    }

    /**
     * 在 {@link ProgressListener#onFailed} 之后仍尝试走一遍 {@link QbSdk#preInit}，避免仅因动态装失败
     * 就永远无法 {@link #isCoreReady}。
     */
    private static void preInitAfterPipelineFailure(Application app) {
        if (coreReady.get()) {
            return;
        }
        Log.w(TAG, "preInit (dynamic install failed / fallback path)");
        try {
            QbSdk.preInit(
                    app,
                    new QbSdk.PreInitCallback() {
                        @Override
                        public void onCoreInitFinished() {
                            Log.i(TAG, "QbSdk.onCoreInitFinished (after dynamic install onFailed)");
                            if (coreReady.compareAndSet(false, true)) {
                                warmupSmttWebView(app);
                            }
                        }

                        @Override
                        public void onViewInitFinished(boolean isX5Code) {
                            Log.i(TAG, "QbSdk.onViewInitFinished (after dynamic install onFailed) isX5Code=" + isX5Code);
                            X5PublicDemoVerify.logPreInitViewFinished(isX5Code);
                            ViewInitObserver o = viewInitObserver;
                            if (o != null) {
                                o.onViewInitFinished(isX5Code);
                            }
                        }
                    });
        } catch (Throwable t) {
            Log.e(TAG, "preInit after dynamic install failed 仍异常", t);
            sPipelineError =
                    (sPipelineError != null ? sPipelineError + " | " : "")
                            + "fallback preInit: "
                            + t.getMessage();
        }
    }

    /** 触发 TBS 视图层初始化；必须在主线程调用。 */
    private static void warmupSmttWebView(final Application app) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            mainHandler.post(() -> warmupSmttWebView(app));
            return;
        }
        WebView wv = null;
        try {
            wv = new WebView(app.getApplicationContext());
            wv.loadUrl("about:blank");
        } catch (Throwable t) {
            Log.e(TAG, "X5 warmup WebView 失败", t);
            sPipelineError = "warmup WebView: " + t.getMessage();
        }
        final WebView toDestroy = wv;
        if (toDestroy != null) {
            mainHandler.postDelayed(
                    () -> {
                        try {
                            toDestroy.destroy();
                        } catch (Throwable ignored) {
                        }
                    },
                    800L);
        }
    }

    private static File materializeConfigFromAssets(Context context) {
        try {
            AssetManager assets = context.getAssets();
            InputStream in = assets.open(ASSET_CONFIG_PATH);
            String name = ASSET_CONFIG_PATH.substring(ASSET_CONFIG_PATH.indexOf('/') + 1);
            File out = new File(context.getFilesDir(), name);
            FileOutputStream fos = new FileOutputStream(out);
            byte[] buf = new byte[4096];
            int n;
            while ((n = in.read(buf)) != -1) {
                fos.write(buf, 0, n);
            }
            fos.close();
            in.close();
            return out;
        } catch (IOException e) {
            Log.e(TAG, "复制 config.tbs 失败", e);
            return null;
        }
    }
}
