package com.gzzm.lobster.bootstrap;

import com.gzzm.lobster.config.LobsterConfig;
import com.gzzm.lobster.sandbox.SandboxPoolService;
import com.gzzm.lobster.skill.SystemSkillLoader;
import com.gzzm.lobster.tool.builtin.BuiltinToolRegistrar;
import com.gzzm.lobster.tool.mcp.McpToolBridge;
import com.gzzm.platform.commons.Tools;
import net.cyan.nest.annotation.Inject;

import java.util.concurrent.TimeUnit;

/**
 * LobsterBootstrap —— 应用启动引导 / Application bootstrap.
 *
 * <p>注册所有内置工具；加载 MCP Server 工具索引；扫描并注册系统 skill。
 * 由现有框架 lifecycle 在启动后调用。
 */
public class LobsterBootstrap {

    @Inject private BuiltinToolRegistrar builtinToolRegistrar;
    @Inject private SystemSkillLoader systemSkillLoader;
    @Inject private McpToolBridge mcpToolBridge;
    @Inject private SandboxPoolService sandboxPoolService;

    /** 向后兼容：不带 webappRoot 的旧调用（单测或非 servlet 环境）. */
    public void onStart() {
        onStart(null);
    }

    /**
     * @param webappRoot  由 BootstrapServlet 从 {@link javax.servlet.ServletContext#getRealPath(String)}
     *                    取得；生产 Tomcat 下 WEB-INF/skills 的定位锚点。可为 null（单测/开发）。
     */
    public void onStart(String webappRoot) {
        try {
            builtinToolRegistrar.registerAll();
            Tools.log("[LobsterBootstrap] built-in tools registered");
        } catch (Throwable t) {
            try { Tools.log("[LobsterBootstrap] tool registrar failed", t); } catch (Throwable ignore) { /* ignore */ }
        }
        try {
            systemSkillLoader.loadAll(webappRoot);
        } catch (Throwable t) {
            try { Tools.log("[LobsterBootstrap] system skill loader failed", t); } catch (Throwable ignore) { /* ignore */ }
        }
        // 启动自检：docker daemon + 沙箱镜像是否就绪。失败不阻塞启动（允许非沙箱功能继续跑），
        // 但日志里留下清晰信号，运维早发现.
        checkSandboxPrereqs();
        try {
            if (LobsterConfig.isSandboxPoolEnabled() && sandboxPoolService != null) {
                sandboxPoolService.warmUp();
                Tools.log("[LobsterBootstrap] sandbox pool prewarm scheduled");
            }
        } catch (Throwable t) {
            try { Tools.log("[LobsterBootstrap] sandbox pool prewarm failed", t); }
            catch (Throwable ignore) { /* ignore */ }
        }
    }

    public void onStop() {
        try {
            if (sandboxPoolService != null) {
                sandboxPoolService.shutdown();
            }
        } catch (Throwable t) {
            try { Tools.log("[LobsterBootstrap] sandbox pool shutdown failed", t); }
            catch (Throwable ignore) { /* ignore */ }
        }
        try {
            if (mcpToolBridge != null) {
                mcpToolBridge.stopAutoRefresh();
            }
        } catch (Throwable t) {
            try { Tools.log("[LobsterBootstrap] MCP auto refresh shutdown failed", t); }
            catch (Throwable ignore) { /* ignore */ }
        }
    }

    private void checkSandboxPrereqs() {
        String docker = LobsterConfig.getSandboxDockerBin();
        String image = LobsterConfig.getSandboxImage();
        // 先测 daemon 可达性（docker version），再测镜像.
        // 分两步的理由：Windows Docker Desktop 首次调 CLI 时 VM 可能还在唤醒，
        // 单次 `image inspect` 10s 超时太紧；先 `docker version` 拿个 ~20s 的耐心，
        // 后面 `image inspect` 基本秒返.
        if (!checkDockerDaemon(docker)) return;
        String inspectOut = checkImage(docker, image);
        if (inspectOut == null) {
            Tools.log("[LobsterBootstrap] sandbox image ready: " + image);
            return;
        }
        // 自愈路径：Docker Desktop on Windows 的已知现象——VM 睡眠/恢复后，content store 里
        // 镜像还在（`docker images` 能列出），但 name index 丢了（`image inspect` 返 No such image）.
        // 先用 `docker images` 看 content 是否还在；在 → 重打一次 tag 强制刷新索引 → 再 inspect 验证.
        // 不在 → 真的缺镜像，落到老的 warning 路径让运维去 build/pull.
        String contentSha = findImageIdByTag(docker, image);
        if (contentSha != null) {
            Tools.log("[LobsterBootstrap] inspect failed but content store still has " + image
                    + " (id=" + contentSha + "); Docker Desktop name-index likely stale after VM "
                    + "sleep/resume. Attempting self-heal via docker tag…");
            if (retagImage(docker, contentSha, image)) {
                String afterHeal = checkImage(docker, image);
                if (afterHeal == null) {
                    Tools.log("[LobsterBootstrap] ✔ sandbox image name index self-healed: "
                            + image + " → " + contentSha);
                    return;
                }
                Tools.log("[LobsterBootstrap] ⚠ re-tag ran but inspect still fails: " + afterHeal
                        + " — Docker Desktop 索引可能真坏了，建议重启 Docker Desktop.");
            } else {
                Tools.log("[LobsterBootstrap] ⚠ self-heal re-tag command itself failed "
                        + "(tag=" + image + ", sha=" + contentSha + ")");
            }
        }
        Tools.log("[LobsterBootstrap] ⚠ sandbox image NOT found: " + image
                + " — run `deploy/sandbox/build.sh` (or build.ps1 on Windows) then set "
                + "<sandboxImage> in lobster.xml, or `docker pull` / `docker load` on this host. "
                + "code_exec will fail until fixed. Raw error: " + inspectOut);
    }

    /** 返 true = daemon OK；false = 已日志告警. */
    private boolean checkDockerDaemon(String docker) {
        try {
            Process p = new ProcessBuilder(docker, "version", "--format", "{{.Server.Version}}")
                    .redirectErrorStream(true).start();
            if (!p.waitFor(30, TimeUnit.SECONDS)) {
                p.destroyForcibly();
                Tools.log("[LobsterBootstrap] ⚠ docker version timed out after 30s — daemon not responding. "
                        + "Check: Docker Desktop / `sudo systemctl status docker`.");
                return false;
            }
            if (p.exitValue() != 0) {
                Tools.log("[LobsterBootstrap] ⚠ docker version failed (exit=" + p.exitValue() + ") via '"
                        + docker + "' — daemon unreachable or current user lacks permission. "
                        + "Check: `docker version` in Tomcat's shell; add tomcat-user to 'docker' group on Linux.");
                return false;
            }
            return true;
        } catch (Throwable t) {
            try { Tools.log("[LobsterBootstrap] ⚠ docker binary '" + docker + "' not in PATH — "
                    + "code_exec will fail. Install docker + ensure PATH contains it in the process "
                    + "that launches Tomcat (for IDE: reload after installing Docker Desktop).", t); }
            catch (Throwable ignore) { /* ignore */ }
            return false;
        }
    }

    /**
     * 按 repo:tag 查 content store，返回 IMAGE ID（短 hash 或 sha256:...）；查不到返 null.
     *
     * <p>用法：{@code docker images --format "{{.ID}}" lobster-sandbox:py3.11-office-v3}.
     * 如果 inspect 失败但这里能返出 id，说明内容还在、只是 name index 坏了——可以 re-tag 自愈.
     */
    private String findImageIdByTag(String docker, String image) {
        try {
            Process p = new ProcessBuilder(docker, "images", "--format", "{{.ID}}", image)
                    .redirectErrorStream(true).start();
            java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();
            try (java.io.InputStream in = p.getInputStream()) {
                byte[] chunk = new byte[4096];
                int n;
                while ((n = in.read(chunk)) > 0) {
                    if (buf.size() < 2048) buf.write(chunk, 0, n);
                }
            }
            if (!p.waitFor(15, TimeUnit.SECONDS)) {
                p.destroyForcibly();
                return null;
            }
            if (p.exitValue() != 0) return null;
            String out = buf.toString("UTF-8").trim();
            if (out.isEmpty()) return null;
            // 多行时取第一行（正常同 repo:tag 只对一条；防未来 manifest list / multi-arch 场景）
            int nl = out.indexOf('\n');
            return nl < 0 ? out : out.substring(0, nl).trim();
        } catch (Throwable t) {
            return null;
        }
    }

    /** 按 SHA/ID 给镜像打 tag；成功返 true. */
    private boolean retagImage(String docker, String sha, String tag) {
        try {
            Process p = new ProcessBuilder(docker, "tag", sha, tag)
                    .redirectErrorStream(true).start();
            if (!p.waitFor(15, TimeUnit.SECONDS)) {
                p.destroyForcibly();
                return false;
            }
            return p.exitValue() == 0;
        } catch (Throwable t) {
            return false;
        }
    }

    /** 返 null = OK；非 null = 错误概要（多半为 "No such image" 类信息）. */
    private String checkImage(String docker, String image) {
        try {
            Process p = new ProcessBuilder(docker, "image", "inspect", image, "--format", "{{.Id}}")
                    .redirectErrorStream(true).start();
            java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();
            try (java.io.InputStream in = p.getInputStream()) {
                byte[] chunk = new byte[4096];
                int n;
                while ((n = in.read(chunk)) > 0) {
                    if (buf.size() < 2048) buf.write(chunk, 0, n);
                }
            }
            if (!p.waitFor(30, TimeUnit.SECONDS)) {
                p.destroyForcibly();
                return "inspect timed out after 30s";
            }
            if (p.exitValue() == 0) return null;
            String err = buf.toString("UTF-8").trim();
            return err.isEmpty() ? ("inspect exit=" + p.exitValue()) : err;
        } catch (Throwable t) {
            return t.getClass().getSimpleName() + ": " + t.getMessage();
        }
    }
}
