#!/usr/bin/env node
/**
 * TIP MCP HTTP Server — SSE Transport
 *
 * Exposes all 12 TIP MCP tools over HTTP/SSE so the server can be registered
 * in Claude Code's ~/.mcp.json as a remote MCP server.
 *
 * Endpoints:
 *   GET  /health   — Health check: { status: "ok", tools: 12 }
 *   GET  /sse      — Opens SSE stream, returns sessionId in endpoint event
 *   POST /message  — Client-to-server messages (requires ?sessionId=...)
 *
 * Auth:
 *   All endpoints (except /health) require:
 *   Authorization: Bearer <MCP_SECRET>
 *
 * Config (env):
 *   MCP_HTTP_PORT   — Listening port (default: 3202)
 *   MCP_SECRET      — Bearer token for auth (required in production)
 *   CORS_ORIGINS    — Comma-separated allowed origins (default: localhost + 127.0.0.1)
 *
 * ~/.mcp.json entry:
 *   {
 *     "tip": {
 *       "type": "sse",
 *       "url": "http://localhost:3202/sse",
 *       "headers": { "Authorization": "Bearer <MCP_SECRET>" }
 *     }
 *   }
 */
import express, { type Request, type Response, type NextFunction } from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { z } from "zod";
import { pool } from "./db.js";
import { registerPricingTools } from "./tools/pricing.js";
import { registerCompatibilityTools } from "./tools/compatibility.js";
import { registerKnowledgeTools } from "./tools/knowledge.js";
import { registerContentTools } from "./tools/content.js";
import { registerSwitchDocTools } from "./tools/switch-docs.js";
import { registerMarketTools } from "./tools/market.js";

// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------

const PORT = parseInt(process.env.MCP_HTTP_PORT ?? "3202", 10);
const MCP_SECRET = process.env.MCP_SECRET ?? "";

const CORS_ORIGINS: string[] = [
  "http://localhost",
  "http://127.0.0.1",
  ...(process.env.CORS_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean),
];

// ---------------------------------------------------------------------------
// Tool count (keep in sync with index.ts tools + tool files)
// search_transceivers, check_compatibility (index.ts) = 2
// pricing.ts: get_pricing, compare_prices, get_competitor_stock = 3
// compatibility.ts: suggest_alternatives, get_templates = 2
// knowledge.ts: search_knowledge_base, search_manuals, get_hype_cycle = 3
// content.ts: get_market_news, generate_blog_draft = 2
// switch-docs.ts: get_switch_docs, search_switches = 2
// market.ts: get_cable_recommendations, get_market_overview, get_technology_roadmap = 3
// Total = 17 registered
// ---------------------------------------------------------------------------
const TOOL_COUNT = 17;

// ---------------------------------------------------------------------------
// Build a new McpServer and register all tools (one server per SSE session)
// ---------------------------------------------------------------------------
async function createMcpServer(): Promise<McpServer> {
  const server = new McpServer({
    name: "tip-mcp-server",
    version: "0.1.0",
  });

  // --- Tool: search_transceivers ---
  server.tool(
    "search_transceivers",
    "Search transceivers by free text, specs, or compatibility. Returns matching transceivers with current pricing if available.",
    {
      query: z.string().optional().describe("Free text query, e.g. '10km for Cisco Nexus' or '400G QSFP-DD ZR'"),
      form_factor: z.string().optional().describe("SFP, SFP+, SFP28, QSFP+, QSFP28, QSFP-DD, OSFP, CFP2, etc."),
      speed_gbps: z.number().optional().describe("Speed in Gbps: 1, 10, 25, 40, 100, 200, 400, 800"),
      reach_label: z.string().optional().describe("SR, LR, ER, ZR, or distance like 10km, 80km"),
      fiber_type: z.enum(["SMF", "MMF"]).optional().describe("Single-mode or Multi-mode fiber"),
      wdm_type: z.enum(["CWDM", "DWDM"]).optional().describe("Wavelength division multiplexing type"),
      vendor: z.string().optional().describe("Vendor filter, e.g. 'Cisco', 'Juniper', 'FS.COM'"),
      max_results: z.number().default(10).describe("Maximum results to return"),
    },
    async ({ query, form_factor, speed_gbps, reach_label, fiber_type, wdm_type, vendor, max_results }) => {
      const conditions: string[] = [];
      const values: unknown[] = [];
      let idx = 1;

      if (query) {
        conditions.push(`t.search_vector @@ plainto_tsquery('english', $${idx})`);
        values.push(query);
        idx++;
      }
      if (form_factor) {
        conditions.push(`t.form_factor ILIKE $${idx}`);
        values.push(`%${form_factor}%`);
        idx++;
      }
      if (speed_gbps) {
        conditions.push(`t.speed_gbps = $${idx}`);
        values.push(speed_gbps);
        idx++;
      }
      if (reach_label) {
        conditions.push(`(t.reach_label ILIKE $${idx} OR t.standard_name ILIKE $${idx})`);
        values.push(`%${reach_label}%`);
        idx++;
      }
      if (fiber_type) {
        conditions.push(`t.fiber_type = $${idx}`);
        values.push(fiber_type);
        idx++;
      }
      if (wdm_type) {
        conditions.push(`t.wdm_type = $${idx}`);
        values.push(wdm_type);
        idx++;
      }
      if (vendor) {
        conditions.push(`v.name ILIKE $${idx}`);
        values.push(`%${vendor}%`);
        idx++;
      }

      const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
      const orderBy = query
        ? `ORDER BY ts_rank(t.search_vector, plainto_tsquery('english', $1)) DESC`
        : "ORDER BY t.speed_gbps DESC, t.reach_meters ASC";

      values.push(max_results);

      const result = await pool.query(
        `SELECT t.id, t.slug, t.standard_name, t.form_factor, t.speed, t.speed_gbps,
                t.reach_label, t.reach_meters, t.fiber_type, t.connector, t.wdm_type,
                t.wavelengths, t.power_consumption_w, t.temp_range, t.category,
                v.name as vendor_name,
                (SELECT jsonb_agg(jsonb_build_object(
                  'vendor', sv.name, 'price', po.price, 'currency', po.currency,
                  'stock', po.stock_level, 'url', po.url
                ) ORDER BY po.time DESC)
                 FROM price_observations po
                 JOIN vendors sv ON sv.id = po.source_vendor_id
                 WHERE po.transceiver_id = t.id
                   AND po.time > NOW() - INTERVAL '7 days'
                ) as pricing
         FROM transceivers t
         LEFT JOIN vendors v ON v.id = t.vendor_id
         ${where}
         ${orderBy}
         LIMIT $${idx}`,
        values
      );

      if (result.rows.length === 0) {
        return {
          content: [{ type: "text", text: "No transceivers found matching your criteria." }],
        };
      }

      const formatted = result.rows.map((r) => ({
        slug: r.slug,
        standard: r.standard_name,
        form_factor: r.form_factor,
        speed: r.speed,
        reach: r.reach_label,
        fiber: r.fiber_type,
        connector: r.connector,
        wdm: r.wdm_type,
        wavelengths: r.wavelengths,
        power_w: r.power_consumption_w,
        temp: r.temp_range,
        category: r.category,
        vendor: r.vendor_name,
        pricing: r.pricing || [],
      }));

      return {
        content: [{
          type: "text",
          text: JSON.stringify({ count: result.rows.length, transceivers: formatted }, null, 2),
        }],
      };
    }
  );

  // --- Tool: check_compatibility ---
  server.tool(
    "check_compatibility",
    "Check compatibility between a switch model and transceivers. Returns verified compatible transceivers with firmware requirements.",
    {
      switch_model: z.string().describe("Switch model, e.g. 'Cisco Nexus 93180YC-FX3' or 'Juniper EX4300'"),
      transceiver_query: z.string().optional().describe("Optional: filter by transceiver type or part number"),
      speed_gbps: z.number().optional().describe("Optional: filter by speed"),
      reach: z.string().optional().describe("Optional: filter by reach (SR, LR, etc.)"),
    },
    async ({ switch_model, transceiver_query, speed_gbps, reach }) => {
      const switchResult = await pool.query(
        `SELECT s.id, s.model, s.series, v.name as vendor
         FROM switches s
         JOIN vendors v ON v.id = s.vendor_id
         WHERE s.model ILIKE $1 OR s.series ILIKE $1
         LIMIT 5`,
        [`%${switch_model}%`]
      );

      if (switchResult.rows.length === 0) {
        return {
          content: [{
            type: "text",
            text: `No switch found matching "${switch_model}". Try a shorter model name or check spelling.`,
          }],
        };
      }

      const sw = switchResult.rows[0];
      const conditions = [`c.switch_id = $1`];
      const values: unknown[] = [sw.id];
      let idx = 2;

      if (transceiver_query) {
        conditions.push(`(t.standard_name ILIKE $${idx} OR t.slug ILIKE $${idx})`);
        values.push(`%${transceiver_query}%`);
        idx++;
      }
      if (speed_gbps) {
        conditions.push(`t.speed_gbps = $${idx}`);
        values.push(speed_gbps);
        idx++;
      }
      if (reach) {
        conditions.push(`t.reach_label ILIKE $${idx}`);
        values.push(`%${reach}%`);
        idx++;
      }

      const compatResult = await pool.query(
        `SELECT t.slug, t.standard_name, t.form_factor, t.speed, t.reach_label,
                t.fiber_type, c.status, c.firmware_min, c.verified_by, c.verification_method
         FROM compatibility c
         JOIN transceivers t ON t.id = c.transceiver_id
         WHERE ${conditions.join(" AND ")}
         AND c.status = 'compatible'
         ORDER BY t.speed_gbps DESC, t.reach_meters ASC
         LIMIT 20`,
        values
      );

      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            switch: { model: sw.model, series: sw.series, vendor: sw.vendor },
            compatible_transceivers: compatResult.rows,
            count: compatResult.rows.length,
          }, null, 2),
        }],
      };
    }
  );

  // Register remaining tools from tool modules
  await registerPricingTools(server);
  await registerCompatibilityTools(server);
  await registerKnowledgeTools(server);
  await registerContentTools(server);
  await registerSwitchDocTools(server);
  await registerMarketTools(server);

  return server;
}

// ---------------------------------------------------------------------------
// Auth middleware
// ---------------------------------------------------------------------------
function requireAuth(req: Request, res: Response, next: NextFunction): void {
  if (!MCP_SECRET) {
    // No secret configured — skip auth (development mode)
    next();
    return;
  }

  const authHeader = req.headers["authorization"] ?? "";
  const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";

  if (token !== MCP_SECRET) {
    res.status(401).json({ error: "Unauthorized: invalid or missing bearer token" });
    return;
  }

  next();
}

// ---------------------------------------------------------------------------
// CORS middleware
// ---------------------------------------------------------------------------
function applyCors(req: Request, res: Response, next: NextFunction): void {
  const origin = req.headers["origin"] ?? "";
  const isAllowed = CORS_ORIGINS.some((allowed) =>
    origin === allowed || origin.startsWith(allowed)
  );

  if (isAllowed) {
    res.setHeader("Access-Control-Allow-Origin", origin);
  }
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  res.setHeader("Access-Control-Allow-Credentials", "true");

  if (req.method === "OPTIONS") {
    res.sendStatus(204);
    return;
  }

  next();
}

// ---------------------------------------------------------------------------
// Session registry: sessionId → SSEServerTransport
// ---------------------------------------------------------------------------
const sessions = new Map<string, SSEServerTransport>();

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
async function main(): Promise<void> {
  const app = express();
  app.use(express.json());
  app.use(applyCors);

  // --- GET /health ---
  app.get("/health", (_req: Request, res: Response) => {
    res.json({ status: "ok", tools: TOOL_COUNT });
  });

  // --- GET /sse --- open SSE stream
  app.get("/sse", requireAuth, async (req: Request, res: Response) => {
    const transport = new SSEServerTransport("/message", res);

    // Register session before starting so POST /message can find it immediately
    sessions.set(transport.sessionId, transport);

    transport.onclose = () => {
      sessions.delete(transport.sessionId);
    };

    // Each SSE connection gets its own McpServer instance
    const server = await createMcpServer();
    await server.connect(transport);

    // Propagate close event from request disconnect
    req.on("close", () => {
      transport.close().catch(() => {
        // ignore errors on close
      });
    });
  });

  // --- POST /message --- receive client messages
  app.post("/message", requireAuth, async (req: Request, res: Response) => {
    const sessionId = req.query["sessionId"] as string | undefined;

    if (!sessionId) {
      res.status(400).json({ error: "Missing required query parameter: sessionId" });
      return;
    }

    const transport = sessions.get(sessionId);

    if (!transport) {
      res.status(404).json({ error: `No active SSE session for sessionId: ${sessionId}` });
      return;
    }

    await transport.handlePostMessage(req, res, req.body);
  });

  const httpServer = app.listen(PORT, () => {
    console.log(`TIP MCP HTTP server listening on port ${PORT}`);
    console.log(`  SSE endpoint:     http://localhost:${PORT}/sse`);
    console.log(`  Message endpoint: http://localhost:${PORT}/message`);
    console.log(`  Health endpoint:  http://localhost:${PORT}/health`);
    if (!MCP_SECRET) {
      console.warn("  WARNING: MCP_SECRET is not set — auth is disabled (development mode only)");
    }
  });

  // Graceful shutdown
  process.on("SIGINT", async () => {
    for (const transport of sessions.values()) {
      await transport.close().catch(() => {
        // ignore errors on close
      });
    }
    sessions.clear();
    await pool.end();
    httpServer.close(() => {
      process.exit(0);
    });
  });
}

main().catch((err: unknown) => {
  console.error("Fatal TIP MCP HTTP server error:", err);
  process.exit(1);
});
