import { isValidConfigKey } from '@pellux/goodvibes-sdk/platform/config/schema';
import { buildOperatorSessionCookie, OPERATOR_SESSION_COOKIE_NAME } from '@pellux/goodvibes-sdk/platform/security/http-auth';
import { normalizeAtSchedule, normalizeCronSchedule, normalizeEverySchedule } from '../../automation/index.js';
import { buildOperatorContract } from '../../control-plane/operator-contract.js';
import { getProviderRuntimeSnapshot, getProviderUsageSnapshot, listProviderRuntimeSnapshots, } from '../../providers/runtime-snapshot.js';
import { inspectKnowledgeGraphqlAccess } from '../../knowledge/index.js';
import { TelemetryApiService } from '../../runtime/telemetry/api.js';
import { inspectInboundTls, inspectOutboundTls } from '../../runtime/network/index.js';
import { dispatchDaemonApiRoutes } from '@pellux/goodvibes-sdk/platform/control-plane/routes/index';
import { handleGitHubAutomationWebhook, handleSlackSurfaceWebhook, handleDiscordSurfaceWebhook, handleNtfySurfaceWebhook, handleGenericWebhookSurface } from '../../adapters/index.js';
import { createDaemonKnowledgeRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/knowledge-routes';
import { createDaemonMediaRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/media-routes';
import { createDaemonRemoteRouteHandlers, handleRemotePairRequest, handleRemotePairVerify, handleRemotePeerHeartbeat, handleRemotePeerWorkPull, handleRemotePeerWorkComplete, } from '@pellux/goodvibes-sdk/platform/daemon/http/remote-routes';
import { createDaemonRuntimeRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/runtime-routes';
import { createDaemonControlRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/control-routes';
import { createDaemonIntegrationRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/integration-routes';
import { createDaemonTelemetryRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/telemetry-routes';
import { createDaemonChannelRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/channel-routes';
import { createDaemonSystemRouteHandlers } from '@pellux/goodvibes-sdk/platform/daemon/http/system-routes';
import { buildChannelRouteContext, buildKnowledgeRouteContext, buildMediaRouteContext, buildSystemRouteContext, } from './router-route-contexts.js';
import { jsonErrorResponse } from '@pellux/goodvibes-sdk/platform/daemon/http/error-response';
import { AppError } from '@pellux/goodvibes-sdk/platform/types/errors';
import { VERSION } from '../../version.js';
export class DaemonHttpRouter {
    context;
    telemetryApi;
    constructor(context) {
        this.context = context;
        this.telemetryApi = context.runtimeStore
            ? new TelemetryApiService({
                runtimeBus: context.runtimeBus,
                runtimeStore: context.runtimeStore,
            })
            : null;
    }
    dispose() {
        this.telemetryApi?.dispose();
    }
    async handleRequest(req) {
        const url = new URL(req.url);
        if (url.pathname === '/login' && req.method === 'POST') {
            return this.handleLogin(req);
        }
        if (url.pathname === '/api/remote/pair/request' && req.method === 'POST') {
            return handleRemotePairRequest({
                parseJsonBody: (request) => this.parseJsonBody(request),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        if (url.pathname === '/api/remote/pair/verify' && req.method === 'POST') {
            return handleRemotePairVerify({
                parseJsonBody: (request) => this.parseJsonBody(request),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        if (url.pathname === '/api/remote/heartbeat' && req.method === 'POST') {
            return handleRemotePeerHeartbeat({
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        if (url.pathname === '/api/remote/work/pull' && req.method === 'POST') {
            return handleRemotePeerWorkPull({
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                distributedRuntime: this.context.distributedRuntime,
            }, req);
        }
        const remoteWorkCompleteMatch = url.pathname.match(/^\/api\/remote\/work\/([^/]+)\/complete$/);
        if (remoteWorkCompleteMatch && req.method === 'POST') {
            return handleRemotePeerWorkComplete({
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                distributedRuntime: this.context.distributedRuntime,
            }, remoteWorkCompleteMatch[1], req);
        }
        if (url.pathname === '/webhook/github' && req.method === 'POST') {
            return this.handleGitHubWebhook(req);
        }
        if (url.pathname.startsWith('/webhook/')) {
            const pluginResponse = await this.context.channelPlugins.handleInbound(url.pathname, req);
            if (pluginResponse)
                return pluginResponse;
        }
        if (url.pathname === '/api/control-plane/web' && req.method === 'GET') {
            return this.context.controlPlaneGateway.renderWebUi();
        }
        if ((url.pathname === '/api/control-plane/auth' || url.pathname === '/api/control-plane/whoami') && req.method === 'GET') {
            const apiResponse = await this.dispatchApiRoutes(req);
            if (apiResponse)
                return apiResponse;
        }
        if (!this.context.checkAuth(req)) {
            return jsonErrorResponse(new AppError('Authentication required', 'AUTH_REQUIRED', false, {
                category: 'authentication',
                source: 'runtime',
                guidance: 'Authenticate with the operator shared token or an authenticated user session before calling daemon APIs.',
            }), { status: 401 });
        }
        const apiResponse = await this.dispatchApiRoutes(req);
        if (apiResponse)
            return apiResponse;
        return jsonErrorResponse(new AppError(`Route not found: ${url.pathname}`, 'NOT_FOUND', false, {
            category: 'not_found',
            source: 'runtime',
            guidance: 'Check the daemon API path and version. New SDK-facing routes are published under /api/v1.',
        }), { status: 404 });
    }
    async dispatchApiRoutes(req) {
        return dispatchDaemonApiRoutes(req, {
            ...createDaemonControlRouteHandlers({
                authToken: this.context.authToken(),
                version: VERSION,
                sessionCookieName: OPERATOR_SESSION_COOKIE_NAME,
                controlPlaneGateway: this.context.controlPlaneGateway,
                extractAuthToken: this.context.extractAuthToken,
                resolveAuthenticatedPrincipal: (request) => {
                    const token = this.context.extractAuthToken(request);
                    return token ? this.context.describeAuthenticatedPrincipal(token) : null;
                },
                gatewayMethods: this.context.gatewayMethods,
                getOperatorContract: () => buildOperatorContract(this.context.gatewayMethods),
                inspectInboundTls: (surface) => inspectInboundTls(this.context.configManager, surface),
                inspectOutboundTls: () => inspectOutboundTls(this.context.configManager),
                invokeGatewayMethodCall: this.context.invokeGatewayMethodCall,
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                requireAdmin: this.context.requireAdmin,
                requireAuthenticatedSession: this.context.requireAuthenticatedSession,
            }, req),
            ...createDaemonIntegrationRouteHandlers({
                channelPlugins: this.context.channelPlugins,
                integrationHelpers: this.context.integrationHelpers,
                memoryEmbeddingRegistry: this.context.memoryEmbeddingRegistry,
                memoryRegistry: this.context.memoryRegistry,
                parseJsonBody: (request) => this.parseJsonBody(request),
                providerRuntime: {
                    listSnapshots: () => listProviderRuntimeSnapshots(this.context.providerRegistry),
                    getSnapshot: (providerId) => getProviderRuntimeSnapshot(this.context.providerRegistry, providerId),
                    getUsageSnapshot: (providerId) => getProviderUsageSnapshot(this.context.providerRegistry, providerId),
                },
                requireAdmin: (request) => this.context.requireAdmin(request),
                userAuth: this.context.userAuth,
            }, req),
            ...createDaemonTelemetryRouteHandlers({
                telemetryApi: this.telemetryApi,
                resolveAuthenticatedPrincipal: (request) => {
                    const token = this.context.extractAuthToken(request);
                    return token ? this.context.describeAuthenticatedPrincipal(token) : null;
                },
            }),
            ...createDaemonChannelRouteHandlers({
                ...buildChannelRouteContext({
                    channelPlugins: this.context.channelPlugins,
                    channelPolicy: this.context.channelPolicy,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    surfaceRegistry: this.context.surfaceRegistry,
                }),
            }),
            ...createDaemonSystemRouteHandlers({
                ...buildSystemRouteContext({
                    approvalBroker: this.context.approvalBroker,
                    configManager: this.context.configManager,
                    integrationHelpers: this.context.integrationHelpers,
                    inspectInboundTls: (surface) => inspectInboundTls(this.context.configManager, surface),
                    inspectOutboundTls: () => inspectOutboundTls(this.context.configManager),
                    isValidConfigKey,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                    platformServiceManager: this.context.platformServiceManager,
                    recordApiResponse: (request, path, response, clientKind) => this.recordApiResponse(request, path, response, clientKind),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    requireAuthenticatedSession: (request) => this.context.requireAuthenticatedSession(request),
                    routeBindings: this.context.routeBindings,
                    watcherRegistry: this.context.watcherRegistry,
                }),
            }, req),
            ...createDaemonRuntimeRouteHandlers({
                parseJsonBody: (request) => this.parseJsonBody(request),
                parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                recordApiResponse: (request, path, response) => this.recordApiResponse(request, path, response),
                requireAdmin: (request) => this.context.requireAdmin(request),
                sessionBroker: {
                    start: () => this.context.sessionBroker.start(),
                    submitMessage: (input) => this.context.sessionBroker.submitMessage(input),
                    steerMessage: (input) => this.context.sessionBroker.steerMessage(input),
                    followUpMessage: (input) => this.context.sessionBroker.followUpMessage(input),
                    bindAgent: async (sessionId, agentId) => {
                        await this.context.sessionBroker.bindAgent(sessionId, agentId);
                    },
                    createSession: (input) => this.context.sessionBroker.createSession(input),
                    getSession: (sessionId) => this.context.sessionBroker.getSession(sessionId),
                    getMessages: (sessionId, limit) => this.context.sessionBroker.getMessages(sessionId, limit),
                    getInputs: (sessionId, limit) => this.context.sessionBroker.getInputs(sessionId, limit),
                    closeSession: (sessionId) => this.context.sessionBroker.closeSession(sessionId),
                    reopenSession: (sessionId) => this.context.sessionBroker.reopenSession(sessionId),
                    cancelInput: (sessionId, inputId) => this.context.sessionBroker.cancelInput(sessionId, inputId),
                    completeAgent: async (sessionId, agentId, message, meta) => {
                        await this.context.sessionBroker.completeAgent(sessionId, agentId, message, meta);
                    },
                },
                agentManager: {
                    getStatus: (agentId) => this.context.agentManager.getStatus(agentId),
                    cancel: (agentId) => this.context.agentManager.cancel(agentId),
                },
                automationManager: {
                    listJobs: () => this.context.automationManager.listJobs(),
                    listRuns: () => this.context.automationManager.listRuns(),
                    getRun: (runId) => this.context.automationManager.getRun(runId) ?? null,
                    triggerHeartbeat: (input) => this.context.automationManager.triggerHeartbeat(input),
                    cancelRun: (runId, reason) => this.context.automationManager.cancelRun(runId, reason),
                    retryRun: (runId) => this.context.automationManager.retryRun(runId),
                    createJob: (input) => this.context.automationManager.createJob(input),
                    updateJob: (jobId, input) => this.context.automationManager.updateJob(jobId, input),
                    removeJob: async (jobId) => {
                        await this.context.automationManager.removeJob(jobId);
                    },
                    setEnabled: (jobId, enabled) => this.context.automationManager.setEnabled(jobId, enabled),
                    runNow: (jobId) => this.context.automationManager.runNow(jobId),
                },
                normalizeAtSchedule,
                normalizeEverySchedule,
                normalizeCronSchedule,
                routeBindings: {
                    start: () => this.context.routeBindings.start(),
                    getBinding: (id) => this.context.routeBindings.getBinding(id),
                },
                trySpawnAgent: (input, logLabel, sessionId) => this.context.trySpawnAgent({
                    ...input,
                    ...(input.tools ? { tools: [...input.tools] } : {}),
                }, logLabel, sessionId),
                queueSurfaceReplyFromBinding: (binding, input) => this.context.queueSurfaceReplyFromBinding(binding, input),
                surfaceDeliveryEnabled: (surface) => this.context.surfaceDeliveryEnabled(surface),
                syncSpawnedAgentTask: (record, sessionId) => this.context.syncSpawnedAgentTask(record, sessionId),
                syncFinishedAgentTask: (record) => this.context.syncFinishedAgentTask(record),
                configManager: this.context.configManager,
                runtimeStore: this.context.runtimeStore,
                runtimeDispatch: this.context.runtimeDispatch,
            }),
            ...createDaemonRemoteRouteHandlers({
                authToken: this.context.authToken(),
                parseJsonBody: (request) => this.parseJsonBody(request),
                requireAdmin: (request) => this.context.requireAdmin(request),
                requireRemotePeer: (request, scope) => this.context.requireRemotePeer(request, scope),
                requireAuthenticatedSession: (request) => this.context.requireAuthenticatedSession(request),
                distributedRuntime: this.context.distributedRuntime,
            }),
            ...createDaemonKnowledgeRouteHandlers({
                ...buildKnowledgeRouteContext({
                    configManager: this.context.configManager,
                    inspectGraphqlAccess: inspectKnowledgeGraphqlAccess,
                    normalizeAtSchedule,
                    normalizeEverySchedule,
                    normalizeCronSchedule,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    parseOptionalJsonBody: (request) => this.parseOptionalJsonBody(request),
                    parseJsonText: (raw) => this.parseJsonText(raw),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    resolveAuthenticatedPrincipal: (request) => {
                        const token = this.context.extractAuthToken(request);
                        return token ? this.context.describeAuthenticatedPrincipal(token) : null;
                    },
                    knowledgeService: this.context.knowledgeService,
                    knowledgeGraphqlService: this.context.knowledgeGraphqlService,
                }),
            }),
            ...createDaemonMediaRouteHandlers({
                ...buildMediaRouteContext({
                    artifactStore: this.context.artifactStore,
                    configManager: this.context.configManager,
                    mediaProviders: this.context.mediaProviders,
                    multimodalService: this.context.multimodalService,
                    parseJsonBody: (request) => this.parseJsonBody(request),
                    requireAdmin: (request) => this.context.requireAdmin(request),
                    voiceService: this.context.voiceService,
                    webSearchService: this.context.webSearchService,
                }),
            }),
        });
    }
    async parseJsonBody(req) {
        try {
            return await req.json();
        }
        catch {
            return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
        }
    }
    async parseOptionalJsonBody(req) {
        const raw = await req.text();
        if (!raw.trim())
            return null;
        return this.parseJsonText(raw);
    }
    parseJsonText(rawBody) {
        try {
            return JSON.parse(rawBody);
        }
        catch {
            return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
        }
    }
    recordApiResponse(req, path, response, clientKind = 'web') {
        this.context.controlPlaneGateway.recordApiRequest({
            method: req.method,
            path,
            status: response.status,
            clientKind,
            ...(response.status >= 400 ? { error: `${req.method} ${path} -> ${response.status}` } : {}),
        });
        return response;
    }
    async handleLogin(req) {
        const body = await this.parseJsonBody(req);
        if (body instanceof Response)
            return body;
        const username = typeof body.username === 'string' ? body.username : '';
        const password = typeof body.password === 'string' ? body.password : '';
        const user = this.context.userAuth.authenticate(username, password);
        if (!user) {
            return Response.json({ error: 'Invalid credentials' }, { status: 401 });
        }
        const session = this.context.userAuth.createSession(user.username);
        return Response.json({
            authenticated: true,
            token: session.token,
            username: session.username,
            expiresAt: session.expiresAt,
        }, {
            headers: {
                'Set-Cookie': buildOperatorSessionCookie(session.token, {
                    req,
                    expiresAt: session.expiresAt,
                    trustProxy: Boolean(this.context.configManager.get('controlPlane.trustProxy')),
                }),
            },
        });
    }
    async handleGitHubWebhook(req) {
        return handleGitHubAutomationWebhook(req, {
            serviceRegistry: this.context.serviceRegistry,
            githubWebhookSecret: this.context.githubWebhookSecret,
            trySpawnAgent: (input, logLabel, sessionId) => this.context.trySpawnAgent(input, logLabel, sessionId),
        });
    }
    async handleSlackWebhook(req) {
        return handleSlackSurfaceWebhook(req, this.context.buildSurfaceAdapterContext());
    }
    async handleDiscordWebhook(req) {
        return handleDiscordSurfaceWebhook(req, this.context.buildSurfaceAdapterContext());
    }
    async handleNtfyWebhook(req) {
        return handleNtfySurfaceWebhook(req, this.context.buildSurfaceAdapterContext());
    }
    async handleGenericWebhook(req) {
        return handleGenericWebhookSurface(req, this.context.buildGenericWebhookAdapterContext());
    }
}
