#!/usr/bin/env node
/**
 * TIP MCP Server — Transceiver Intelligence Platform
 *
 * 15 Tools for LLM access to transceiver data, pricing, compatibility,
 * hype cycle, knowledge base, news, market intelligence, and blog generation.
 *
 * Transport: stdio (for Claude Code, EO Global Pulse, etc.)
 *
 * Usage:
 *   tsx src/index.ts                    — Run MCP server via stdio
 *   npx @tip/mcp-server                 — After npm install -g
 *
 * Claude Code config (~/.claude/mcp.json):
 *   { "tip": { "command": "npx", "args": ["@tip/mcp-server"] } }
 */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.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 { registerMarketTools } from "./tools/market.js";
import { registerSwitchDocTools } from "./tools/switch-docs.js";
import { finderTools, handleFinderTool } from "./tools/finder.js";
import { registerEquivalencesTools } from "./tools/equivalences.js";

async function main() {
  const server = new McpServer({
    name: "tip-mcp-server",
    version: "0.2.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/manufacturer filter, e.g. 'Cisco', 'Juniper', 'FS.COM', 'Flexoptix'"),
      category: z.string().optional().describe("Category filter: DataCenter, AOC, DAC, DWDM, CWDM, Coherent, Metro, LongHaul, etc."),
      market_status: z.enum(["Mainstream", "Growth", "Emerging", "Legacy", "EOL"]).optional().describe("Market status filter"),
      max_results: z.number().default(10).describe("Maximum results to return"),
    },
    async ({ query, form_factor, speed_gbps, reach_label, fiber_type, wdm_type, vendor, category, market_status, 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++;
      }
      if (category) {
        conditions.push(`t.category ILIKE $${idx}`);
        values.push(`%${category}%`);
        idx++;
      }
      if (market_status) {
        conditions.push(`t.market_status = $${idx}`);
        values.push(market_status);
        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,
                t.market_status, t.hype_cycle_phase,
                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,
        speed_gbps: r.speed_gbps,
        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,
        market_status: r.market_status,
        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. When no exact match is found, suggests alternative transceivers that may work.",
    {
      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 }) => {
      // First: find the switch
      const switchResult = await pool.query(
        `SELECT s.id, s.model, s.series, s.max_speed_gbps, s.ports_config,
                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) {
        // Suggest similar switches using trigram similarity
        const similarResult = await pool.query(
          `SELECT s.model, s.series, v.name as vendor,
                  similarity(s.model, $1) as sim
           FROM switches s
           JOIN vendors v ON v.id = s.vendor_id
           WHERE similarity(s.model, $1) > 0.1
           ORDER BY sim DESC
           LIMIT 5`,
          [switch_model]
        );

        const suggestions = similarResult.rows.length > 0
          ? `\n\nDid you mean one of these?\n${similarResult.rows.map(r => `  - ${r.vendor} ${r.model} (${r.series})`).join("\n")}`
          : "";

        return {
          content: [{
            type: "text",
            text: `No switch found matching "${switch_model}". Try a shorter model name or check spelling.${suggestions}`,
          }],
        };
      }

      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.id, t.slug, t.standard_name, t.form_factor, t.speed, t.speed_gbps,
                t.reach_label, t.reach_meters, t.fiber_type,
                c.status, c.firmware_min, c.verified_by, c.verification_method, c.notes as compat_notes,
                (SELECT MIN(po.price) FROM price_observations po
                 WHERE po.transceiver_id = t.id AND po.time > NOW() - INTERVAL '7 days'
                ) as min_price
         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
      );

      // If no compatible transceivers found, suggest alternatives
      let alternatives: unknown[] = [];
      if (compatResult.rows.length === 0) {
        // Find what form factors / speeds this switch supports based on ports_config
        const portSpeeds: number[] = [];
        if (sw.max_speed_gbps) portSpeeds.push(parseFloat(sw.max_speed_gbps));

        // Try to find transceivers that match the requested criteria even without verified compatibility
        const altConditions: string[] = [];
        const altValues: unknown[] = [];
        let altIdx = 1;

        if (transceiver_query) {
          altConditions.push(`(t.standard_name ILIKE $${altIdx} OR t.slug ILIKE $${altIdx})`);
          altValues.push(`%${transceiver_query}%`);
          altIdx++;
        }
        if (speed_gbps) {
          altConditions.push(`t.speed_gbps = $${altIdx}`);
          altValues.push(speed_gbps);
          altIdx++;
        }
        if (reach) {
          altConditions.push(`t.reach_label ILIKE $${altIdx}`);
          altValues.push(`%${reach}%`);
          altIdx++;
        }

        const altWhere = altConditions.length > 0 ? `WHERE ${altConditions.join(" AND ")}` : "";

        const altResult = await pool.query(
          `SELECT t.slug, t.standard_name, t.form_factor, t.speed, t.speed_gbps,
                  t.reach_label, t.fiber_type, v.name as vendor,
                  (SELECT MIN(po.price) FROM price_observations po
                   WHERE po.transceiver_id = t.id AND po.time > NOW() - INTERVAL '7 days'
                  ) as min_price,
                  -- Check if this transceiver is compatible with OTHER switches from the same vendor
                  (SELECT COUNT(*) FROM compatibility c2
                   JOIN switches sw2 ON sw2.id = c2.switch_id
                   WHERE c2.transceiver_id = t.id
                     AND c2.status = 'compatible'
                     AND sw2.vendor_id = (SELECT vendor_id FROM switches WHERE id = '${sw.id}')
                  ) as same_vendor_compat_count
           FROM transceivers t
           LEFT JOIN vendors v ON v.id = t.vendor_id
           ${altWhere}
           ORDER BY same_vendor_compat_count DESC, t.speed_gbps DESC
           LIMIT 10`,
          altValues
        );

        alternatives = altResult.rows.map(r => ({
          ...r,
          min_price: r.min_price ? parseFloat(r.min_price) : null,
          compatibility_note: parseInt(r.same_vendor_compat_count) > 0
            ? `Compatible with ${r.same_vendor_compat_count} other ${sw.vendor} switches — likely compatible but NOT verified for ${sw.model}`
            : "Not verified for any switches from this vendor. Test before deploying.",
        }));
      }

      return {
        content: [{
          type: "text",
          text: JSON.stringify({
            switch: {
              model: sw.model,
              series: sw.series,
              vendor: sw.vendor,
              max_speed_gbps: sw.max_speed_gbps,
            },
            compatible_transceivers: compatResult.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,
              status: r.status,
              firmware_min: r.firmware_min,
              verified_by: r.verified_by,
              method: r.verification_method,
              notes: r.compat_notes,
              min_price: r.min_price ? parseFloat(r.min_price) : null,
            })),
            count: compatResult.rows.length,
            ...(compatResult.rows.length === 0 && alternatives.length > 0 ? {
              no_verified_match: true,
              suggested_alternatives: alternatives,
              suggestion_note: `No verified compatible transceivers found for ${sw.vendor} ${sw.model}. These alternatives may work based on specs and compatibility with similar ${sw.vendor} switches, but should be tested before production deployment.`,
            } : {}),
          }, null, 2),
        }],
      };
    }
  );

  // Register tool modules
  await registerPricingTools(server);
  await registerCompatibilityTools(server);
  await registerKnowledgeTools(server);
  await registerContentTools(server);
  await registerMarketTools(server);
  await registerSwitchDocTools(server);
  await registerEquivalencesTools(server);

  // --- Register finder.ts tools (find_flexoptix_for_switch, get_competitor_alerts) ---
  for (const [toolName, toolDef] of Object.entries(finderTools)) {
    const schema: Record<string, z.ZodTypeAny> = {};
    for (const [propName, propDef] of Object.entries(toolDef.inputSchema.properties ?? {})) {
      const p = propDef as { type: string; description?: string };
      schema[propName] = p.type === "number"
        ? z.number().optional().describe(p.description ?? "")
        : z.string().optional().describe(p.description ?? "");
    }
    server.tool(
      toolName,
      toolDef.description,
      schema,
      async (args) => {
        const result = await handleFinderTool(toolName, args as Record<string, unknown>);
        return { content: [{ type: "text" as const, text: result }] };
      }
    );
  }

  // --- Ollama-compatible LLM tools: market analysis (TIP_LLM) + blog generation (FO_BlogLLM) ---
  const OLLAMA_BASE = process.env["OLLAMA_BASE_URL"] ?? "https://ollama.fichtmueller.org";
  const TIP_LLM_MODEL = process.env["TIP_LLM_MODEL"] ?? "tip-llm-v1";
  const BLOG_LLM_MODEL = process.env["BLOG_LLM_MODEL"] ?? "fo-blog-v10";
  const BLOG_LLM_FALLBACK = process.env["BLOG_LLM_FALLBACK_MODEL"] ?? "qwen2.5:14b";

  server.tool(
    "analyze_market_with_llm",
    "Deep market analysis for a transceiver technology using TIP_LLM. Provides expert narrative on adoption trends, pricing trajectory, competitive dynamics, and buy/wait/hold recommendation.",
    {
      technology: z.string().describe("Technology to analyze, e.g. '400G QSFP-DD', '800G OSFP', '100G ZR'"),
      context: z.string().optional().describe("Additional context or specific questions to address"),
      horizon: z.enum(["3m", "6m", "12m", "18m"]).default("12m").describe("Forecast horizon"),
    },
    async ({ technology, context, horizon }) => {
      // Gather DB data to enrich the prompt
      const [hype, prices, news] = await Promise.all([
        pool.query(
          `SELECT hype_phase, hype_score, ROUND(current_share*100,1) AS share_pct,
                  asp_current_usd, asp_decline_pct_3y, years_to_next_phase
           FROM hype_cycle_analysis WHERE technology ILIKE $1
           ORDER BY computed_at DESC LIMIT 1`,
          [`%${technology}%`]
        ),
        pool.query(
          `SELECT v.name AS vendor, ROUND(MIN(po.price)::NUMERIC,2) AS min_price,
                  ROUND(MAX(po.price)::NUMERIC,2) AS max_price, po.currency
           FROM price_observations po JOIN vendors v ON v.id = po.source_vendor_id
           JOIN transceivers t ON t.id = po.transceiver_id
           WHERE t.speed ILIKE $1 AND po.time > NOW() - INTERVAL '7 days'
           GROUP BY v.name, po.currency ORDER BY min_price ASC LIMIT 10`,
          [`%${technology.split("-")[0]}%`]
        ),
        pool.query(
          `SELECT title, summary, published_at FROM news_articles
           WHERE content_vector @@ plainto_tsquery('english', $1)
           ORDER BY published_at DESC LIMIT 5`,
          [technology]
        ).catch(() => ({ rows: [] })),
      ]);

      const dataContext = [
        hype.rows[0] ? `Hype Cycle: phase=${hype.rows[0].hype_phase}, score=${hype.rows[0].hype_score}/100, market_share=${hype.rows[0].share_pct}%, OEM_ASP=$${hype.rows[0].asp_current_usd}, ASP_decline_3y=${hype.rows[0].asp_decline_pct_3y}%, years_to_next_phase=${hype.rows[0].years_to_next_phase}` : "",
        prices.rows.length > 0 ? `Current pricing: ${prices.rows.map((r) => `${r.vendor} ${r.currency}${r.min_price}–${r.max_price}`).join(", ")}` : "",
        news.rows.length > 0 ? `Recent news: ${news.rows.map((r: {title:string}) => r.title).join(" | ")}` : "",
      ].filter(Boolean).join("\n");

      const prompt = `You are a senior optical networking market analyst at a transceiver intelligence platform.

Technology: ${technology}
Forecast horizon: ${horizon}
${dataContext ? `\nLive data:\n${dataContext}` : ""}
${context ? `\nSpecific questions: ${context}` : ""}

Provide a concise expert market analysis covering:
1. Current market phase and what it means for buyers/sellers
2. Price trajectory over the next ${horizon} — will prices rise, fall, or stabilize?
3. Key demand drivers and risks
4. Competitive dynamics (OEM vs compatible vendors)
5. Buy / Wait / Hold recommendation with reasoning

Keep the analysis actionable and data-driven. Under 400 words.`;

      try {
        const resp = await fetch(`${OLLAMA_BASE}/api/generate`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ model: TIP_LLM_MODEL, prompt, stream: false }),
          signal: AbortSignal.timeout(120_000),
        });
        if (!resp.ok) throw new Error(`Ollama HTTP ${resp.status}`);
        const data = await resp.json() as { response?: string };
        return { content: [{ type: "text" as const, text: data.response ?? "No response from model." }] };
      } catch (err: unknown) {
        return { content: [{ type: "text" as const, text: `LLM unavailable: ${(err as Error).message}. Use /api/hype-cycle/analysis for raw data.` }] };
      }
    }
  );

  server.tool(
    "generate_blog_post",
    "Generate a professional Flexoptix blog post using the latest FO_BlogLLM model. Automatically enriched with live pricing, hype cycle data, and competitor analysis.",
    {
      topic: z.string().describe("Blog topic, e.g. '400G QSFP-DD vs 400G ZR — which for your DC?'"),
      target_audience: z.enum(["network_engineer", "procurement", "executive", "general"]).default("network_engineer").describe("Target reader"),
      tone: z.enum(["technical", "consultative", "educational"]).default("consultative").describe("Writing tone"),
      word_count: z.number().default(600).describe("Target word count (300–1000)"),
    },
    async ({ topic, target_audience, tone, word_count }) => {
      // Gather enrichment data
      const keywords = topic.match(/\b(\d+G|QSFP|SFP|OSFP|ZR|SR|LR|ER)\b/gi) ?? [];
      const priceData = keywords.length > 0 ? await pool.query(
        `SELECT v.name AS vendor, t.form_factor, t.speed,
                ROUND(MIN(po.price)::NUMERIC,2) AS min_price, po.currency
         FROM price_observations po JOIN vendors v ON v.id = po.source_vendor_id
         JOIN transceivers t ON t.id = po.transceiver_id
         WHERE t.speed ILIKE ANY($1) AND po.time > NOW() - INTERVAL '7 days'
         GROUP BY v.name, t.form_factor, t.speed, po.currency ORDER BY min_price ASC LIMIT 8`,
        [keywords.map((k: string) => `%${k}%`)]
      ).catch(() => ({ rows: [] })) : { rows: [] };

      const enrichment = priceData.rows.length > 0
        ? `\nCurrent market prices (use naturally in article):\n${priceData.rows.map((r: {vendor:string;form_factor:string;speed:string;min_price:string;currency:string}) => `- ${r.form_factor} ${r.speed}: from ${r.currency}${r.min_price} at ${r.vendor}`).join("\n")}`
        : "";

      const systemPrompt = `You are a professional technical writer for Flexoptix, Europe's leading transceiver specialist. Write in a ${tone} tone for a ${target_audience.replace(/_/g," ")} audience. Articles should highlight Flexoptix expertise and the value of our FlexBox universal coding solution.`;

      const userPrompt = `Write a ${word_count}-word blog post on: "${topic}"
${enrichment}

Include:
- Compelling introduction
- Technical explanation appropriate for audience
- Real pricing context where available
- Call-to-action mentioning Flexoptix or FlexBox
- SEO-friendly subheadings

Do not include a title (added separately). Start directly with the article body.`;

      try {
        const resp = await fetch(`${OLLAMA_BASE}/api/chat`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            model: BLOG_LLM_MODEL,
            messages: [
              { role: "system", content: systemPrompt },
              { role: "user", content: userPrompt },
            ],
            stream: false,
          }),
          signal: AbortSignal.timeout(180_000),
        });
        if (!resp.ok) {
          // Fallback to generic local model if FO_BlogLLM is unavailable
          const fallbackResp = await fetch(`${OLLAMA_BASE}/api/chat`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              model: BLOG_LLM_FALLBACK,
              messages: [
                { role: "system", content: systemPrompt },
                { role: "user", content: userPrompt },
              ],
              stream: false,
            }),
            signal: AbortSignal.timeout(180_000),
          });
          if (!fallbackResp.ok) throw new Error(`Both ${BLOG_LLM_MODEL} and ${BLOG_LLM_FALLBACK} unavailable`);
          const fallbackData = await fallbackResp.json() as { message?: { content?: string } };
          return { content: [{ type: "text" as const, text: `[Generated with ${BLOG_LLM_FALLBACK} — ${BLOG_LLM_MODEL} unavailable]\n\n${fallbackData.message?.content ?? "No content"}` }] };
        }
        const data = await resp.json() as { message?: { content?: string } };
        return { content: [{ type: "text" as const, text: data.message?.content ?? "No content generated." }] };
      } catch (err: unknown) {
        return { content: [{ type: "text" as const, text: `LLM unavailable: ${(err as Error).message}. Check OLLAMA_BASE_URL env var.` }] };
      }
    }
  );

  // Start server
  const transport = new StdioServerTransport();
  await server.connect(transport);

  // Graceful shutdown
  process.on("SIGINT", async () => {
    await pool.end();
    process.exit(0);
  });
}

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