import { DaemonServer } from '../daemon/server.js';
import { HttpListener } from '../daemon/http-listener.js';
import { logger } from '../utils/logger.js';
import net from 'node:net';
import { summarizeError } from '../utils/error-display.js';
const DEFAULT_SERVICE_START_TIMEOUT_MS = 1500;
async function isTcpPortInUse(host, port, timeoutMs = 250) {
    return await new Promise((resolve) => {
        const socket = new net.Socket();
        let settled = false;
        const finish = (result) => {
            if (settled)
                return;
            settled = true;
            socket.destroy();
            resolve(result);
        };
        socket.setTimeout(timeoutMs);
        socket.once('connect', () => finish(true));
        socket.once('timeout', () => finish(false));
        socket.once('error', () => finish(false));
        socket.connect(port, host);
    });
}
async function startWithTimeout(label, start, timeoutMs, cleanup) {
    let timer = null;
    const startPromise = start().then(() => 'started');
    try {
        const result = await Promise.race([
            startPromise,
            new Promise((resolve) => {
                timer = setTimeout(() => resolve('timed_out'), timeoutMs);
            }),
        ]);
        if (result === 'timed_out') {
            logger.warn(`${label} startup timed out; continuing without it in this host instance`, { timeoutMs });
            if (cleanup) {
                void cleanup().catch((error) => {
                    logger.warn(`${label} cleanup after startup timeout failed`, { error: summarizeError(error) });
                });
                void startPromise.then(() => cleanup()).catch(() => { });
            }
        }
        return result;
    }
    finally {
        if (timer !== null)
            clearTimeout(timer);
    }
}
export async function startHostServices(config, runtimeBus, hookDispatcher, factories = {}, runtimeServices) {
    const sharedUserAuth = runtimeServices.localUserAuthManager;
    const createDaemonServer = factories.createDaemonServer ?? ((bus, userAuth, services) => new DaemonServer({ runtimeBus: bus, userAuth, runtimeServices: services }));
    const createHttpListener = factories.createHttpListener ?? ((dispatcher, userAuth, configManager) => new HttpListener({ hookDispatcher: dispatcher, userAuth, configManager }));
    const startupTimeoutMs = factories.startupTimeoutMs ?? DEFAULT_SERVICE_START_TIMEOUT_MS;
    const daemonHost = String(config.get('controlPlane.host') ?? '127.0.0.1');
    const daemonPort = Number(config.get('controlPlane.port') ?? 3421);
    const httpListenerHost = String(config.get('httpListener.host') ?? '127.0.0.1');
    const httpListenerPort = Number(config.get('httpListener.port') ?? 3422);
    const probeDaemonPortInUse = factories.probeDaemonPortInUse ?? ((host, port) => isTcpPortInUse(host, port));
    const probeHttpListenerPortInUse = factories.probeHttpListenerPortInUse ?? ((host, port) => isTcpPortInUse(host, port));
    let daemonServer = null;
    let httpListener = null;
    if (config.get('danger.daemon')) {
        if (await probeDaemonPortInUse(daemonHost, daemonPort)) {
            logger.warn('Daemon server port already in use; continuing without local daemon in this host instance', {
                host: daemonHost,
                port: daemonPort,
            });
        }
        else {
            daemonServer = createDaemonServer(runtimeBus, sharedUserAuth, runtimeServices);
            daemonServer.enable({ daemon: true }, factories.sharedDaemonToken);
            try {
                const service = daemonServer;
                const result = await startWithTimeout('Daemon server', () => service.start(), startupTimeoutMs, () => service.stop());
                if (result === 'timed_out') {
                    daemonServer = null;
                }
            }
            catch (error) {
                const message = summarizeError(error);
                if (message.includes('EADDRINUSE') || message.includes('Address already in use')) {
                    logger.warn('Daemon server port already in use; continuing without local daemon in this host instance', { error: message });
                    daemonServer = null;
                }
                else {
                    throw error;
                }
            }
        }
    }
    if (config.get('danger.httpListener')) {
        if (await probeHttpListenerPortInUse(httpListenerHost, httpListenerPort)) {
            logger.warn('HTTP listener port already in use; continuing without local listener in this host instance', {
                host: httpListenerHost,
                port: httpListenerPort,
            });
        }
        else {
            httpListener = createHttpListener(hookDispatcher, sharedUserAuth, runtimeServices.configManager);
            httpListener.enable({ httpListener: true }, factories.sharedHttpListenerToken);
            try {
                const service = httpListener;
                const result = await startWithTimeout('HTTP listener', () => service.start(), startupTimeoutMs, () => service.stop());
                if (result === 'timed_out') {
                    httpListener = null;
                }
            }
            catch (error) {
                const message = summarizeError(error);
                if (message.includes('EADDRINUSE') || message.includes('Address already in use')) {
                    logger.warn('HTTP listener port already in use; continuing without local listener in this host instance', { error: message });
                    httpListener = null;
                }
                else {
                    throw error;
                }
            }
        }
    }
    return {
        daemonServer,
        httpListener,
        listRecentControlPlaneEvents(limit) {
            return daemonServer?.listRecentControlPlaneEvents(limit) ?? [];
        },
        async stop() {
            await Promise.allSettled([
                daemonServer?.stop(),
                httpListener?.stop(),
            ]);
        },
    };
}
export const startExternalServices = startHostServices;
