import { createHash } from "node:crypto";
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync, } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { getRuntimeHomeDir, port, schedulerBaseUrl } from "../config.js";
import { logger } from "../utils/logger.js";
import { loadRuntimeBinding, normalizeSchedulerBaseUrl, } from "./runtime-binding.js";
export const AWS_MCP_SERVER_NAME = "aws-mcp";
const AWS_MCP_ALLOWED_ENV_KEYS = [
    "AWS_PROJECT_NAME",
    "AWS_PROMPT",
    "AWS_ROLE_NAME",
    "AWS_SERVER_URL",
    "AWS_MCP_HTTP_URL",
    "AWS_RUNTIME_BRIDGE_BASE_URL",
    "AWS_HEARTBEAT_INTERVAL",
    "AWS_HEARTBEAT_TIMEOUT",
    "PATH",
    "HOME",
    "USERPROFILE",
    "TMP",
    "TEMP",
    "SystemRoot",
    "COMSPEC",
    "PATHEXT",
    "WINDIR",
];
function getPackageRoot() {
    const currentFile = fileURLToPath(import.meta.url);
    return path.resolve(path.dirname(currentFile), "..", "..");
}
function getBundledEntryPath(packageRoot = getPackageRoot()) {
    return path.join(getBundledMcpRoot(packageRoot), "dist", "index.js");
}
function getBundledMcpRoot(packageRoot) {
    return path.join(packageRoot, "package", "aws-client-agent-mcp");
}
function getReleasedMcpRoot() {
    return path.join(getRuntimeHomeDir(), ".aws-bridge", "mcp");
}
export function getReleasedMcpDistPath() {
    return path.join(getReleasedMcpRoot(), "dist");
}
function getReleaseManifestPath(releasedRoot = getReleasedMcpRoot()) {
    return path.join(releasedRoot, ".release.json");
}
function readPackageVersion(packageJsonPath) {
    try {
        const metadata = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
        return metadata.version || "unknown";
    }
    catch {
        return "unknown";
    }
}
function getBridgeVersion(packageRoot) {
    return readPackageVersion(path.join(packageRoot, "package.json"));
}
function getBundledMcpVersion(packageRoot) {
    return readPackageVersion(path.join(getBundledMcpRoot(packageRoot), "package.json"));
}
function copyIfExists(sourcePath, targetPath) {
    if (!existsSync(sourcePath)) {
        return;
    }
    copyFileOrDirectory(sourcePath, targetPath);
}
function copyFileOrDirectory(sourcePath, targetPath) {
    const stat = statSync(sourcePath);
    if (stat.isDirectory()) {
        cpSync(sourcePath, targetPath, {
            errorOnExist: false,
            force: true,
            recursive: true,
        });
        return;
    }
    cpSync(sourcePath, targetPath, { errorOnExist: false, force: true });
}
function syncReleasedPackageMetadata(packageRoot, releasedRoot) {
    const bundledMcpRoot = getBundledMcpRoot(packageRoot);
    copyIfExists(path.join(bundledMcpRoot, "package.json"), path.join(releasedRoot, "package.json"));
    copyIfExists(path.join(bundledMcpRoot, "README.md"), path.join(releasedRoot, "README.md"));
    copyIfExists(path.join(bundledMcpRoot, "package-lock.json"), path.join(releasedRoot, "package-lock.json"));
}
function shouldReplaceNodeModulesLink(targetPath) {
    if (!existsSync(targetPath)) {
        return true;
    }
    const stat = lstatSync(targetPath);
    return stat.isSymbolicLink() || stat.isDirectory();
}
function isExpectedNodeModulesLink(targetPath, sourcePath) {
    if (!existsSync(targetPath)) {
        return false;
    }
    try {
        return realpathSync(targetPath) === realpathSync(sourcePath);
    }
    catch {
        return false;
    }
}
function ensureReleasedNodeModules(packageRoot, releasedRoot) {
    const sourceNodeModules = path.join(packageRoot, "node_modules");
    const releasedNodeModules = path.join(releasedRoot, "node_modules");
    if (!existsSync(sourceNodeModules)) {
        logger.warn(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 未找到依赖目录，release 产物可能无法解析运行时依赖: ${sourceNodeModules}`);
        return;
    }
    if (isExpectedNodeModulesLink(releasedNodeModules, sourceNodeModules)) {
        return;
    }
    if (!shouldReplaceNodeModulesLink(releasedNodeModules)) {
        logger.warn(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 依赖路径已存在但不是目录或链接，已保留: ${releasedNodeModules}`);
        return;
    }
    rmSync(releasedNodeModules, { recursive: true, force: true });
    symlinkSync(sourceNodeModules, releasedNodeModules, process.platform === "win32" ? "junction" : "dir");
}
function listDistFiles(dirPath, rootPath = dirPath) {
    return readdirSync(dirPath, { withFileTypes: true }).flatMap((entry) => {
        const entryPath = path.join(dirPath, entry.name);
        if (entry.isDirectory()) {
            return listDistFiles(entryPath, rootPath);
        }
        if (!entry.isFile()) {
            return [];
        }
        return [path.relative(rootPath, entryPath).split(path.sep).join("/")];
    });
}
function hashDistDirectory(distPath) {
    const hash = createHash("sha256");
    for (const relativePath of listDistFiles(distPath).sort()) {
        const filePath = path.join(distPath, ...relativePath.split("/"));
        const stat = statSync(filePath);
        hash.update(relativePath);
        hash.update("\0");
        hash.update(String(stat.size));
        hash.update("\0");
        hash.update(readFileSync(filePath));
        hash.update("\0");
    }
    return `sha256:${hash.digest("hex")}`;
}
function readReleaseManifest(releasedRoot) {
    try {
        const parsed = JSON.parse(readFileSync(getReleaseManifestPath(releasedRoot), "utf-8"));
        if (typeof parsed.bridgeVersion === "string" &&
            typeof parsed.mcpVersion === "string" &&
            typeof parsed.distHash === "string") {
            return {
                bridgeVersion: parsed.bridgeVersion,
                mcpVersion: parsed.mcpVersion,
                distHash: parsed.distHash,
                releasedAt: typeof parsed.releasedAt === "string" ? parsed.releasedAt : "",
            };
        }
    }
    catch {
        return null;
    }
    return null;
}
function shouldRefreshReleasedMcp(releasedEntry, manifest, nextManifest) {
    return (!existsSync(releasedEntry) ||
        !manifest ||
        manifest.bridgeVersion !== nextManifest.bridgeVersion ||
        manifest.mcpVersion !== nextManifest.mcpVersion ||
        manifest.distHash !== nextManifest.distHash);
}
export function releaseBundledAwsClientAgentMcp(options = {}) {
    const packageRoot = options.packageRoot || getPackageRoot();
    const releasedRoot = options.releasedRoot || getReleasedMcpRoot();
    const bundledDist = path.dirname(getBundledEntryPath(packageRoot));
    const releasedDist = path.join(releasedRoot, "dist");
    const releasedEntry = path.join(releasedDist, "index.js");
    if (!existsSync(path.join(bundledDist, "index.js"))) {
        return null;
    }
    const nextManifest = {
        bridgeVersion: getBridgeVersion(packageRoot),
        mcpVersion: getBundledMcpVersion(packageRoot),
        distHash: hashDistDirectory(bundledDist),
    };
    const currentManifest = readReleaseManifest(releasedRoot);
    if (shouldRefreshReleasedMcp(releasedEntry, currentManifest, nextManifest)) {
        rmSync(releasedDist, { recursive: true, force: true });
        mkdirSync(releasedRoot, { recursive: true });
        syncReleasedPackageMetadata(packageRoot, releasedRoot);
        ensureReleasedNodeModules(packageRoot, releasedRoot);
        cpSync(bundledDist, releasedDist, {
            errorOnExist: false,
            force: true,
            recursive: true,
        });
        writeFileSync(getReleaseManifestPath(releasedRoot), `${JSON.stringify({
            ...nextManifest,
            releasedAt: options.now ? options.now() : new Date().toISOString(),
        }, null, 2)}\n`, "utf-8");
        logger.info(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 已更新到: ${releasedDist}`);
    }
    else {
        syncReleasedPackageMetadata(packageRoot, releasedRoot);
        ensureReleasedNodeModules(packageRoot, releasedRoot);
        logger.info(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 已是最新: ${releasedDist}`);
    }
    return releasedEntry;
}
function getStringEnv() {
    return Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string"));
}
function getAllowedAwsMcpEnv(baseEnv) {
    return Object.fromEntries(AWS_MCP_ALLOWED_ENV_KEYS.flatMap((key) => {
        const value = baseEnv[key];
        return typeof value === "string" && value.length > 0
            ? [[key, value]]
            : [];
    }));
}
function parseAwsClientAgentMcpArgs(raw) {
    try {
        const parsed = JSON.parse(raw);
        if (Array.isArray(parsed) &&
            parsed.every((value) => typeof value === "string")) {
            return parsed;
        }
    }
    catch (error) {
        logger.warn(`[runtime-bridge] AWS_CLIENT_AGENT_MCP_ARGS 不是合法 JSON，已忽略: ${error instanceof Error ? error.message : String(error)}`);
    }
    logger.warn("[runtime-bridge] AWS_CLIENT_AGENT_MCP_ARGS 必须是 string[] JSON，已忽略");
    return [];
}
function toWebSocketUrl(baseUrl) {
    const url = new URL("/ws/agent", normalizeSchedulerBaseUrl(baseUrl) || baseUrl);
    if (url.protocol === "https:") {
        url.protocol = "wss:";
    }
    else {
        url.protocol = "ws:";
    }
    return url.toString();
}
function toMcpHttpUrl(baseUrl) {
    return new URL("/mcp/call", normalizeSchedulerBaseUrl(baseUrl) || baseUrl).toString();
}
function resolveSchedulerBaseUrlForMcp() {
    const envSchedulerBaseUrl = String(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL || "").trim();
    if (envSchedulerBaseUrl) {
        return envSchedulerBaseUrl;
    }
    const binding = loadRuntimeBinding();
    const pairedSchedulerBaseUrl = String(binding.schedulerBaseUrl || "").trim();
    if (binding.status === "paired" && pairedSchedulerBaseUrl) {
        return pairedSchedulerBaseUrl;
    }
    return schedulerBaseUrl;
}
export function resolveAwsClientAgentMcpCommand(options = {}) {
    return getAwsClientAgentMcpPreparedInfo(options);
}
export function getAwsClientAgentMcpPreparedInfo(options = {}) {
    const overrideCommand = String(process.env.AWS_CLIENT_AGENT_MCP_COMMAND || "").trim();
    if (overrideCommand) {
        return {
            source: "override",
            command: overrideCommand,
            args: process.env.AWS_CLIENT_AGENT_MCP_ARGS
                ? parseAwsClientAgentMcpArgs(process.env.AWS_CLIENT_AGENT_MCP_ARGS)
                : [],
        };
    }
    const exists = options.exists || existsSync;
    const release = options.release || releaseBundledAwsClientAgentMcp;
    const released = release();
    if (released && exists(released)) {
        return { source: "bundled", command: process.execPath, args: [released] };
    }
    return { source: "global", command: "aws-client-agent-mcp", args: [] };
}
export function buildAwsMcpServerConfig(input) {
    const env = getStringEnv();
    const command = getAwsClientAgentMcpPreparedInfo();
    const effectiveSchedulerBaseUrl = resolveSchedulerBaseUrlForMcp();
    return {
        command: command.command,
        args: command.args,
        env: {
            ...getAllowedAwsMcpEnv(env),
            AWS_AGENT_ID: input.agentId,
            AWS_WORKSPACE_PATH: input.workspacePath,
            AWS_SERVER_URL: env.AWS_SERVER_URL || toWebSocketUrl(effectiveSchedulerBaseUrl),
            AWS_MCP_HTTP_URL: env.AWS_MCP_HTTP_URL || toMcpHttpUrl(effectiveSchedulerBaseUrl),
            AWS_RUNTIME_BRIDGE_BASE_URL: env.AWS_RUNTIME_BRIDGE_BASE_URL || `http://127.0.0.1:${port}`,
            AWS_MCP_CLAIM_LAUNCH_BINDING: env.AWS_MCP_CLAIM_LAUNCH_BINDING || "true",
        },
    };
}
export function ensureAwsClientAgentMcpReleased() {
    const prepared = getAwsClientAgentMcpPreparedInfo();
    const commandLine = prepared.args[0]
        ? `${prepared.command} ${prepared.args.join(" ")}`
        : prepared.command;
    if (prepared.source === "global") {
        logger.warn(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 未找到 bundled 产物，启动时将回退到 PATH 命令: ${commandLine}`);
        return;
    }
    logger.info(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 已准备(${prepared.source}): ${commandLine}`);
}
