/**
 * Vigthoria MCP Client — Model Context Protocol Integration
 * 
 * Connects to MCP servers to discover and execute tools.
 * Enables AI agents to use external tools (filesystem, database,
 * APIs, etc.) via the standardized MCP protocol.
 * 
 * Features:
 * - Auto-discover MCP servers from configuration
 * - List available tools from connected servers
 * - Execute tool calls from AI responses
 * - Handle tool results and inject back into conversation
 * - Connection health monitoring
 */

const vscode = require('vscode');
const { spawn } = require('child_process');
const net = require('net');
const path = require('path');

class VigthoriMCPClient {
    constructor() {
        this.servers = new Map();          // serverId -> { process, config, tools, status }
        this.allTools = new Map();         // toolName -> { serverId, schema }
        this.statusBarItem = null;
        this._requestId = 0;
        this._pendingRequests = new Map(); // id -> { resolve, reject, timeout }

        console.log('[MCP-CLIENT] ✨ Initialized');
    }

    register(context) {
        context.subscriptions.push(
            vscode.commands.registerCommand('vigthoria.mcpListServers', () => this.showServerStatus()),
            vscode.commands.registerCommand('vigthoria.mcpListTools', () => this.showAvailableTools()),
            vscode.commands.registerCommand('vigthoria.mcpReconnect', () => this.reconnectAll()),
            vscode.commands.registerCommand('vigthoria.mcpConfigureServers', () => this.openConfiguration()),
        );

        // Status bar
        this.statusBarItem = vscode.window.createStatusBarItem(
            vscode.StatusBarAlignment.Right, 95
        );
        this.statusBarItem.command = 'vigthoria.mcpListServers';
        this.statusBarItem.text = '$(plug) MCP: --';
        this.statusBarItem.tooltip = 'Vigthoria MCP — Model Context Protocol';
        this.statusBarItem.show();
        context.subscriptions.push(this.statusBarItem);

        // Watch for config changes
        context.subscriptions.push(
            vscode.workspace.onDidChangeConfiguration(e => {
                if (e.affectsConfiguration('vigthoria.mcpServers')) {
                    this.reconnectAll();
                }
            })
        );

        // Auto-connect on startup
        setTimeout(() => this.connectFromConfig(), 3000);

        console.log('[MCP-CLIENT] ✅ Registered');
    }

    /**
     * Load MCP server configs and connect
     */
    async connectFromConfig() {
        const config = vscode.workspace.getConfiguration('vigthoria');
        const serverConfigs = config.get('mcpServers', {});

        // Also check workspace .vigthoria/mcp.json
        const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
        let fileConfigs = {};
        
        if (workspaceRoot) {
            try {
                const mcpConfigPath = path.join(workspaceRoot, '.vigthoria', 'mcp.json');
                const data = await vscode.workspace.fs.readFile(vscode.Uri.file(mcpConfigPath));
                fileConfigs = JSON.parse(Buffer.from(data).toString('utf-8'));
            } catch {
                // No config file, that's fine
            }
        }

        const allConfigs = { ...fileConfigs, ...serverConfigs };

        if (Object.keys(allConfigs).length === 0) {
            this._updateStatusBar(0, 0);
            return;
        }

        for (const [serverId, serverConfig] of Object.entries(allConfigs)) {
            if (serverConfig.disabled) continue;
            await this.connectServer(serverId, serverConfig);
        }

        this._updateStatusBar(
            Array.from(this.servers.values()).filter(s => s.status === 'connected').length,
            this.servers.size
        );
    }

    /**
     * Connect to a single MCP server
     */
    async connectServer(serverId, config) {
        // Disconnect existing connection if any
        this.disconnectServer(serverId);

        const serverState = {
            config,
            process: null,
            tools: [],
            status: 'connecting',
            buffer: '',
        };

        try {
            if (config.transport === 'stdio' || config.command) {
                await this._connectStdio(serverId, serverState, config);
            } else if (config.transport === 'sse' || config.url) {
                await this._connectSSE(serverId, serverState, config);
            } else {
                console.warn(`[MCP-CLIENT] Unknown transport for server "${serverId}"`);
                serverState.status = 'error';
            }

            this.servers.set(serverId, serverState);

            // Initialize the MCP protocol
            if (serverState.status === 'connected') {
                await this._initialize(serverId);
                await this._listTools(serverId);
            }

        } catch (error) {
            console.error(`[MCP-CLIENT] Failed to connect to "${serverId}":`, error);
            serverState.status = 'error';
            this.servers.set(serverId, serverState);
        }
    }

    /**
     * Connect via stdio (spawn process)
     */
    async _connectStdio(serverId, serverState, config) {
        return new Promise((resolve, reject) => {
            const args = config.args || [];
            const env = { ...process.env, ...(config.env || {}) };
            
            const proc = spawn(config.command, args, {
                env,
                cwd: config.cwd || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath,
                stdio: ['pipe', 'pipe', 'pipe'],
            });

            serverState.process = proc;

            proc.stdout.on('data', (data) => {
                serverState.buffer += data.toString();
                this._processBuffer(serverId);
            });

            proc.stderr.on('data', (data) => {
                console.error(`[MCP-CLIENT] ${serverId} stderr:`, data.toString());
            });

            proc.on('error', (error) => {
                console.error(`[MCP-CLIENT] ${serverId} process error:`, error);
                serverState.status = 'error';
                reject(error);
            });

            proc.on('exit', (code) => {
                console.log(`[MCP-CLIENT] ${serverId} exited with code ${code}`);
                serverState.status = 'disconnected';
                this._updateStatusBar(
                    Array.from(this.servers.values()).filter(s => s.status === 'connected').length,
                    this.servers.size
                );
            });

            // Give it a moment to start
            setTimeout(() => {
                if (proc.exitCode === null) {
                    serverState.status = 'connected';
                    resolve();
                } else {
                    reject(new Error(`Process exited with code ${proc.exitCode}`));
                }
            }, 1000);
        });
    }

    /**
     * Connect via SSE (Server-Sent Events)
     */
    async _connectSSE(serverId, serverState, config) {
        // SSE connection for HTTP-based MCP servers
        serverState.url = config.url;
        serverState.status = 'connected';
        serverState.transport = 'sse';
        // Actual SSE handling would use fetch/EventSource for streaming
        console.log(`[MCP-CLIENT] SSE connection to ${config.url} configured`);
    }

    /**
     * Send JSON-RPC request to MCP server
     */
    async _sendRequest(serverId, method, params = {}) {
        const server = this.servers.get(serverId);
        if (!server || server.status !== 'connected') {
            throw new Error(`Server "${serverId}" not connected`);
        }

        const id = ++this._requestId;
        const request = {
            jsonrpc: '2.0',
            id,
            method,
            params,
        };

        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                this._pendingRequests.delete(id);
                reject(new Error(`Request ${method} timed out`));
            }, 30000);

            this._pendingRequests.set(id, { resolve, reject, timeout });

            if (server.process?.stdin) {
                const message = JSON.stringify(request) + '\n';
                server.process.stdin.write(message);
            } else if (server.transport === 'sse') {
                // HTTP POST for SSE transport
                this._sendHTTP(server.url, request)
                    .then(resolve)
                    .catch(reject);
            }
        });
    }

    async _sendHTTP(url, request) {
        try {
            const response = await fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(request),
            });
            return await response.json();
        } catch (error) {
            throw new Error(`HTTP request failed: ${error.message}`);
        }
    }

    /**
     * Process incoming buffer for JSON-RPC responses
     */
    _processBuffer(serverId) {
        const server = this.servers.get(serverId);
        if (!server) return;

        const lines = server.buffer.split('\n');
        server.buffer = lines.pop() || '';  // Keep incomplete line

        for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed) continue;

            try {
                const message = JSON.parse(trimmed);
                this._handleMessage(serverId, message);
            } catch {
                // Not valid JSON, skip
            }
        }
    }

    /**
     * Handle incoming JSON-RPC message
     */
    _handleMessage(serverId, message) {
        if (message.id && this._pendingRequests.has(message.id)) {
            const pending = this._pendingRequests.get(message.id);
            clearTimeout(pending.timeout);
            this._pendingRequests.delete(message.id);

            if (message.error) {
                pending.reject(new Error(message.error.message));
            } else {
                pending.resolve(message.result);
            }
        } else if (message.method) {
            // Server-initiated notification
            this._handleNotification(serverId, message);
        }
    }

    _handleNotification(serverId, notification) {
        console.log(`[MCP-CLIENT] Notification from ${serverId}:`, notification.method);
        
        if (notification.method === 'notifications/tools/list_changed') {
            this._listTools(serverId).catch(console.error);
        }
    }

    /**
     * Initialize MCP protocol handshake
     */
    async _initialize(serverId) {
        try {
            const result = await this._sendRequest(serverId, 'initialize', {
                protocolVersion: '2024-11-05',
                capabilities: {
                    tools: {},
                },
                clientInfo: {
                    name: 'vigthoria-code',
                    version: '2.0.1'
                }
            });

            console.log(`[MCP-CLIENT] ${serverId} initialized:`, result?.serverInfo);

            // Send initialized notification
            const server = this.servers.get(serverId);
            if (server?.process?.stdin) {
                const notification = JSON.stringify({
                    jsonrpc: '2.0',
                    method: 'notifications/initialized'
                }) + '\n';
                server.process.stdin.write(notification);
            }

        } catch (error) {
            console.error(`[MCP-CLIENT] Init failed for ${serverId}:`, error);
        }
    }

    /**
     * List tools from an MCP server
     */
    async _listTools(serverId) {
        try {
            const result = await this._sendRequest(serverId, 'tools/list');
            const server = this.servers.get(serverId);
            
            if (server && result?.tools) {
                server.tools = result.tools;
                
                // Register tools in global map
                for (const tool of result.tools) {
                    this.allTools.set(tool.name, {
                        serverId,
                        schema: tool.inputSchema,
                        description: tool.description
                    });
                }

                console.log(`[MCP-CLIENT] ${serverId}: ${result.tools.length} tools available`);
            }
        } catch (error) {
            console.error(`[MCP-CLIENT] Tool listing failed for ${serverId}:`, error);
        }
    }

    /**
     * Execute a tool call
     * 
     * @param {string} toolName - Name of the tool to call
     * @param {object} arguments - Tool arguments
     * @returns {object} Tool result
     */
    async callTool(toolName, args = {}) {
        const toolInfo = this.allTools.get(toolName);
        if (!toolInfo) {
            throw new Error(`Unknown tool: ${toolName}`);
        }

        const result = await this._sendRequest(toolInfo.serverId, 'tools/call', {
            name: toolName,
            arguments: args,
        });

        return result;
    }

    /**
     * Get all available tools formatted for AI context
     */
    getToolDescriptions() {
        if (this.allTools.size === 0) return '';

        let desc = '### Available MCP Tools:\n\n';
        
        for (const [name, info] of this.allTools) {
            desc += `- **${name}**: ${info.description || 'No description'}\n`;
            if (info.schema?.properties) {
                desc += `  Parameters: ${Object.keys(info.schema.properties).join(', ')}\n`;
            }
        }

        return desc;
    }

    /**
     * Parse AI response for tool call requests
     */
    parseToolCalls(responseText) {
        const toolCalls = [];
        
        // Look for <tool_call> or ```tool_call blocks
        const patterns = [
            /<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/g,
            /```tool_call\s*\n([\s\S]*?)```/g,
            /\[TOOL_CALL:\s*(\w+)\(([\s\S]*?)\)\]/g,
        ];

        for (const pattern of patterns) {
            let match;
            while ((match = pattern.exec(responseText)) !== null) {
                try {
                    if (match[2] !== undefined) {
                        // [TOOL_CALL: name(args)] format
                        toolCalls.push({
                            name: match[1],
                            arguments: JSON.parse(match[2] || '{}')
                        });
                    } else {
                        const parsed = JSON.parse(match[1]);
                        toolCalls.push({
                            name: parsed.name || parsed.tool,
                            arguments: parsed.arguments || parsed.params || parsed.input || {}
                        });
                    }
                } catch {
                    // Invalid JSON, skip
                }
            }
        }

        return toolCalls;
    }

    /**
     * Execute parsed tool calls and return results
     */
    async executeToolCalls(toolCalls) {
        const results = [];

        for (const call of toolCalls) {
            try {
                const result = await this.callTool(call.name, call.arguments);
                results.push({
                    tool: call.name,
                    success: true,
                    result: result
                });
            } catch (error) {
                results.push({
                    tool: call.name,
                    success: false,
                    error: error.message
                });
            }
        }

        return results;
    }

    /**
     * Format tool results for AI context
     */
    formatToolResults(results) {
        if (results.length === 0) return '';

        let text = '### Tool Results:\n\n';
        
        for (const r of results) {
            if (r.success) {
                const content = typeof r.result === 'string' ? r.result :
                    r.result?.content?.[0]?.text || JSON.stringify(r.result, null, 2);
                text += `**${r.tool}** ✅:\n\`\`\`\n${content}\n\`\`\`\n\n`;
            } else {
                text += `**${r.tool}** ❌: ${r.error}\n\n`;
            }
        }

        return text;
    }

    // --- UI Commands ---

    showServerStatus() {
        if (this.servers.size === 0) {
            vscode.window.showInformationMessage(
                'No MCP servers configured. Add servers in settings or .vigthoria/mcp.json',
                'Configure'
            ).then(choice => {
                if (choice === 'Configure') this.openConfiguration();
            });
            return;
        }

        const items = Array.from(this.servers.entries()).map(([id, server]) => {
            const icon = server.status === 'connected' ? '🟢' : 
                         server.status === 'connecting' ? '🟡' : '🔴';
            return {
                label: `${icon} ${id}`,
                description: `${server.status} — ${server.tools.length} tools`,
                detail: server.config.command || server.config.url || ''
            };
        });

        vscode.window.showQuickPick(items, {
            placeHolder: 'MCP Servers'
        });
    }

    async showAvailableTools() {
        if (this.allTools.size === 0) {
            vscode.window.showInformationMessage('No MCP tools available');
            return;
        }

        const items = Array.from(this.allTools.entries()).map(([name, info]) => ({
            label: `$(tools) ${name}`,
            description: info.serverId,
            detail: info.description || 'No description'
        }));

        await vscode.window.showQuickPick(items, {
            placeHolder: `${this.allTools.size} MCP tools available`
        });
    }

    async openConfiguration() {
        const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
        if (!workspaceRoot) {
            vscode.window.showWarningMessage('No workspace open');
            return;
        }

        const configPath = path.join(workspaceRoot, '.vigthoria', 'mcp.json');
        
        try {
            await vscode.workspace.fs.stat(vscode.Uri.file(configPath));
        } catch {
            // Create default config
            const defaultConfig = {
                "vigthoria-mcp-server": {
                    "command": "node",
                    "args": ["/var/www/vigthoria-mcp-server/server.js"],
                    "env": {},
                    "disabled": false
                }
            };
            
            const dir = path.join(workspaceRoot, '.vigthoria');
            await vscode.workspace.fs.createDirectory(vscode.Uri.file(dir));
            await vscode.workspace.fs.writeFile(
                vscode.Uri.file(configPath),
                Buffer.from(JSON.stringify(defaultConfig, null, 2))
            );
        }

        const doc = await vscode.workspace.openTextDocument(configPath);
        await vscode.window.showTextDocument(doc);
    }

    async reconnectAll() {
        // Disconnect all
        for (const [serverId] of this.servers) {
            this.disconnectServer(serverId);
        }
        this.allTools.clear();

        // Reconnect
        await this.connectFromConfig();
        
        const connected = Array.from(this.servers.values()).filter(s => s.status === 'connected').length;
        vscode.window.showInformationMessage(`MCP: ${connected}/${this.servers.size} servers connected`);
    }

    disconnectServer(serverId) {
        const server = this.servers.get(serverId);
        if (server) {
            if (server.process) {
                server.process.kill();
            }
            // Remove tools from global map
            for (const tool of server.tools) {
                this.allTools.delete(tool.name);
            }
            this.servers.delete(serverId);
        }
    }

    _updateStatusBar(connected, total) {
        if (!this.statusBarItem) return;

        if (total === 0) {
            this.statusBarItem.text = '$(plug) MCP: --';
            this.statusBarItem.tooltip = 'No MCP servers configured';
        } else {
            this.statusBarItem.text = `$(plug) MCP: ${connected}/${total}`;
            this.statusBarItem.tooltip = `MCP: ${connected} connected, ${this.allTools.size} tools`;
        }
    }

    dispose() {
        for (const [serverId] of this.servers) {
            this.disconnectServer(serverId);
        }
        if (this.statusBarItem) this.statusBarItem.dispose();
        
        for (const pending of this._pendingRequests.values()) {
            clearTimeout(pending.timeout);
            pending.reject(new Error('MCP client disposed'));
        }
        this._pendingRequests.clear();
    }
}

module.exports = { VigthoriMCPClient };
