import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
import { jsonErrorResponse } from '@pellux/goodvibes-sdk/platform/daemon/http/error-response';
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
import { AgentManager } from '../tools/agent/index.js';
import {} from '../runtime/remote/index.js';
import { isSurfaceDeliveryEnabled } from './surface-policy.js';
import { configureDaemonSessionContinuation, createDaemonFacadeCollaborators, resolveDaemonFacadeRuntime, } from './facade-composition.js';
import { GlobalNetworkTransportInstaller, resolveInboundTlsContext, } from '../runtime/network/index.js';
import { createRuntimeServices } from '../runtime/services.js';
import { readAutomationReasoningEffort, readAutomationWakeMode, readExternalContentSource, readStringList, } from './helpers.js';
import { requirePortAvailable } from './port-check.js';
// ---------------------------------------------------------------------------
// DaemonServer
// ---------------------------------------------------------------------------
/**
 * DaemonServer — HTTP task server, disabled by default.
 *
 * Enable via: danger.daemon = true in config.
 * All routes require Bearer token auth (set via enable()).
 * POST /task    — submit a task; returns agentId.
 * GET  /task/:id — returns agent status.
 * GET  /status  — server health check.
 */
export class DaemonServer {
    config;
    enabled = false;
    server = null;
    port;
    host;
    agentManager;
    runtimeServices;
    integrationHelpers;
    configManager;
    authToken = null;
    userAuth;
    githubWebhookSecret;
    automationManager;
    runtimeBus;
    runtimeStore;
    runtimeDispatch;
    controlPlaneGateway;
    gatewayMethods;
    sessionBroker;
    approvalBroker;
    routeBindings;
    deliveryManager;
    surfaceRegistry;
    channelPolicy;
    channelPlugins;
    channelReplyPipeline;
    providerRuntime;
    builtinChannels;
    watcherRegistry;
    platformServiceManager;
    distributedRuntime;
    voiceService;
    webSearchService;
    knowledgeService;
    knowledgeGraphqlService;
    mediaProviders;
    multimodalService;
    artifactStore;
    serviceRegistry;
    serveFactory;
    pendingSurfaceReplies = new Map();
    controlPlaneHelper;
    surfaceDeliveryHelper;
    surfaceActionHelper;
    transportEventsHelper;
    httpRouter;
    replyPoller = null;
    tlsState = null;
    approvalBrokerUnsubscribe = null;
    constructor(config = {}, _configManager) {
        this.config = config;
        const resolved = resolveDaemonFacadeRuntime(config, _configManager);
        this.configManager = resolved.configManager;
        this.runtimeServices = resolved.runtimeServices;
        this.integrationHelpers = resolved.integrationHelpers;
        this.port = resolved.port;
        this.host = resolved.host;
        this.agentManager = resolved.agentManager;
        this.userAuth = resolved.userAuth;
        this.automationManager = resolved.automationManager;
        this.runtimeBus = resolved.runtimeBus;
        this.runtimeStore = resolved.runtimeStore;
        this.runtimeDispatch = resolved.runtimeDispatch;
        this.controlPlaneGateway = resolved.controlPlaneGateway;
        this.gatewayMethods = resolved.gatewayMethods;
        this.sessionBroker = resolved.sessionBroker;
        this.approvalBroker = resolved.approvalBroker;
        this.routeBindings = resolved.routeBindings;
        this.deliveryManager = resolved.deliveryManager;
        this.surfaceRegistry = resolved.surfaceRegistry;
        this.channelPolicy = resolved.channelPolicy;
        this.channelPlugins = resolved.channelPlugins;
        this.watcherRegistry = resolved.watcherRegistry;
        this.platformServiceManager = resolved.platformServiceManager;
        this.distributedRuntime = resolved.distributedRuntime;
        this.voiceService = resolved.voiceService;
        this.webSearchService = resolved.webSearchService;
        this.knowledgeService = resolved.knowledgeService;
        this.knowledgeGraphqlService = resolved.knowledgeGraphqlService;
        this.mediaProviders = resolved.mediaProviders;
        this.multimodalService = resolved.multimodalService;
        this.artifactStore = resolved.artifactStore;
        this.serviceRegistry = resolved.serviceRegistry;
        this.serveFactory = resolved.serveFactory;
        this.githubWebhookSecret = resolved.githubWebhookSecret;
        const collaborators = createDaemonFacadeCollaborators({
            runtime: resolved,
            pendingSurfaceReplies: this.pendingSurfaceReplies,
            authToken: () => this.authToken,
            trustProxyEnabled: () => this.trustProxyEnabled(),
            dispatchApiRoutes: (req) => this.dispatchApiRoutes(req),
            parseJsonBody: (req) => this.parseJsonBody(req),
            requireAuthenticatedSession: (req) => this.requireAuthenticatedSession(req),
            trySpawnAgent: (input, logLabel, sessionId) => this.trySpawnAgent(input, logLabel, sessionId),
            checkAuth: (req) => this.checkAuth(req),
            extractAuthToken: (req) => this.extractAuthToken(req),
            requireAdmin: (req) => this.requireAdmin(req),
            requireRemotePeer: (req, scope) => this.requireRemotePeer(req, scope),
            describeAuthenticatedPrincipal: (token) => this.describeAuthenticatedPrincipal(token),
            invokeGatewayMethodCall: (input) => this.invokeGatewayMethodCall(input),
            syncSpawnedAgentTask: (record, sessionId) => this.syncSpawnedAgentTask(record, sessionId),
            syncFinishedAgentTask: (record) => this.syncFinishedAgentTask(record),
            surfaceDeliveryEnabled: (surface) => this.surfaceDeliveryEnabled(surface),
            signWebhookPayload: (body, secret) => this.signWebhookPayload(body, secret),
            handleApprovalAction: (approvalId, action, req) => this.handleApprovalAction(approvalId, action, req),
            tlsState: () => this.tlsState,
        });
        this.channelReplyPipeline = collaborators.channelReplyPipeline;
        this.controlPlaneHelper = collaborators.controlPlaneHelper;
        this.surfaceDeliveryHelper = collaborators.surfaceDeliveryHelper;
        this.surfaceActionHelper = collaborators.surfaceActionHelper;
        this.transportEventsHelper = collaborators.transportEventsHelper;
        this.httpRouter = collaborators.httpRouter;
        this.providerRuntime = collaborators.providerRuntime;
        this.builtinChannels = collaborators.builtinChannels;
        configureDaemonSessionContinuation({
            sessionBroker: this.sessionBroker,
            trySpawnAgent: (input, logLabel, sessionId) => this.trySpawnAgent(input, logLabel, sessionId),
            queueSurfaceReplyFromBinding: (binding, input) => this.surfaceDeliveryHelper.queueSurfaceReplyFromBinding(binding, input),
        });
        this.distributedRuntime.attachRuntime({
            sessionBridge: this.sessionBroker,
            approvalBridge: this.approvalBroker,
            automationBridge: this.automationManager,
            eventPublisher: (event, payload) => {
                this.controlPlaneGateway.publishEvent(event, payload);
            },
        });
    }
    listRecentControlPlaneEvents(limit = 100) {
        return this.controlPlaneGateway.listRecentEvents(limit);
    }
    /**
     * Enable the daemon. Requires danger.daemon = true in config.
     * The provided token is used to authenticate all incoming requests.
     * Returns true if enabled, false if the config forbids it.
     */
    enable(dangerConfig, token) {
        if (!dangerConfig.daemon) {
            logger.info('DaemonServer.enable: danger.daemon is false — not enabling');
            return false;
        }
        this.enabled = true;
        this.authToken = token ?? null;
        this.controlPlaneGateway.setServerState({ enabled: true, host: this.host, port: this.port });
        return true;
    }
    /**
     * Start the daemon. Refuses to start if not explicitly enabled.
     */
    async start() {
        if (!this.enabled) {
            logger.info('Daemon mode is disabled. Enable via danger.daemon config.');
            return;
        }
        if (this.authToken === null) {
            logger.info('DaemonServer: starting with session-based authentication via UserAuth');
        }
        if (this.server !== null) {
            logger.info('DaemonServer: already running');
            return;
        }
        new GlobalNetworkTransportInstaller().install(this.configManager);
        if (!this.approvalBrokerUnsubscribe) {
            this.approvalBrokerUnsubscribe = this.approvalBroker.subscribe((approval) => {
                void this.surfaceDeliveryHelper.notifyApprovalUpdate(approval);
            });
        }
        this.routeBindings.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
        });
        this.surfaceRegistry.attachRuntime(this.runtimeStore);
        this.deliveryManager.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
        });
        this.automationManager.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
            deliveryManager: this.deliveryManager,
        });
        this.controlPlaneGateway.attachRuntime({
            runtimeBus: this.runtimeBus,
            runtimeStore: this.runtimeStore,
        });
        const self = this;
        await requirePortAvailable(this.port, this.host, 'daemon');
        this.transportEventsHelper.emitTransportInitializing();
        try {
            this.tlsState = resolveInboundTlsContext(this.configManager, 'controlPlane');
            this.server = this.serveFactory({
                port: this.port,
                hostname: this.host,
                ...(this.tlsState.tls ? { tls: this.tlsState.tls } : {}),
                async fetch(req, server) {
                    const upgrade = self.tryUpgradeControlPlaneWebSocket(req, server);
                    if (upgrade === 'upgraded')
                        return;
                    if (upgrade)
                        return upgrade;
                    return self.handleRequest(req);
                },
                websocket: {
                    open(ws) {
                        self.handleControlPlaneWebSocketOpen(ws);
                    },
                    message(ws, message) {
                        void self.handleControlPlaneWebSocketMessage(ws, message);
                    },
                    close(ws) {
                        self.handleControlPlaneWebSocketClose(ws);
                    },
                },
            });
            await Promise.all([
                this.sessionBroker.start(),
                this.approvalBroker.start(),
                this.channelPolicy.start(),
                this.automationManager.start(),
                this.distributedRuntime.start(),
            ]);
            await this.providerRuntime.startConfigured();
            if (this.replyPoller === null) {
                this.replyPoller = setInterval(() => {
                    void this.pollPendingSurfaceReplies();
                }, 2_000);
            }
            this.surfaceRegistry.syncConfiguredSurfaces();
            if (this.configManager.get('watchers.enabled')) {
                this.watcherRegistry.registerPollingWatcher({
                    id: 'daemon-heartbeat',
                    label: 'Daemon heartbeat',
                    source: {
                        id: 'source:daemon-heartbeat',
                        kind: 'watcher',
                        label: 'Daemon heartbeat',
                        enabled: true,
                        createdAt: Date.now(),
                        updatedAt: Date.now(),
                        metadata: {},
                    },
                    intervalMs: Number(this.configManager.get('watchers.heartbeatIntervalMs') ?? 30_000),
                    run: () => new Date().toISOString(),
                });
                this.watcherRegistry.startWatcher('daemon-heartbeat');
            }
            this.controlPlaneGateway.setServerState({ enabled: true, host: this.host, port: this.port });
            this.transportEventsHelper.emitTransportConnected();
            logger.info('DaemonServer started', {
                port: this.port,
                host: this.host,
                tlsMode: this.tlsState.mode,
                scheme: this.tlsState.scheme,
                trustProxy: this.tlsState.trustProxy,
            });
        }
        catch (err) {
            const message = summarizeError(err);
            if (this.replyPoller !== null) {
                clearInterval(this.replyPoller);
                this.replyPoller = null;
            }
            this.pendingSurfaceReplies.clear();
            this.automationManager.stop();
            this.providerRuntime.stopAll();
            this.watcherRegistry.stopWatcher('daemon-heartbeat', 'daemon-start-failed');
            this.approvalBrokerUnsubscribe?.();
            this.approvalBrokerUnsubscribe = null;
            if (this.server !== null) {
                this.server.stop(true);
                this.server = null;
            }
            this.tlsState = null;
            this.controlPlaneGateway.setServerState({ enabled: this.enabled, host: this.host, port: this.port });
            this.transportEventsHelper.emitTransportTerminalFailure(message);
            throw err;
        }
    }
    /**
     * Stop the daemon server.
     *
     * Services are stopped in reverse start order. Each service stop is raced
     * against a 10-second timeout so a hung service cannot block the full
     * shutdown sequence (C1 fix).
     */
    async stop() {
        if (this.server === null)
            return;
        // Synchronous pre-stop teardown
        this.watcherRegistry.stopWatcher('daemon-heartbeat', 'daemon-stopped');
        if (this.replyPoller !== null) {
            clearInterval(this.replyPoller);
            this.replyPoller = null;
        }
        this.pendingSurfaceReplies.clear();
        this.approvalBrokerUnsubscribe?.();
        this.approvalBrokerUnsubscribe = null;
        this.httpRouter.dispose();
        // Stop services that expose async teardown. Note: sessionBroker, approvalBroker,
        // channelPolicy, and distributedRuntime expose start() only — their lifecycle ends
        // when the server socket closes. We stop what we can in reverse start order.
        this.providerRuntime.stopAll();
        this.automationManager.stop();
        this.server.stop(true);
        this.server = null;
        this.tlsState = null;
        this.controlPlaneGateway.setServerState({ enabled: this.enabled, host: this.host, port: this.port });
        this.transportEventsHelper.emitTransportDisconnected('Daemon server stopped', false);
        logger.info('DaemonServer stopped');
    }
    /**
     * Returns true if the server is currently running.
     */
    get isRunning() {
        return this.server !== null;
    }
    // -------------------------------------------------------------------------
    // Auth
    // -------------------------------------------------------------------------
    extractAuthToken(req) {
        return this.controlPlaneHelper.extractAuthToken(req);
    }
    checkAuth(req) {
        return this.controlPlaneHelper.checkAuth(req);
    }
    requireAuthenticatedSession(req) {
        return this.controlPlaneHelper.requireAuthenticatedSession(req);
    }
    requireAdmin(req) {
        return this.controlPlaneHelper.requireAdmin(req);
    }
    async requireRemotePeer(req, scope) {
        return await this.controlPlaneHelper.requireRemotePeer(req, scope);
    }
    describeAuthenticatedPrincipal(token) {
        return this.controlPlaneHelper.describeAuthenticatedPrincipal(token);
    }
    getGrantedGatewayScopes(includeWrite) {
        return this.controlPlaneHelper.getGrantedGatewayScopes(includeWrite);
    }
    validateGatewayInvocation(descriptor, context) {
        return this.controlPlaneHelper.validateGatewayInvocation(descriptor, context);
    }
    tryUpgradeControlPlaneWebSocket(req, server) {
        return this.controlPlaneHelper.tryUpgradeControlPlaneWebSocket(req, server);
    }
    handleControlPlaneWebSocketOpen(ws) {
        this.controlPlaneHelper.handleControlPlaneWebSocketOpen(ws);
    }
    async handleControlPlaneWebSocketMessage(ws, message) {
        await this.controlPlaneHelper.handleControlPlaneWebSocketMessage(ws, message);
    }
    handleControlPlaneWebSocketClose(ws) {
        this.controlPlaneHelper.handleControlPlaneWebSocketClose(ws);
    }
    async invokeWebSocketControlPlaneCall(input) {
        return await this.controlPlaneHelper.invokeWebSocketControlPlaneCall(input);
    }
    async invokeGatewayMethodCall(input) {
        return await this.controlPlaneHelper.invokeGatewayMethodCall(input);
    }
    // -------------------------------------------------------------------------
    // Request handling
    // -------------------------------------------------------------------------
    async handleRequest(req) {
        return await this.httpRouter.handleRequest(req);
    }
    async dispatchApiRoutes(req) {
        return await this.httpRouter.dispatchApiRoutes(req);
    }
    async parseJsonBody(req) {
        return await this.httpRouter.parseJsonBody(req);
    }
    async parseOptionalJsonBody(req) {
        return await this.httpRouter.parseOptionalJsonBody(req);
    }
    parseJsonText(rawBody) {
        return this.httpRouter.parseJsonText(rawBody);
    }
    recordApiResponse(req, path, response, clientKind = 'web') {
        return this.httpRouter.recordApiResponse(req, path, response, clientKind);
    }
    async handleApprovalAction(approvalId, action, req) {
        const body = await this.parseOptionalJsonBody(req);
        const payload = body instanceof Response || body === null ? {} : body;
        const actor = this.requireAuthenticatedSession(req)?.username ?? (this.authToken ? 'shared-token' : 'operator');
        const note = typeof payload.note === 'string' ? payload.note : undefined;
        if (action === 'claim') {
            const approval = await this.approvalBroker.claimApproval(approvalId, actor, 'web', note);
            return approval
                ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
                : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
        }
        if (action === 'cancel') {
            const approval = await this.approvalBroker.cancelApproval(approvalId, actor, 'web', note);
            return approval
                ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
                : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
        }
        const approval = await this.approvalBroker.resolveApproval(approvalId, {
            approved: action === 'approve',
            remember: typeof payload.remember === 'boolean' ? payload.remember : false,
            actor,
            actorSurface: 'web',
            note,
        });
        return approval
            ? this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ approval }))
            : this.recordApiResponse(req, `/api/approvals/${approvalId}/${action}`, Response.json({ error: 'Unknown approval' }, { status: 404 }));
    }
    trySpawnAgent(input, logLabel = 'DaemonServer', sessionId) {
        try {
            const spawnInput = Array.isArray(input.tools)
                ? {
                    ...input,
                    tools: [...(input.tools ?? [])],
                }
                : input;
            const record = this.agentManager.spawn(spawnInput);
            this.syncSpawnedAgentTask(record, sessionId);
            return record;
        }
        catch (err) {
            const message = summarizeError(err);
            logger.error(`${logLabel}: agent spawn failed`, { error: message });
            return jsonErrorResponse(err, { status: 500, fallbackMessage: 'Failed to spawn agent' });
        }
    }
    syncSpawnedAgentTask(record, sessionId) {
        this.runtimeDispatch?.syncRuntimeTask({
            id: record.id,
            kind: 'agent',
            title: record.task.length > 80 ? `${record.task.slice(0, 77)}...` : record.task,
            description: record.task,
            status: record.status === 'pending' ? 'queued' : 'running',
            owner: record.id,
            cancellable: true,
            childTaskIds: [],
            queuedAt: record.startedAt,
            startedAt: record.status === 'pending' ? undefined : record.startedAt,
            correlationId: sessionId,
        }, 'daemon.server.agent-spawn');
    }
    syncFinishedAgentTask(record) {
        const status = record.status === 'completed'
            ? 'completed'
            : record.status === 'failed'
                ? 'failed'
                : 'cancelled';
        this.runtimeDispatch?.transitionRuntimeTask(record.id, status, {
            endedAt: record.completedAt ?? Date.now(),
            result: record.fullOutput ?? record.streamingContent,
            error: record.error,
        }, 'daemon.server.agent-finish');
    }
    surfaceDeliveryEnabled(surface) {
        return isSurfaceDeliveryEnabled(this.configManager, surface);
    }
    async pollPendingSurfaceReplies() {
        await this.surfaceDeliveryHelper.pollPendingSurfaceReplies((record) => this.syncFinishedAgentTask(record));
    }
    trustProxyEnabled() {
        return this.tlsState?.trustProxy ?? Boolean(this.configManager.get('controlPlane.trustProxy'));
    }
    signWebhookPayload(body, secret) {
        return this.surfaceDeliveryHelper.signWebhookPayload(body, secret);
    }
}
