#!/usr/bin/env node
/**
 * PeerCortex MCP Server
 * Exposes PeerCortex API as MCP tools for Claude Desktop / Claude Code.
 * Communicates via stdio. Every response includes _provenance metadata.
 *
 * Usage in claude_desktop_config.json:
 * { "mcpServers": { "peercortex": { "command": "node", "args": ["/opt/peercortex-app/mcp-server.js"] } } }
 */

const PEERCORTEX_BASE = process.env.PEERCORTEX_BASE || "http://localhost:3101";

// ── Minimal MCP stdio server (no SDK dependency issues) ──────────────────────
const readline = require("readline");
const http = require("http");
const https = require("https");

function callApi(path) {
  return new Promise((resolve, reject) => {
    const url = PEERCORTEX_BASE + path;
    const mod = url.startsWith("https") ? https : http;
    const req = mod.get(url, { headers: { "User-Agent": "PeerCortex-MCP/1.0" } }, (res) => {
      let data = "";
      res.on("data", (c) => (data += c));
      res.on("end", () => {
        try { resolve(JSON.parse(data)); }
        catch (e) { reject(new Error("JSON parse error: " + e.message)); }
      });
    });
    req.on("error", reject);
    req.setTimeout(30000, () => { req.destroy(new Error("timeout")); });
  });
}

// Summarize a full lookup into a compact MCP-friendly object
function summarizeLookup(d) {
  if (!d || !d.network) return null;
  const n   = d.network   || {};
  const p   = d.prefixes  || {};
  const r   = d.rpki      || {};
  const ix  = d.ix_presence || {};
  const nb  = d.neighbours  || {};
  const rs  = d.resilience_score;
  const rl  = d.route_leak;
  const dq  = d.data_quality || {};
  const prov= d._provenance  || {};

  return {
    asn:              n.asn,
    name:             n.name,
    org:              n.org_name,
    website:          n.website,
    rir:              n.rir,
    country:          n.country,
    type:             n.type,
    policy:           n.policy,
    prefixes: {
      total:          p.total,
      ipv4:           p.ipv4,
      ipv6:           p.ipv6,
    },
    rpki: {
      coverage_pct:   r.coverage_percent,
      valid:          r.valid,
      invalid:        r.invalid,
      not_found:      r.not_found,
    },
    ix_presence: {
      unique_ixps:    ix.unique_ixps,
      total_connections: ix.total_connections,
    },
    neighbours: {
      upstream_count:   nb.upstream_count,
      downstream_count: nb.downstream_count,
      peer_count:       nb.peer_count,
      upstreams: (nb.upstreams || []).slice(0, 10).map(u => `AS${u.asn} ${u.name}`),
    },
    resilience_score: rs ? {
      score:            rs.score,
      single_transit_cap: rs.single_transit_cap_applied,
      breakdown:        rs.breakdown,
      _provenance:      rs._provenance,
    } : null,
    route_leak: rl ? {
      detected:         rl.detected,
      patterns:         rl.patterns,
      _provenance:      rl._provenance,
    } : null,
    data_quality: {
      score:            dq.score,
      confidence:       dq.confidence,
    },
    _provenance: prov,
  };
}

// ── MCP Tool Definitions ──────────────────────────────────────────────────────
const TOOLS = [
  {
    name: "lookup_asn",
    description: "Look up comprehensive ASN data including prefixes, RPKI, IX presence, neighbours, resilience score, and route leak detection. All data points include provenance metadata (source, validation method, confidence level).",
    inputSchema: {
      type: "object",
      properties: {
        asn: { type: "string", description: "ASN number, e.g. '24940' or 'AS24940'" },
      },
      required: ["asn"],
    },
  },
  {
    name: "compare_networks",
    description: "Compare two ASNs side by side — prefixes, RPKI coverage, IX presence, resilience scores, peering policy.",
    inputSchema: {
      type: "object",
      properties: {
        asn1: { type: "string", description: "First ASN" },
        asn2: { type: "string", description: "Second ASN" },
      },
      required: ["asn1", "asn2"],
    },
  },
  {
    name: "get_health_report",
    description: "Run the PeerCortex 13-point health report for an ASN: RPKI, ASPA, ROA coverage, IRR, MANRS, route visibility, and more.",
    inputSchema: {
      type: "object",
      properties: {
        asn: { type: "string", description: "ASN number" },
      },
      required: ["asn"],
    },
  },
  {
    name: "search_network",
    description: "Search for a network by name or organization. Returns matching ASNs with basic info.",
    inputSchema: {
      type: "object",
      properties: {
        query: { type: "string", description: "Network name or organization to search for" },
      },
      required: ["query"],
    },
  },
  {
    name: "get_resilience_score",
    description: "Get the weighted resilience score (1-10) for an ASN with full breakdown: transit diversity, peering breadth, IXP presence, path redundancy.",
    inputSchema: {
      type: "object",
      properties: {
        asn: { type: "string", description: "ASN number" },
      },
      required: ["asn"],
    },
  },
];

// ── MCP Request Handlers ──────────────────────────────────────────────────────
async function handleToolCall(name, args) {
  try {
    if (name === "lookup_asn") {
      const asnNum = String(args.asn).replace(/[^0-9]/g, "");
      const d = await callApi(`/api/lookup?asn=${asnNum}`);
      const summary = summarizeLookup(d);
      return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
    }

    if (name === "compare_networks") {
      const a1 = String(args.asn1).replace(/[^0-9]/g, "");
      const a2 = String(args.asn2).replace(/[^0-9]/g, "");
      const [d1, d2] = await Promise.all([
        callApi(`/api/lookup?asn=${a1}`),
        callApi(`/api/lookup?asn=${a2}`),
      ]);
      const comparison = {
        asn1: summarizeLookup(d1),
        asn2: summarizeLookup(d2),
        comparison: {
          prefixes:         { [a1]: d1?.prefixes?.total, [a2]: d2?.prefixes?.total },
          rpki_coverage:    { [a1]: d1?.rpki?.coverage_percent + "%", [a2]: d2?.rpki?.coverage_percent + "%" },
          unique_ixps:      { [a1]: d1?.ix_presence?.unique_ixps, [a2]: d2?.ix_presence?.unique_ixps },
          upstream_count:   { [a1]: d1?.neighbours?.upstream_count, [a2]: d2?.neighbours?.upstream_count },
          resilience_score: { [a1]: d1?.resilience_score?.score, [a2]: d2?.resilience_score?.score },
          route_leak:       { [a1]: d1?.route_leak?.detected, [a2]: d2?.route_leak?.detected },
        },
      };
      return { content: [{ type: "text", text: JSON.stringify(comparison, null, 2) }] };
    }

    if (name === "get_health_report") {
      const asnNum = String(args.asn).replace(/[^0-9]/g, "");
      const d = await callApi(`/api/validate?asn=${asnNum}`);
      return { content: [{ type: "text", text: JSON.stringify(d, null, 2) }] };
    }

    if (name === "search_network") {
      const q = encodeURIComponent(args.query);
      const d = await callApi(`/api/search?q=${q}`);
      return { content: [{ type: "text", text: JSON.stringify(d, null, 2) }] };
    }

    if (name === "get_resilience_score") {
      const asnNum = String(args.asn).replace(/[^0-9]/g, "");
      const d = await callApi(`/api/lookup?asn=${asnNum}`);
      const rs = d?.resilience_score;
      const n  = d?.network || {};
      if (!rs) {
        return { content: [{ type: "text", text: JSON.stringify({ error: "No resilience data available for AS" + asnNum }) }] };
      }
      return { content: [{ type: "text", text: JSON.stringify({
        asn: asnNum,
        name: n.name,
        resilience_score: rs,
        _note: "Score 1-10. Hard cap at 5.0 if single transit provider detected.",
      }, null, 2) }] };
    }

    return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
  } catch (err) {
    return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
  }
}

// ── MCP stdio protocol ────────────────────────────────────────────────────────
const rl = readline.createInterface({ input: process.stdin });
let buffer = "";

rl.on("line", async (line) => {
  buffer += line;
  let msg;
  try { msg = JSON.parse(buffer); buffer = ""; }
  catch (e) { return; } // not complete JSON yet

  const { jsonrpc, id, method, params } = msg;

  let response;

  if (method === "initialize") {
    response = {
      jsonrpc: "2.0", id,
      result: {
        protocolVersion: "2024-11-05",
        capabilities: { tools: {} },
        serverInfo: { name: "peercortex", version: "0.6.9" },
      },
    };
  } else if (method === "tools/list") {
    response = { jsonrpc: "2.0", id, result: { tools: TOOLS } };
  } else if (method === "tools/call") {
    const result = await handleToolCall(params.name, params.arguments || {});
    response = { jsonrpc: "2.0", id, result };
  } else if (method === "notifications/initialized") {
    return; // no response needed
  } else {
    response = {
      jsonrpc: "2.0", id,
      error: { code: -32601, message: `Method not found: ${method}` },
    };
  }

  process.stdout.write(JSON.stringify(response) + "\n");
});

process.on("uncaughtException", (err) => {
  process.stderr.write("PeerCortex MCP error: " + err.message + "\n");
});
