import { createServer } from "node:http";
import { mkdir, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { webhookCallback } from "grammy";
import { agentLabel } from "./agents/shared/agent.js";
import { createBot, registerCommands } from "./channels/telegram/bot.js";
import { createDiscordBridge } from "./channels/discord/discord-bot.js";
import { startDiscordBridgeOrDisable } from "./channels/discord/discord-startup.js";
import { createSlackBridge } from "./channels/slack/slack-bot.js";
import { createMatrixBridge } from "./channels/matrix/matrix-bot.js";
import { checkAuthStatus } from "./agents/codex/codex-auth.js";
import { describeCodexCli, resolveCodexCli } from "./agents/codex/codex-cli.js";
import { checkClaudeCodeAuthStatus } from "./agents/claude-code/claude-code-auth.js";
import { describeClaudeCodeCli, resolveClaudeCodeCli } from "./agents/claude-code/claude-code-cli.js";
import { findLaunchProfile, formatLaunchProfileBehavior } from "./agents/codex/codex-launch.js";
import { enabledAgents } from "./agents/shared/agent-factory.js";
import { loadConfig } from "./core/config.js";
import { checkHermesAuthStatus } from "./agents/hermes/hermes-auth.js";
import { describeHermesCli, resolveHermesCli } from "./agents/hermes/hermes-cli.js";
import { checkOpenClawAuthStatus } from "./agents/openclaw/openclaw-auth.js";
import { describeOpenClawCli, resolveOpenClawCli } from "./agents/openclaw/openclaw-cli.js";
import { installConsoleLogger } from "./core/logger.js";
import { friendlyErrorText } from "./core/error-messages.js";
import { resolveDashboardEnvPath, SettingsService } from "./core/settings-service.js";
import { checkPiAuthStatus } from "./agents/pi/pi-auth.js";
import { describePiCli, resolvePiCli } from "./agents/pi/pi-cli.js";
import { startPeerHealthMonitor } from "./peers/peer-health-monitor.js";
import { startPeerOutboundRelay } from "./peers/peer-outbound-relay.js";
import { startPeerServer } from "./peers/peer-server.js";
import { RelayRuntime } from "./runtime/relay-runtime.js";
import { configureRedaction } from "./core/redaction.js";
import { SessionRegistry } from "./state/session-registry.js";
import { UserStore } from "./access/user-management.js";
import { createSessionWorktreeStore, SessionWorktreeService } from "./worktrees/worktree-service.js";
let registry;
let bot;
let discordBridge;
let slackBridge;
let matrixBridge;
let webhookServer;
let peerServer;
let peerHealthMonitor;
let peerOutboundRelay;
let peerRuntime;
let runtimeConfig;
try {
    const config = loadConfig();
    const home = process.env.NORDRELAY_HOME || path.join(os.homedir(), ".nordrelay");
    runtimeConfig = config;
    configureRedaction(config.telegramRedactPatterns);
    installConsoleLogger(config.logFormat);
    const worktreeService = new SessionWorktreeService(config, createSessionWorktreeStore(config));
    registry = new SessionRegistry(config, { worktreeService });
    if (config.telegramEnabled) {
        bot = createBot(config, registry);
        await registerCommands(bot);
    }
    const requestedDiscordEnabled = config.discordEnabled;
    discordBridge = createDiscordBridge(config, registry);
    discordBridge = await startDiscordBridgeOrDisable(config, discordBridge);
    if (requestedDiscordEnabled && !config.discordEnabled) {
        try {
            await persistDiscordDisabledSetting();
        }
        catch (error) {
            config.adapterWarnings = [...(config.adapterWarnings ?? []), `Failed to persist DISCORD_ENABLED=false: ${friendlyErrorText(error)}`];
        }
    }
    slackBridge = createSlackBridge(config, registry);
    await slackBridge?.start();
    matrixBridge = createMatrixBridge(config, registry);
    await matrixBridge?.start();
    if (config.peerEnabled) {
        peerRuntime = new RelayRuntime(config, { home });
        peerServer = await startPeerServer({ config, runtime: peerRuntime });
    }
    if (config.peerOutboundRelayEnabled) {
        peerRuntime ??= new RelayRuntime(config, { home });
        peerOutboundRelay = startPeerOutboundRelay({ config, runtime: peerRuntime });
    }
    peerHealthMonitor = startPeerHealthMonitor({ config });
    console.log("NordRelay running");
    const userStore = new UserStore();
    if (userStore.hasAdminUser()) {
        console.log("User management: admin user configured");
    }
    else {
        console.warn("Warning: no NordRelay admin user exists. Run `nordrelay user create-admin` to enable WebUI and chat-adapter access.");
    }
    const authStatus = await checkDefaultAgentAuth(config);
    console.log(`Auth (${agentLabel(config.defaultAgent)}): ${authStatus.authenticated ? "authenticated" : "not authenticated"} (${authStatus.method})`);
    if (!authStatus.authenticated) {
        console.warn(`Warning: ${agentLabel(config.defaultAgent)} is not authenticated. ${authStatus.detail}`);
    }
    for (const warning of config.adapterWarnings ?? []) {
        console.warn(`Warning: ${warning}`);
    }
    console.log(`Workspace: ${config.workspace}`);
    console.log(`Enabled agents: ${enabledAgents(config).join(", ")} (default: ${config.defaultAgent})`);
    if (config.codexModel) {
        console.log(`Default model: ${config.codexModel}`);
    }
    const codexCli = resolveCodexCli();
    const piCli = resolvePiCli(process.env, config.piCliPath);
    const hermesCli = resolveHermesCli(process.env, config.hermesCliPath);
    const openClawCli = resolveOpenClawCli(process.env, config.openClawCliPath);
    const claudeCodeCli = resolveClaudeCodeCli(process.env, config.claudeCodeCliPath);
    console.log(`Codex CLI: ${describeCodexCli(codexCli)}`);
    console.log(`Pi CLI: ${describePiCli(piCli)}`);
    console.log(`Hermes CLI: ${describeHermesCli(hermesCli)}`);
    console.log(`OpenClaw CLI: ${describeOpenClawCli(openClawCli)}`);
    console.log(`Claude Code CLI: ${describeClaudeCodeCli(claudeCodeCli)}`);
    if (config.hermesEnabled) {
        console.log(`Hermes API: ${config.hermesApiBaseUrl}`);
    }
    if (config.openClawEnabled) {
        console.log(`OpenClaw Gateway: ${config.openClawGatewayUrl}`);
    }
    const defaultLaunchProfile = findLaunchProfile(config.launchProfiles, config.defaultLaunchProfileId);
    if (defaultLaunchProfile) {
        console.log(`Default launch profile: ${defaultLaunchProfile.label} (${formatLaunchProfileBehavior(defaultLaunchProfile)})`);
        if (defaultLaunchProfile.unsafe) {
            console.warn("Warning: Default launch profile uses danger-full-access.");
        }
    }
    console.log("Session mode: per chat context");
    console.log(`WebUI: ${config.webuiEnabled ? "enabled" : "disabled"}`);
    console.log(`Telegram: ${config.telegramEnabled ? config.telegramTransport : "disabled"}`);
    console.log(`Discord: ${config.discordEnabled ? "enabled" : "disabled"}`);
    console.log(`Slack: ${config.slackEnabled ? (config.slackSocketMode ? "socket-mode" : `http:${config.slackPort}`) : "disabled"}`);
    console.log(`Matrix: ${config.matrixEnabled ? config.matrixHomeserverUrl : "disabled"}`);
    console.log(`Peers: ${peerServer ? peerServer.url : "disabled"}`);
    console.log(`Peer outbound relay: ${peerOutboundRelay ? "enabled" : "disabled"}`);
    await writeConnectorState({
        status: "ready",
        pid: Number(process.env.NORDRELAY_WRAPPER_PID) || process.pid,
        appPid: process.pid,
        workspace: config.workspace,
        sessionMode: "per chat context",
        authenticated: authStatus.authenticated,
        authMethod: authStatus.method,
        codexCli: describeCodexCli(codexCli),
        piCli: describePiCli(piCli),
        hermesCli: describeHermesCli(hermesCli),
        openClawCli: describeOpenClawCli(openClawCli),
        claudeCodeCli: describeClaudeCodeCli(claudeCodeCli),
        openClawGateway: config.openClawGatewayUrl,
        telegramTransport: config.telegramTransport,
        discordEnabled: config.discordEnabled,
        slackEnabled: config.slackEnabled,
        matrixEnabled: config.matrixEnabled,
        peerEnabled: config.peerEnabled,
        peerOutboundRelayEnabled: config.peerOutboundRelayEnabled,
        webuiEnabled: config.webuiEnabled,
        peerUrl: peerServer?.url,
        peerTlsFingerprint: peerServer?.tlsFingerprint,
        adapterWarnings: config.adapterWarnings ?? [],
    });
}
catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    console.error(`Failed to start NordRelay: ${message}`);
    await writeConnectorState({
        status: "error",
        pid: Number(process.env.NORDRELAY_WRAPPER_PID) || process.pid,
        appPid: process.pid,
        error: message,
    });
    registry?.disposeAll();
    process.exit(1);
}
async function checkDefaultAgentAuth(config) {
    const agentId = config.defaultAgent;
    if (agentId === "pi") {
        return checkPiAuthStatus(config.piDefaultModel);
    }
    if (agentId === "hermes") {
        return checkHermesAuthStatus({
            baseUrl: config.hermesApiBaseUrl,
            apiKey: config.hermesApiKey,
        });
    }
    if (agentId === "openclaw") {
        return checkOpenClawAuthStatus({
            gatewayUrl: config.openClawGatewayUrl,
            token: config.openClawGatewayToken,
            password: config.openClawGatewayPassword,
        });
    }
    if (agentId === "claude-code") {
        return checkClaudeCodeAuthStatus(config.claudeCodeCliPath);
    }
    return checkAuthStatus(config.codexApiKey);
}
async function persistDiscordDisabledSetting() {
    const home = process.env.NORDRELAY_HOME || path.join(os.homedir(), ".nordrelay");
    const result = await new SettingsService(resolveDashboardEnvPath(home)).update({ DISCORD_ENABLED: "false" });
    if (result.errors.length > 0) {
        throw new Error(result.errors.map((error) => `${error.key}: ${error.message}`).join("; "));
    }
}
let shuttingDown = false;
const shutdown = (signal) => {
    if (shuttingDown) {
        return;
    }
    shuttingDown = true;
    console.log(`Received ${signal}, shutting down NordRelay...`);
    if (bot && runtimeConfig?.telegramTransport !== "webhook")
        bot.stop();
    void discordBridge?.stop().catch((error) => {
        console.warn("Failed to stop Discord bridge:", error instanceof Error ? error.message : String(error));
    });
    void slackBridge?.stop().catch((error) => {
        console.warn("Failed to stop Slack bridge:", error instanceof Error ? error.message : String(error));
    });
    void matrixBridge?.stop().catch((error) => {
        console.warn("Failed to stop Matrix bridge:", error instanceof Error ? error.message : String(error));
    });
    webhookServer?.close();
    void peerServer?.close().catch((error) => {
        console.warn("Failed to stop peer server:", error instanceof Error ? error.message : String(error));
    });
    peerHealthMonitor?.close();
    peerOutboundRelay?.close();
    setTimeout(() => {
        registry?.disposeAll();
        peerRuntime?.dispose();
        void writeConnectorState({
            status: "stopped",
            pid: Number(process.env.NORDRELAY_WRAPPER_PID) || process.pid,
            appPid: process.pid,
            signal,
        }).finally(() => {
            console.log("NordRelay stopped.");
            process.exit(0);
        });
    }, 500);
};
process.once("SIGINT", () => shutdown("SIGINT"));
process.once("SIGTERM", () => shutdown("SIGTERM"));
const MAX_RESTART_ATTEMPTS = 5;
const RESTART_DELAY_MS = 3000;
let restartAttempts = 0;
async function startPolling() {
    try {
        await bot.start({
            drop_pending_updates: process.env.NORDRELAY_DROP_PENDING_UPDATES !== "0",
            onStart: () => {
                restartAttempts = 0;
            },
        });
    }
    catch (error) {
        if (shuttingDown) {
            return;
        }
        const message = error instanceof Error ? error.message : String(error);
        const is409 = message.includes("409") || message.includes("Conflict");
        if (is409 && restartAttempts < MAX_RESTART_ATTEMPTS) {
            restartAttempts += 1;
            console.warn(`Polling error (attempt ${restartAttempts}/${MAX_RESTART_ATTEMPTS}): ${message}`);
            console.warn(`Restarting polling in ${RESTART_DELAY_MS / 1000}s...`);
            await new Promise((resolve) => setTimeout(resolve, RESTART_DELAY_MS));
            return startPolling();
        }
        console.error(`Fatal polling error: ${message}`);
        registry?.disposeAll();
        process.exit(1);
    }
}
if (registry && bot) {
    if (runtimeConfig?.telegramTransport === "webhook") {
        webhookServer = await startWebhook(bot, runtimeConfig);
    }
    else {
        await startPolling();
    }
}
async function startWebhook(activeBot, config) {
    const callback = webhookCallback(activeBot, "http", {
        secretToken: config.telegramWebhookSecret,
    });
    const server = createServer((req, res) => {
        if (req.method === "GET" && req.url === "/healthz") {
            res.writeHead(200, { "content-type": "text/plain" });
            res.end("ok\n");
            return;
        }
        if (req.url?.split("?")[0] !== config.telegramWebhookPath) {
            res.writeHead(404, { "content-type": "text/plain" });
            res.end("not found\n");
            return;
        }
        void callback(req, res);
    });
    await activeBot.api.setWebhook(joinWebhookUrl(config.telegramWebhookUrl, config.telegramWebhookPath), {
        secret_token: config.telegramWebhookSecret,
        drop_pending_updates: process.env.NORDRELAY_DROP_PENDING_UPDATES !== "0",
    });
    await new Promise((resolve) => {
        server.listen(config.telegramWebhookPort, config.telegramWebhookHost, resolve);
    });
    console.log(`Webhook listening on ${config.telegramWebhookHost}:${config.telegramWebhookPort}${config.telegramWebhookPath}`);
    return server;
}
function joinWebhookUrl(baseUrl, webhookPath) {
    return `${baseUrl.replace(/\/+$/, "")}${webhookPath.startsWith("/") ? webhookPath : `/${webhookPath}`}`;
}
async function writeConnectorState(payload) {
    const stateFile = process.env.NORDRELAY_STATE_FILE;
    if (!stateFile) {
        return;
    }
    try {
        await mkdir(path.dirname(stateFile), { recursive: true });
        await writeFile(stateFile, `${JSON.stringify({ ...payload, updatedAt: new Date().toISOString() }, null, 2)}\n`, "utf8");
    }
    catch (error) {
        console.warn("Failed to write connector state:", error instanceof Error ? error.message : String(error));
    }
}
