import { spawnSync } from "node:child_process";
import { mkdir, stat, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { getAgentUpdateLogPath, getConnectorHealth, getConnectorHome, getConnectorLogPath, getConnectorStatePath, getSourceRoot, getUpdateLogPath, getVersionChecks, readFormattedLogTail, resolveNpmSpawnCommand, } from "./operations.js";
import { redactText } from "../core/redaction.js";
import { createZipBuffer } from "./zip-writer.js";
export async function createSupportBundle(options) {
    const createdAt = new Date();
    const health = options.health ?? await getConnectorHealth(cliPathOptions(options.config));
    const versionChecks = options.versionChecks ?? await getVersionChecks(cliPathOptions(options.config));
    const entries = [];
    addJson(entries, "manifest.json", {
        createdAt: createdAt.toISOString(),
        source: options.source ?? "web",
        package: "@nordbyte/nordrelay",
        includedFiles: [],
    });
    addJson(entries, "config/redacted-config.json", redactValue(options.config));
    addJson(entries, "config/relevant-env.json", redactValue(relevantEnvironment()));
    addJson(entries, "runtime/health.json", redactValue(health));
    addJson(entries, "runtime/version-checks.json", redactValue(versionChecks));
    addJson(entries, "runtime/state-backend.json", {
        stateBackend: options.config.stateBackend,
        stateFile: getConnectorStatePath(),
        connectorHome: getConnectorHome(),
        sourceRoot: getSourceRoot(),
        workspace: options.config.workspace,
        databasePath: health.databasePath,
    });
    addJson(entries, "runtime/agent-paths.json", agentPaths(health));
    addJson(entries, "system/info.json", systemInfo());
    if (options.diagnostics) {
        addJson(entries, "runtime/diagnostics.json", redactValue(options.diagnostics));
    }
    if (options.observability) {
        addJson(entries, "runtime/observability.json", redactValue(options.observability));
    }
    if (options.adapterHealth) {
        addJson(entries, "runtime/adapter-health.json", redactValue(options.adapterHealth));
    }
    if (options.auditEvents) {
        addJson(entries, "audit/recent-events.json", redactValue(options.auditEvents));
    }
    if (options.agentUpdateJobs) {
        addJson(entries, "updates/jobs.json", redactValue(options.agentUpdateJobs));
    }
    await addLog(entries, "logs/connector.log", getConnectorLogPath());
    await addLog(entries, "logs/nordrelay-update.log", getUpdateLogPath());
    await addLog(entries, "logs/agent-updates.log", getAgentUpdateLogPath());
    const includedFiles = entries.map((entry) => entry.name);
    entries[0] = {
        name: "manifest.json",
        data: jsonText({
            createdAt: createdAt.toISOString(),
            source: options.source ?? "web",
            package: "@nordbyte/nordrelay",
            includedFiles,
        }),
        date: createdAt,
    };
    const buffer = createZipBuffer(entries);
    const name = `nordrelay-diagnostics-${formatTimestamp(createdAt)}.zip`;
    const supportDir = path.join(getConnectorHome(), "support");
    await mkdir(supportDir, { recursive: true });
    const bundlePath = path.join(supportDir, name);
    await writeFile(bundlePath, buffer);
    const stats = await stat(bundlePath);
    return {
        path: bundlePath,
        name,
        sizeBytes: stats.size,
        createdAt: createdAt.toISOString(),
        includedFiles,
    };
}
function cliPathOptions(config) {
    return {
        piCliPath: config.piCliPath,
        hermesCliPath: config.hermesCliPath,
        openClawCliPath: config.openClawCliPath,
        claudeCodeCliPath: config.claudeCodeCliPath,
    };
}
function addJson(entries, name, value) {
    entries.push({ name, data: jsonText(value) });
}
async function addLog(entries, name, filePath) {
    const tail = await readFormattedLogTail(300, filePath);
    entries.push({
        name,
        data: [
            `File: ${tail.filePath}`,
            `Updated: ${tail.updatedAt ? tail.updatedAt.toISOString() : "-"}`,
            `Lines: ${tail.lineCount}/${tail.requestedLines}`,
            "",
            tail.plain || "(empty)",
        ].join("\n"),
    });
}
function jsonText(value) {
    return `${JSON.stringify(value, null, 2)}\n`;
}
function redactValue(value) {
    if (Array.isArray(value)) {
        return value.map((item) => redactValue(item));
    }
    if (!value || typeof value !== "object") {
        return typeof value === "string" ? redactText(value) : value;
    }
    const output = {};
    for (const [key, rawValue] of Object.entries(value)) {
        output[key] = isSecretKey(key) ? "[REDACTED]" : redactValue(rawValue);
    }
    return output;
}
function isSecretKey(key) {
    return /(token|secret|password|authorization|api[_-]?key|apikey|botToken|webhookSecret|gatewayPassword)/i.test(key);
}
function relevantEnvironment() {
    const prefixes = [
        "NORDRELAY_",
        "TELEGRAM_",
        "DISCORD_",
        "CODEX_",
        "PI_",
        "HERMES_",
        "OPENCLAW_",
        "CLAUDE_",
        "WORKSPACE_",
        "ARTIFACT_",
        "VOICE_",
        "FASTER_WHISPER_",
    ];
    const exact = new Set(["MAX_FILE_SIZE", "TOOL_VERBOSITY", "CONNECTOR_LOG_FORMAT", "NODE_ENV"]);
    return Object.fromEntries(Object.entries(process.env)
        .filter(([key, value]) => value !== undefined && (exact.has(key) || prefixes.some((prefix) => key.startsWith(prefix))))
        .sort(([left], [right]) => left.localeCompare(right))
        .map(([key, value]) => [key, value ?? ""]));
}
function agentPaths(health) {
    return {
        codex: { label: health.codexCli, path: health.codexCliPath, version: health.codexCliVersion },
        pi: { label: health.piCli, path: health.piCliPath, version: health.piCliVersion },
        hermes: { label: health.hermesCli, path: health.hermesCliPath, version: health.hermesCliVersion },
        openclaw: { label: health.openClawCli, path: health.openClawCliPath, version: health.openClawCliVersion },
        "claude-code": { label: health.claudeCodeCli, path: health.claudeCodeCliPath, version: health.claudeCodeCliVersion },
    };
}
function systemInfo() {
    const npm = resolveNpmSpawnCommand();
    return {
        os: {
            platform: os.platform(),
            release: os.release(),
            arch: os.arch(),
            type: os.type(),
            homedir: os.homedir(),
            tmpdir: os.tmpdir(),
            cpus: os.cpus().length,
            totalMemoryBytes: os.totalmem(),
            freeMemoryBytes: os.freemem(),
            uptimeSeconds: os.uptime(),
        },
        node: {
            executable: process.execPath,
            version: process.version,
            versions: process.versions,
            argv: process.argv,
            cwd: process.cwd(),
            pid: process.pid,
            uptimeSeconds: process.uptime(),
        },
        npm: npm ? {
            command: npm.display,
            version: detectNpmVersion(npm),
        } : {
            command: null,
            version: null,
            error: "npm not found",
        },
    };
}
function detectNpmVersion(npm) {
    const args = [...npm.argsPrefix, "--version"];
    const result = spawnSync(npm.shell ? formatShellCommand(npm.command, args) : npm.command, npm.shell ? [] : args, {
        encoding: "utf8",
        shell: npm.shell,
        timeout: 3000,
        windowsHide: true,
    });
    if (result.error || result.status !== 0) {
        return null;
    }
    return String(result.stdout || "").trim() || null;
}
function formatShellCommand(command, args) {
    return [command, ...args].map(quoteWindowsCmdArg).join(" ");
}
function quoteWindowsCmdArg(value) {
    if (value.length === 0) {
        return "\"\"";
    }
    if (!/[\s"&|<>()^%]/.test(value)) {
        return value;
    }
    return `"${value
        .replace(/%/g, "%%")
        .replace(/(\\*)"/g, '$1$1\\"')
        .replace(/(\\+)$/g, "$1$1")}"`;
}
function formatTimestamp(date) {
    return [
        date.getFullYear(),
        pad2(date.getMonth() + 1),
        pad2(date.getDate()),
        "-",
        pad2(date.getHours()),
        pad2(date.getMinutes()),
        pad2(date.getSeconds()),
    ].join("");
}
function pad2(value) {
    return String(value).padStart(2, "0");
}
