/**
 * Agent gRPC Server
 *
 * gRPC server for real-time agent-to-agent communication
 * Replaces the WebSocket server with gRPC bidirectional streaming
 */
import * as grpc from '@grpc/grpc-js';
import { EventEmitter } from 'events';
import { log } from '../utils/logger.js';
import { v4 as uuidv4 } from 'uuid';
export class AgentGrpcServer extends EventEmitter {
    server;
    connections = new Map();
    messageCallbacks = new Map();
    port;
    constructor(port) {
        super();
        this.port = port;
        try {
            log.info(`Creating gRPC server on port ${port}...`);
            this.server = new grpc.Server();
            this.setupServices();
            log.info(`gRPC server created successfully, will bind to port ${port}`);
        }
        catch (error) {
            log.error(`Failed to create gRPC server on port ${port}:`, error);
            throw error;
        }
    }
    setupServices() {
        // Define the AgentLink service
        const agentLinkService = {
            // Bidirectional streaming for agent communication
            connect: this.handleAgentConnect.bind(this),
            // Health check endpoint
            healthCheck: this.handleHealthCheck.bind(this),
        };
        // Add service to server
        // Note: In a full implementation, we would use generated proto definitions
        // For now, we'll use a dynamic approach
        this.server.addService(this.createServiceDefinition(), agentLinkService);
    }
    createServiceDefinition() {
        // Create a service definition for AgentLink
        return {
            connect: {
                path: '/agentlink.AgentLink/Connect',
                requestStream: true,
                responseStream: true,
                requestSerialize: this.serializeMessage,
                requestDeserialize: this.deserializeMessage,
                responseSerialize: this.serializeMessage,
                responseDeserialize: this.deserializeMessage,
            },
            healthCheck: {
                path: '/agentlink.AgentLink/HealthCheck',
                requestStream: false,
                responseStream: false,
                requestSerialize: (value) => Buffer.from(JSON.stringify(value)),
                requestDeserialize: (buffer) => JSON.parse(buffer.toString()),
                responseSerialize: (value) => Buffer.from(JSON.stringify(value)),
                responseDeserialize: (buffer) => JSON.parse(buffer.toString()),
            }
        };
    }
    serializeMessage(value) {
        return Buffer.from(JSON.stringify(value));
    }
    deserializeMessage(buffer) {
        return JSON.parse(buffer.toString());
    }
    handleAgentConnect(call) {
        const connectionId = uuidv4();
        log.info(`New gRPC connection established: ${connectionId}`);
        // Set up message handlers
        call.on('data', (message) => {
            this.handleIncomingMessage(connectionId, message);
        });
        call.on('error', (error) => {
            log.error(`gRPC connection error for ${connectionId}:`, error);
            this.handleDisconnection(connectionId);
        });
        call.on('end', () => {
            log.info(`gRPC connection ended: ${connectionId}`);
            this.handleDisconnection(connectionId);
        });
        // Store connection
        const connection = {
            call,
            agentId: '', // Will be set when agent registers
            agentName: '',
            lastSeen: Date.now(),
            stream: call,
        };
        this.connections.set(connectionId, connection);
        // Send welcome message
        const welcomeMessage = {
            conversationId: 'system',
            messageId: uuidv4(),
            fromAgent: 'master',
            toAgent: '',
            fromMaster: 'master',
            toMaster: '',
            type: 'welcome',
            seq: 0,
            content: JSON.stringify({
                message: 'Connected to gRPC agent server',
                connectionId,
                timestamp: Date.now(),
            }),
            final: true,
            timestamp: Date.now(),
            receivedAt: Date.now(),
            metadata: {},
        };
        call.write(welcomeMessage);
    }
    handleHealthCheck(call, callback) {
        callback(null, {
            status: 1, // SERVING
            uptime: process.uptime(),
            connections: this.connections.size,
        });
    }
    handleIncomingMessage(connectionId, message) {
        message.receivedAt = Date.now();
        // Update last seen
        const connection = this.connections.get(connectionId);
        if (connection) {
            connection.lastSeen = Date.now();
            // Handle registration
            if (message.type === 'register') {
                try {
                    const registration = JSON.parse(message.content);
                    connection.agentId = registration.agentId;
                    connection.agentName = registration.agentName || registration.agentId;
                    connection.persona = registration.persona;
                    log.info(`Agent registered: ${connection.agentId} (${connection.agentName})`);
                    // Emit registration event
                    this.emit('agentRegistered', {
                        agentId: connection.agentId,
                        agentName: connection.agentName,
                        persona: connection.persona,
                        connectionId,
                    });
                    // Send registration confirmation
                    const confirmMessage = {
                        conversationId: 'system',
                        messageId: uuidv4(),
                        fromAgent: 'master',
                        toAgent: connection.agentId,
                        fromMaster: 'master',
                        toMaster: '',
                        type: 'registration_confirmed',
                        seq: 0,
                        content: JSON.stringify({
                            message: 'Registration confirmed',
                            agentId: connection.agentId,
                            timestamp: Date.now(),
                        }),
                        final: true,
                        timestamp: Date.now(),
                        receivedAt: Date.now(),
                        metadata: {},
                    };
                    connection.call.write(confirmMessage);
                    return;
                }
                catch (error) {
                    log.error('Failed to parse registration:', error);
                    return;
                }
            }
            // Handle heartbeat
            if (message.type === 'heartbeat') {
                // Just update lastSeen, already done above
                return;
            }
            // Route the message
            this.routeMessage(message);
        }
    }
    handleDisconnection(connectionId) {
        const connection = this.connections.get(connectionId);
        if (connection && connection.agentId) {
            log.info(`Agent disconnected: ${connection.agentId}`);
            // Emit disconnection event
            this.emit('agentDisconnected', {
                agentId: connection.agentId,
                agentName: connection.agentName,
                connectionId,
            });
        }
        this.connections.delete(connectionId);
    }
    routeMessage(message) {
        const targetAgent = message.toAgent;
        if (!targetAgent || targetAgent === 'broadcast') {
            // Broadcast to all connected agents (except sender)
            this.broadcastMessage(message);
            return;
        }
        // Find target connection
        let targetConnection;
        for (const [_, connection] of this.connections.entries()) {
            if (connection.agentId === targetAgent) {
                targetConnection = connection;
                break;
            }
        }
        if (targetConnection) {
            // Send to target agent
            targetConnection.call.write(message);
            // Emit message delivered event
            this.emit('messageDelivered', {
                from: message.fromAgent,
                to: targetAgent,
                messageId: message.messageId,
                conversationId: message.conversationId,
            });
        }
        else {
            // Target not found
            log.warn(`Target agent not found: ${targetAgent}`);
            // Send error back to sender
            const errorMessage = {
                conversationId: message.conversationId,
                messageId: uuidv4(),
                fromAgent: 'master',
                toAgent: message.fromAgent,
                fromMaster: 'master',
                toMaster: '',
                type: 'error',
                seq: 0,
                content: JSON.stringify({
                    code: 'AGENT_NOT_FOUND',
                    message: `Target agent ${targetAgent} not found`,
                    originalMessageId: message.messageId,
                }),
                final: true,
                timestamp: Date.now(),
                receivedAt: Date.now(),
                metadata: {},
            };
            // Find sender connection
            for (const [_, connection] of this.connections.entries()) {
                if (connection.agentId === message.fromAgent) {
                    connection.call.write(errorMessage);
                    break;
                }
            }
        }
    }
    broadcastMessage(message) {
        // Send to all connected agents except sender
        for (const [_, connection] of this.connections.entries()) {
            if (connection.agentId && connection.agentId !== message.fromAgent) {
                const broadcastMessage = { ...message };
                broadcastMessage.toAgent = connection.agentId;
                connection.call.write(broadcastMessage);
            }
        }
        log.debug(`Broadcast message from ${message.fromAgent} to ${this.connections.size - 1} agents`);
    }
    async start() {
        return new Promise((resolve, reject) => {
            this.server.bindAsync(`0.0.0.0:${this.port}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
                if (error) {
                    log.error(`Failed to bind gRPC server to port ${this.port}:`, error);
                    reject(error);
                    return;
                }
                this.server.start();
                log.info(`gRPC server started on port ${port}`);
                resolve();
            });
        });
    }
    async stop() {
        return new Promise((resolve) => {
            this.server.tryShutdown(() => {
                log.info('gRPC server stopped');
                resolve();
            });
        });
    }
    sendMessageToAgent(agentId, message) {
        for (const [_, connection] of this.connections.entries()) {
            if (connection.agentId === agentId) {
                connection.call.write(message);
                return true;
            }
        }
        return false;
    }
    broadcastToAll(message) {
        for (const [_, connection] of this.connections.entries()) {
            if (connection.agentId) {
                connection.call.write(message);
            }
        }
    }
    getConnectedAgents() {
        const agents = [];
        for (const [_, connection] of this.connections.entries()) {
            if (connection.agentId) {
                agents.push({
                    agentId: connection.agentId,
                    agentName: connection.agentName,
                    persona: connection.persona,
                });
            }
        }
        return agents;
    }
    getConnectionCount() {
        return this.connections.size;
    }
    createChatMessage(fromAgent, toAgent, content, conversationId, metadata) {
        return {
            conversationId: conversationId || `conv_${Date.now()}`,
            messageId: uuidv4(),
            fromAgent,
            toAgent,
            fromMaster: 'master',
            toMaster: '',
            type: 'chat',
            seq: 0,
            content,
            final: true,
            timestamp: Date.now(),
            receivedAt: 0,
            metadata: metadata || {},
        };
    }
    createToolCallMessage(fromAgent, toAgent, toolName, params, conversationId) {
        return {
            conversationId: conversationId || `tool_${Date.now()}`,
            messageId: uuidv4(),
            fromAgent,
            toAgent,
            fromMaster: 'master',
            toMaster: '',
            type: 'tool_call',
            seq: 0,
            content: JSON.stringify({
                tool: toolName,
                params,
                timestamp: Date.now(),
            }),
            final: true,
            timestamp: Date.now(),
            receivedAt: 0,
            metadata: {},
        };
    }
    createToolResultMessage(fromAgent, toAgent, callId, result, error, conversationId) {
        return {
            conversationId: conversationId || `result_${Date.now()}`,
            messageId: uuidv4(),
            fromAgent,
            toAgent,
            fromMaster: 'master',
            toMaster: '',
            type: 'tool_result',
            seq: 0,
            content: JSON.stringify({
                callId,
                result,
                error,
                timestamp: Date.now(),
            }),
            final: true,
            timestamp: Date.now(),
            receivedAt: 0,
            metadata: {},
        };
    }
}
//# sourceMappingURL=agent-grpc.js.map