const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} = require('@modelcontextprotocol/sdk/types.js');
const axios = require('axios');
const { spawn } = require('child_process');
const path = require('path');

/**
 * MCP Server — Dynamic Tool Discovery
 *
 * Connects to the running Electron app's REST API and:
 *   1. Fetches the dynamic tool list from /api/mcp/tools (McpAggregator)
 *   2. Routes all tool calls through /api/mcp/call
 *
 * This means extension MCP tools are automatically exposed to external MCP clients
 * (Claude Desktop, LM Studio, etc.) without any code changes here.
 *
 * Auto-starts the Electron app if it's not already running.
 */
class McpServer {
  constructor(apiBaseUrl) {
    const port = process.env.LUMA_API_PORT || '3000';
    const host = process.env.LUMA_API_HOST || '127.0.0.1';
    this.apiBase = apiBaseUrl || `http://${host}:${port}/api`;
    this.healthUrl = `http://${host}:${port}/api/health`;
    this.apiKey = process.env.LUMA_API_KEY || null;
    this.electronProcess = null;
    this.autoStarted = false;

    this.server = new Server(
      {
        name: 'luma-browser',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupToolHandlers();
  }

  // Returns axios request options with Authorization: Bearer <key> when
  // LUMA_API_KEY is set. When the app-side policy has requireApiKey off the
  // header is ignored by the middleware, so setting the env var is safe either way.
  _authOptions(extra = {}) {
    const opts = { ...extra };
    if (this.apiKey) {
      opts.headers = { ...(opts.headers || {}), Authorization: `Bearer ${this.apiKey}` };
    }
    return opts;
  }

  setupToolHandlers() {
    // Dynamic tool listing — fetches from the running app's McpAggregator
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      try {
        const response = await axios.get(`${this.apiBase}/mcp/tools`, this._authOptions({ timeout: 5000 }));
        return { tools: response.data.tools || [] };
      } catch (error) {
        console.error('Failed to fetch tool list:', error.message);
        return { tools: [] };
      }
    });

    // Generic tool call handler — routes through McpAggregator
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        const response = await axios.post(`${this.apiBase}/mcp/call`, {
          name,
          arguments: args,
        }, this._authOptions({ timeout: 300000 }));

        return response.data;
      } catch (error) {
        const errorMessage = error.response?.data?.content?.[0]?.text
          || error.response?.data?.error
          || error.message
          || 'Tool call failed';

        return {
          content: [{
            type: 'text',
            text: JSON.stringify({ success: false, error: errorMessage }, null, 2),
          }],
        };
      }
    });
  }

  /**
   * Check if the Electron app is running
   */
  async isElectronRunning() {
    try {
      await axios.get(this.healthUrl, { timeout: 2000 });
      return true;
    } catch (error) {
      return false;
    }
  }

  /**
   * Start the Electron app in headless mode
   */
  async startElectronApp() {
    let appPath;
    let args = [];

    // Detect if running from installed app (app.asar.unpacked) or dev source
    const isInstalledApp = __dirname.includes('app.asar.unpacked');

    if (isInstalledApp) {
      const resourcesDir = path.join(__dirname, '..', '..');
      const appDir = path.join(resourcesDir, '..');
      if (process.platform === 'win32') {
        appPath = path.join(appDir, 'LumaBrowser.exe');
      } else if (process.platform === 'darwin') {
        appPath = path.join(resourcesDir, '..', 'MacOS', 'LumaBrowser');
      } else {
        appPath = path.join(appDir, 'lumabrowser');
        if (!require('fs').existsSync(appPath)) {
          appPath = path.join(appDir, 'LumaBrowser');
        }
      }
    } else {
      appPath = require('electron');
      args = [path.join(__dirname, '..', '..')];
    }

    console.error('Starting Electron app in headless mode...');

    this.electronProcess = spawn(appPath, args, {
      stdio: 'ignore',
      detached: false,
      env: {
        ...process.env,
        ELECTRON_ENABLE_LOGGING: '0',
        ELECTRON_NO_ATTACH_CONSOLE: '1'
      }
    });

    this.autoStarted = true;

    this.electronProcess.on('error', (error) => {
      console.error('Failed to start Electron app:', error);
      this.electronProcess = null;
      this.autoStarted = false;
    });

    this.electronProcess.on('exit', (code, signal) => {
      if (this.autoStarted) {
        console.error(`Electron app exited with code ${code} and signal ${signal}`);
      }
      this.electronProcess = null;
      this.autoStarted = false;
    });

    // Wait for the app to be ready
    const maxAttempts = 30;
    for (let i = 0; i < maxAttempts; i++) {
      await new Promise(resolve => setTimeout(resolve, 1000));
      if (await this.isElectronRunning()) {
        console.error('Electron app is ready!');
        return;
      }
    }

    throw new Error('Electron app failed to start within 30 seconds');
  }

  /**
   * Stop the Electron app if it was auto-started
   */
  async stop() {
    if (this.electronProcess && this.autoStarted) {
      console.error('Stopping auto-started Electron app...');
      this.electronProcess.kill();
      this.electronProcess = null;
      this.autoStarted = false;
    }
  }

  async start() {
    const isRunning = await this.isElectronRunning();

    if (!isRunning) {
      await this.startElectronApp();
    } else {
      console.error('Electron app is already running, using existing instance.');
    }

    const transport = new StdioServerTransport();
    await this.server.connect(transport);
  }
}

module.exports = McpServer;
