import { randomUUID } from 'node:crypto';
import { createDomainDispatch } from '../runtime/store/index.js';
import { emitControlPlaneAuthGranted, emitControlPlaneClientConnected, emitControlPlaneClientDisconnected, emitControlPlaneSubscriptionCreated, emitControlPlaneSubscriptionDropped, } from '../runtime/emitters/index.js';
import { renderControlPlaneGatewayWebUi } from '@pellux/goodvibes-sdk/platform/control-plane/gateway-web-ui';
const DEFAULT_DOMAINS = [
    'session',
    'tasks',
    'agents',
    'automation',
    'routes',
    'control-plane',
    'deliveries',
    'surfaces',
    'watchers',
    'transport',
    'ops',
    'knowledge',
];
const DEFAULT_SERVER_CONFIG = {
    enabled: false,
    host: '127.0.0.1',
    port: 3421,
    streamingMode: 'sse',
    sessionTtlMs: 12 * 60 * 60 * 1000,
};
function serializeEnvelope(envelope) {
    return {
        type: envelope.type,
        timestamp: envelope.ts,
        traceId: envelope.traceId,
        sessionId: envelope.sessionId,
        source: envelope.source,
        payload: envelope.payload,
    };
}
function toClientDescriptor(record) {
    return {
        id: record.id,
        surface: record.kind,
        label: record.label,
        connectedAt: record.authenticatedAt ?? record.lastSeenAt ?? Date.now(),
        lastSeenAt: record.lastSeenAt ?? Date.now(),
        ...(record.metadata.userId && typeof record.metadata.userId === 'string' ? { userId: record.metadata.userId } : {}),
    };
}
export class ControlPlaneGateway {
    runtimeBus;
    dispatch;
    serverConfig;
    clients = new Map();
    liveClients = new Map();
    websocketClients = new Map();
    recentMessages = [];
    recentEvents = [];
    requestCount = 0;
    errorCount = 0;
    lastRequestAt;
    constructor(config = {}) {
        this.runtimeBus = config.runtimeBus ?? null;
        this.dispatch = config.runtimeStore ? createDomainDispatch(config.runtimeStore) : null;
        this.serverConfig = {
            ...DEFAULT_SERVER_CONFIG,
            ...config.server,
        };
        if (this.dispatch) {
            this.dispatch.syncControlPlaneState({
                enabled: this.serverConfig.enabled,
                host: this.serverConfig.host,
                port: this.serverConfig.port,
                connectionState: this.serverConfig.enabled ? 'disconnected' : 'disabled',
            }, 'control-plane.gateway.init');
        }
    }
    attachRuntime(config) {
        if (config.runtimeBus) {
            this.runtimeBus = config.runtimeBus;
        }
        if (config.runtimeStore) {
            this.dispatch = createDomainDispatch(config.runtimeStore);
            this.dispatch.syncControlPlaneState({
                enabled: this.serverConfig.enabled,
                host: this.serverConfig.host,
                port: this.serverConfig.port,
                connectionState: this.serverConfig.enabled ? 'disconnected' : 'disabled',
            }, 'control-plane.gateway.attach');
            for (const client of this.clients.values()) {
                this.dispatch.syncControlPlaneClient(client, 'control-plane.gateway.attach');
            }
        }
    }
    listClients() {
        return [...this.clients.values()]
            .sort((a, b) => (b.lastSeenAt ?? 0) - (a.lastSeenAt ?? 0) || a.id.localeCompare(b.id))
            .map(toClientDescriptor);
    }
    getSnapshot() {
        const active = [...this.clients.values()].filter((client) => client.connected);
        return {
            server: this.serverConfig,
            totals: {
                clients: this.clients.size,
                activeClients: active.length,
                surfaceMessages: this.recentMessages.length,
                recentEvents: this.recentEvents.length,
                requests: this.requestCount,
                errors: this.errorCount,
            },
            clients: this.listClients(),
            messages: this.listSurfaceMessages(20),
            recentEvents: this.listRecentEvents(30),
        };
    }
    listSurfaceMessages(limit = 50) {
        return this.recentMessages.slice(0, Math.max(1, limit));
    }
    listRecentEvents(limit = 100) {
        return this.recentEvents.slice(0, Math.max(1, limit));
    }
    publishSurfaceMessage(input) {
        const message = {
            id: `cpmsg-${randomUUID().slice(0, 8)}`,
            createdAt: Date.now(),
            ...input,
        };
        this.recentMessages.unshift(message);
        if (this.recentMessages.length > 200) {
            this.recentMessages.length = 200;
        }
        const record = this.rememberEvent('surface-message', message);
        for (const client of this.liveClients.values()) {
            if (client.kind !== 'web')
                continue;
            if (message.clientId && client.clientId !== message.clientId)
                continue;
            if (message.routeId && client.routeId && client.routeId !== message.routeId)
                continue;
            if (message.surfaceId && client.surfaceId && client.surfaceId !== message.surfaceId)
                continue;
            client.send('surface-message', message, record.id);
        }
        return message;
    }
    publishEvent(event, payload, filter) {
        const record = this.rememberEvent(event, payload);
        for (const client of this.liveClients.values()) {
            if (filter?.clientKind && client.kind !== filter.clientKind)
                continue;
            if (filter?.clientId && client.clientId !== filter.clientId)
                continue;
            if (filter?.routeId && client.routeId && client.routeId !== filter.routeId)
                continue;
            if (filter?.surfaceId && client.surfaceId && client.surfaceId !== filter.surfaceId)
                continue;
            client.send(event, payload, record.id);
        }
    }
    recordApiRequest(input) {
        this.requestCount += 1;
        this.lastRequestAt = Date.now();
        if (input.status >= 400 || input.error) {
            this.errorCount += 1;
        }
        this.dispatch?.syncControlPlaneState({
            requestCount: this.requestCount,
            errorCount: this.errorCount,
            lastRequestAt: this.lastRequestAt,
            ...(input.error ? { lastError: input.error } : {}),
        }, 'control-plane.gateway.api-request');
        this.rememberEvent('api-request', {
            method: input.method,
            path: input.path,
            status: input.status,
            clientKind: input.clientKind ?? 'web',
            ...(input.error ? { error: input.error } : {}),
        });
    }
    setServerState(patch) {
        Object.assign(this.serverConfig, patch);
        const hasActiveClient = [...this.clients.values()].some((client) => client.connected);
        this.dispatch?.syncControlPlaneState({
            enabled: this.serverConfig.enabled,
            host: this.serverConfig.host,
            port: this.serverConfig.port,
            connectionState: this.serverConfig.enabled ? (hasActiveClient ? 'connected' : 'disconnected') : 'disabled',
            requestCount: this.requestCount,
            errorCount: this.errorCount,
            lastRequestAt: this.lastRequestAt,
        }, 'control-plane.gateway.state');
    }
    openWebSocketClient(options, send) {
        if (!this.runtimeBus) {
            throw new Error('Runtime event bus unavailable');
        }
        const selectedDomains = options.domains?.length ? [...options.domains] : [...DEFAULT_DOMAINS];
        const clientId = options.clientId ?? `cp-${randomUUID().slice(0, 8)}`;
        const label = options.label ?? `${options.clientKind ?? 'web'}:${clientId}`;
        const now = Date.now();
        const surfaceKind = options.clientKind === 'daemon' ? 'service' : (options.clientKind ?? 'web');
        const clientRecord = {
            id: clientId,
            kind: surfaceKind,
            label,
            transport: 'websocket',
            connected: true,
            sessionId: options.sessionId,
            routeId: options.routeId,
            surfaceId: options.surfaceId,
            authenticatedAt: now,
            lastSeenAt: now,
            remoteAddress: options.remoteAddress,
            capabilities: [...(options.capabilities ?? [])],
            metadata: {
                domains: selectedDomains,
                ...(options.principalId ? { userId: options.principalId } : {}),
            },
        };
        const traceId = `control-plane:${clientId}`;
        this.clients.set(clientId, clientRecord);
        this.liveClients.set(clientId, {
            clientId,
            kind: options.clientKind ?? 'web',
            surfaceId: options.surfaceId,
            routeId: options.routeId,
            send,
        });
        this.websocketClients.set(clientId, {
            clientId,
            traceId,
            domains: new Set(),
            unsubscribers: new Map(),
        });
        this.dispatch?.syncControlPlaneState({
            enabled: true,
            isRunning: true,
            connectionState: 'connected',
        }, 'control-plane.gateway.ws-connect');
        this.dispatch?.syncControlPlaneClient(clientRecord, 'control-plane.gateway.ws-connect');
        const eventClientKind = options.clientKind === 'daemon' ? 'service' : (options.clientKind ?? 'web');
        emitControlPlaneClientConnected(this.runtimeBus, {
            sessionId: options.sessionId ?? 'control-plane',
            source: 'control-plane.gateway',
            traceId,
        }, {
            clientId,
            clientKind: eventClientKind,
            transport: 'ws',
        });
        emitControlPlaneSubscriptionCreated(this.runtimeBus, {
            sessionId: options.sessionId ?? 'control-plane',
            source: 'control-plane.gateway',
            traceId,
        }, {
            clientId,
            subscriptionId: clientId,
            topics: selectedDomains,
        });
        if (options.principalId) {
            emitControlPlaneAuthGranted(this.runtimeBus, {
                sessionId: options.sessionId ?? 'control-plane',
                source: 'control-plane.gateway',
                traceId,
            }, {
                clientId,
                principalId: options.principalId,
                principalKind: options.principalKind ?? 'token',
                scopes: [...(options.scopes ?? ['read:events'])],
            });
        }
        this.subscribeWebSocketClient(clientId, selectedDomains);
        send('ready', { clientId, domains: selectedDomains, transport: 'websocket' });
        this.replayRecentTraffic(send);
        return { clientId, domains: selectedDomains };
    }
    touchWebSocketClient(clientId, metadata = {}) {
        const existing = this.clients.get(clientId);
        if (!existing)
            return;
        const updated = {
            ...existing,
            lastSeenAt: Date.now(),
            metadata: {
                ...existing.metadata,
                ...metadata,
            },
        };
        this.clients.set(clientId, updated);
        this.dispatch?.syncControlPlaneClient(updated, 'control-plane.gateway.ws-touch');
    }
    authenticateClient(clientId, input) {
        const existing = this.clients.get(clientId);
        if (!existing || !this.runtimeBus)
            return;
        const updated = {
            ...existing,
            label: input.label ?? existing.label,
            authenticatedAt: Date.now(),
            lastSeenAt: Date.now(),
            capabilities: input.capabilities ? [...input.capabilities] : existing.capabilities,
            metadata: {
                ...existing.metadata,
                userId: input.principalId,
            },
        };
        this.clients.set(clientId, updated);
        this.dispatch?.syncControlPlaneClient(updated, 'control-plane.gateway.ws-auth');
        emitControlPlaneAuthGranted(this.runtimeBus, {
            sessionId: updated.sessionId ?? 'control-plane',
            source: 'control-plane.gateway',
            traceId: `control-plane:${clientId}:auth`,
        }, {
            clientId,
            principalId: input.principalId,
            principalKind: input.principalKind ?? 'token',
            scopes: [...(input.scopes ?? ['read:events'])],
        });
    }
    subscribeWebSocketClient(clientId, domains) {
        const wsClient = this.websocketClients.get(clientId);
        if (!wsClient || !this.runtimeBus)
            return;
        const liveClient = this.liveClients.get(clientId);
        if (!liveClient)
            return;
        const nextDomains = [...new Set(domains)];
        for (const domain of nextDomains) {
            if (wsClient.unsubscribers.has(domain))
                continue;
            const unsubscribe = this.runtimeBus.onDomain(domain, (envelope) => {
                this.touchWebSocketClient(clientId, { lastEventType: envelope.type });
                const serialized = serializeEnvelope(envelope);
                const record = this.rememberEvent(domain, serialized);
                liveClient.send(domain, serialized, record.id);
            });
            wsClient.unsubscribers.set(domain, unsubscribe);
            wsClient.domains.add(domain);
        }
        this.touchWebSocketClient(clientId, { domains: [...wsClient.domains] });
    }
    unsubscribeWebSocketClient(clientId, domains) {
        const wsClient = this.websocketClients.get(clientId);
        if (!wsClient)
            return;
        const targetDomains = domains?.length ? [...new Set(domains)] : [...wsClient.domains];
        for (const domain of targetDomains) {
            const unsubscribe = wsClient.unsubscribers.get(domain);
            unsubscribe?.();
            wsClient.unsubscribers.delete(domain);
            wsClient.domains.delete(domain);
        }
        this.touchWebSocketClient(clientId, { domains: [...wsClient.domains] });
    }
    closeWebSocketClient(clientId, reason = 'socket-closed') {
        const wsClient = this.websocketClients.get(clientId);
        if (!wsClient)
            return;
        if (this.runtimeBus) {
            emitControlPlaneSubscriptionDropped(this.runtimeBus, {
                sessionId: this.clients.get(clientId)?.sessionId ?? 'control-plane',
                source: 'control-plane.gateway',
                traceId: wsClient.traceId,
            }, {
                clientId,
                subscriptionId: clientId,
                reason,
            });
            emitControlPlaneClientDisconnected(this.runtimeBus, {
                sessionId: this.clients.get(clientId)?.sessionId ?? 'control-plane',
                source: 'control-plane.gateway',
                traceId: wsClient.traceId,
            }, {
                clientId,
                reason,
            });
        }
        this.unsubscribeWebSocketClient(clientId);
        this.websocketClients.delete(clientId);
        this.liveClients.delete(clientId);
        const previous = this.clients.get(clientId);
        if (!previous)
            return;
        const disconnected = {
            ...previous,
            connected: false,
            lastSeenAt: Date.now(),
        };
        this.clients.set(clientId, disconnected);
        this.dispatch?.syncControlPlaneClient(disconnected, 'control-plane.gateway.ws-disconnect');
        this.dispatch?.syncControlPlaneState({
            enabled: true,
            isRunning: true,
            connectionState: [...this.clients.values()].some((client) => client.connected) ? 'connected' : 'disconnected',
        }, 'control-plane.gateway.ws-disconnect');
    }
    replayRecentTraffic(send, sinceId) {
        const sinceIndex = sinceId ? this.recentEvents.findIndex((event) => event.id === sinceId) : -1;
        const recentEvents = sinceIndex >= 0
            ? this.recentEvents.slice(0, sinceIndex).reverse()
            : this.listRecentEvents(20).reverse();
        for (const recentEvent of recentEvents) {
            send(recentEvent.event, recentEvent.payload, recentEvent.id);
        }
    }
    createEventStream(request, options = {}) {
        if (!this.runtimeBus) {
            return Response.json({ error: 'Runtime event bus unavailable' }, { status: 503 });
        }
        const encoder = new TextEncoder();
        const selectedDomains = options.domains?.length ? [...options.domains] : [...DEFAULT_DOMAINS];
        const lastEventId = request.headers.get('last-event-id')?.trim() || undefined;
        const clientId = options.clientId ?? `cp-${randomUUID().slice(0, 8)}`;
        const label = options.label ?? `${options.clientKind ?? 'web'}:${clientId}`;
        const now = Date.now();
        const transport = options.transport ?? 'sse';
        const surfaceKind = options.clientKind === 'daemon' ? 'service' : (options.clientKind ?? 'web');
        const clientRecord = {
            id: clientId,
            kind: surfaceKind,
            label,
            transport: transport === 'ws' ? 'websocket' : transport === 'local' ? 'local' : 'sse',
            connected: true,
            sessionId: options.sessionId,
            routeId: options.routeId,
            surfaceId: options.surfaceId,
            authenticatedAt: now,
            lastSeenAt: now,
            remoteAddress: options.remoteAddress,
            capabilities: [...(options.capabilities ?? [])],
            metadata: {
                domains: selectedDomains,
                ...(options.principalId ? { userId: options.principalId } : {}),
            },
        };
        this.clients.set(clientId, clientRecord);
        this.dispatch?.syncControlPlaneState({
            enabled: true,
            isRunning: true,
            connectionState: 'connected',
        }, 'control-plane.gateway.connect');
        this.dispatch?.syncControlPlaneClient(clientRecord, 'control-plane.gateway.connect');
        const traceId = `control-plane:${clientId}`;
        const eventClientKind = options.clientKind === 'daemon' ? 'service' : (options.clientKind ?? 'web');
        emitControlPlaneClientConnected(this.runtimeBus, {
            sessionId: options.sessionId ?? 'control-plane',
            source: 'control-plane.gateway',
            traceId,
        }, {
            clientId,
            clientKind: eventClientKind,
            transport,
        });
        emitControlPlaneSubscriptionCreated(this.runtimeBus, {
            sessionId: options.sessionId ?? 'control-plane',
            source: 'control-plane.gateway',
            traceId,
        }, {
            clientId,
            subscriptionId: clientId,
            topics: selectedDomains,
        });
        if (options.principalId) {
            emitControlPlaneAuthGranted(this.runtimeBus, {
                sessionId: options.sessionId ?? 'control-plane',
                source: 'control-plane.gateway',
                traceId,
            }, {
                clientId,
                principalId: options.principalId,
                principalKind: options.principalKind ?? 'token',
                scopes: [...(options.scopes ?? ['read:events'])],
            });
        }
        let teardown = () => { };
        const stream = new ReadableStream({
            start: (controller) => {
                const send = (event, payload, id) => {
                    controller.enqueue(encoder.encode(`${id ? `id: ${id}\n` : ''}event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`));
                };
                const unsubs = selectedDomains.map((domain) => this.runtimeBus.onDomain(domain, (envelope) => {
                    const updated = {
                        ...clientRecord,
                        lastSeenAt: Date.now(),
                        metadata: {
                            ...clientRecord.metadata,
                            lastEventType: envelope.type,
                        },
                    };
                    this.clients.set(clientId, updated);
                    this.dispatch?.syncControlPlaneClient(updated, 'control-plane.gateway.heartbeat');
                    const serialized = serializeEnvelope(envelope);
                    const record = this.rememberEvent(domain, serialized);
                    send(domain, serialized, record.id);
                }));
                this.liveClients.set(clientId, {
                    clientId,
                    kind: options.clientKind ?? 'web',
                    surfaceId: options.surfaceId,
                    routeId: options.routeId,
                    send,
                });
                const heartbeat = setInterval(() => {
                    send('heartbeat', { clientId, ts: Date.now() });
                }, 15_000);
                teardown = () => {
                    clearInterval(heartbeat);
                    for (const unsub of unsubs)
                        unsub();
                    this.liveClients.delete(clientId);
                    const previous = this.clients.get(clientId);
                    if (previous) {
                        const disconnected = {
                            ...previous,
                            connected: false,
                            lastSeenAt: Date.now(),
                        };
                        this.clients.set(clientId, disconnected);
                        this.dispatch?.syncControlPlaneClient(disconnected, 'control-plane.gateway.disconnect');
                        this.dispatch?.syncControlPlaneState({
                            enabled: true,
                            isRunning: true,
                            connectionState: [...this.clients.values()].some((client) => client.connected) ? 'connected' : 'disconnected',
                        }, 'control-plane.gateway.disconnect');
                        emitControlPlaneSubscriptionDropped(this.runtimeBus, {
                            sessionId: options.sessionId ?? 'control-plane',
                            source: 'control-plane.gateway',
                            traceId,
                        }, {
                            clientId,
                            subscriptionId: clientId,
                            reason: 'stream-closed',
                        });
                        emitControlPlaneClientDisconnected(this.runtimeBus, {
                            sessionId: options.sessionId ?? 'control-plane',
                            source: 'control-plane.gateway',
                            traceId,
                        }, {
                            clientId,
                            reason: 'stream-closed',
                        });
                    }
                };
                request.signal.addEventListener('abort', () => {
                    teardown();
                    controller.close();
                }, { once: true });
                send('ready', { clientId, domains: selectedDomains });
                this.replayRecentTraffic(send, lastEventId);
            },
            cancel: () => {
                teardown();
            },
        });
        return new Response(stream, {
            headers: {
                'content-type': 'text/event-stream',
                'cache-control': 'no-cache',
                connection: 'keep-alive',
            },
        });
    }
    renderWebUi(authTokenHint = '') {
        return renderControlPlaneGatewayWebUi(authTokenHint);
    }
    rememberEvent(event, payload) {
        const record = {
            id: `cpe-${randomUUID().slice(0, 8)}`,
            event,
            createdAt: Date.now(),
            payload,
        };
        this.recentEvents.unshift(record);
        if (this.recentEvents.length > 500) {
            this.recentEvents.length = 500;
        }
        this.dispatch?.syncControlPlaneState({
            requestCount: this.requestCount,
            errorCount: this.errorCount,
            lastRequestAt: this.lastRequestAt,
            lastEventAt: record.createdAt,
        }, 'control-plane.gateway.event');
        return record;
    }
}
