import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { z } from 'zod';

const PORT = parseInt(process.env.PORT || '3102');
const SEARXNG_URL = (process.env.SEARXNG_URL || 'http://localhost:11437').replace(/\/$/, '');

function createServer() {
  const server = new McpServer({ name: 'searxng-mcp', version: '1.0.0' });

  server.tool(
    'searxng_search',
    'Search the web using the self-hosted SearXNG meta-search engine. Returns titles, URLs, and content snippets from multiple search engines.',
    {
      query: z.string().describe('Search query'),
      categories: z.enum(['general', 'news', 'images', 'videos', 'science', 'files', 'social_media', 'it'])
        .optional()
        .describe('Search category, default: general'),
      language: z.string().optional().describe('Language code (e.g. "ru", "en", "auto"), default: auto'),
      time_range: z.enum(['day', 'week', 'month', 'year']).optional().describe('Limit results to time range'),
      limit: z.number().optional().describe('Max results to return, default 10'),
    },
    async ({ query, categories, language, time_range, limit = 10 }) => {
      try {
        const params = new URLSearchParams({
          q: query,
          format: 'json',
          categories: categories || 'general',
          language: language || 'auto',
        });
        if (time_range) params.set('time_range', time_range);

        const res = await fetch(`${SEARXNG_URL}/search?${params}`);
        if (!res.ok) {
          return { content: [{ type: 'text', text: `SearXNG returned HTTP ${res.status}` }], isError: true };
        }

        const data = await res.json();
        const results = (data.results || []).slice(0, limit).map(r => ({
          title: r.title || null,
          url: r.url || null,
          content: (r.content || '').slice(0, 500) || null,
          engine: r.engine || null,
          score: r.score != null ? Math.round(r.score * 100) / 100 : null,
          publishedDate: r.publishedDate || null,
        }));

        const out = {
          query: data.query,
          totalResults: data.number_of_results,
          count: results.length,
          results,
        };

        return { content: [{ type: 'text', text: JSON.stringify(out, null, 2) }] };
      } catch (e) {
        return { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
      }
    }
  );

  return server;
}

const app = express();
app.use(express.json());

const sseTransports = new Map();

app.all('/mcp', async (req, res) => {
  try {
    const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
    res.on('close', () => transport.close());
    await createServer().connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (e) {
    if (!res.headersSent) res.status(500).json({ error: e.message });
  }
});

app.get('/sse', async (req, res) => {
  const transport = new SSEServerTransport('/messages', res);
  sseTransports.set(transport.sessionId, transport);
  res.on('close', () => sseTransports.delete(transport.sessionId));
  await createServer().connect(transport);
});

app.post('/messages', async (req, res) => {
  const transport = sseTransports.get(req.query.sessionId);
  if (!transport) return res.status(400).send('Unknown session');
  await transport.handlePostMessage(req, res);
});

app.get('/health', (_, res) => res.json({ status: 'ok', searxng: SEARXNG_URL }));

app.listen(PORT, () => console.log(`searxng-mcp listening on :${PORT}`));
