const fs = require("fs");
const http = require("http");
const https = require("https");
const crypto = require("crypto");

// ── LOCAL DATABASE CLIENT (BGP, RPKI, Threat Intel) ──────────────
const localDb = require('./local-db-client');
console.log('[PeerCortex] Local DB client initialized');


// Load .env file
require('./src/backend/config');

const BGPROUTES_API_KEY = process.env.BGPROUTES_API_KEY || "";
const BGPROUTES_API_URL = process.env.BGPROUTES_API_URL || "https://api.bgproutes.io/v1";

// bio-rd gRPC client (optional — graceful fallback if unavailable)
let risClient = null;
try {
  const { createRisClient } = require('./bio-rd-client');
  const BIO_RD_HOST = process.env.BIO_RD_HOST || 'localhost';
  const BIO_RD_PORT = parseInt(process.env.BIO_RD_PORT || '4321');
  risClient = createRisClient(BIO_RD_HOST, BIO_RD_PORT);
  console.log(`[bio-rd] RIS client configured → ${BIO_RD_HOST}:${BIO_RD_PORT}`);
} catch (e) {
  console.log('[bio-rd] RIS client not available (bio-rd-client.js missing or gRPC not installed)');
}

const {
  PEERINGDB_API_KEY,
  PEERINGDB_API_URL,
  getPdbLocal,
  queryPeeringDBLocal,
  pdbSemaphore,
  fetchPeeringDB,
  fetchPeeringDBWithRetry,
} = require("./server/services/peeringdb");

// ── SMTP / Email ──────────────────────────────────────────────
const { sendFeedbackMail } = require('./src/backend/services/smtp');
// ── SMTP / Email ──────────────────────────────────────────────

const { loadVisitors, trackVisitor } = require("./server/visitors");

// ═══════════════════════════════════════════════════════════════
// PEERCORTEX v0.6.1 — New Features
// ═══════════════════════════════════════════════════════════════

// ── BGP Community Database ─────────────────────────────────────
// Migrated to src/features/bgp-communities/bgp-community-db.ts

// ── Hijack Monitoring ──────────────────────────────────────────
const {
  DATA_DIR,
  loadHijackSubs,
  loadHijackAlerts,
  loadWebhookSubs,
  saveWebhookSubs,
  saveHijackAlerts,
  saveHijackSubs,
} = require("./server/hijack-monitoring/store");
const { webhookSignature, deliverWebhook, notifyWebhooks } = require("./server/hijack-monitoring/webhooks");
const { classifyHijackSeverity, checkHijacksForAsn, runHijackCheck } = require("./server/hijack-monitoring/monitor");

const {
  aspaAdoptionDailyHistory,
  recordAspaAdoptionSnapshot,
  getAdoptionTrend,
  buildAspaAdoptionDashboard,
} = require("./server/aspa-adoption/tracker");

const { fetchRirDelegation, fetchIpv6AdoptionStats } = require("./server/ipv6-adoption");

const { UA } = require("./server/data/constants");
const { CITY_COORDS } = require("./server/data/city-coords");

// ============================================================
const {
  scoreRingSvg,
  pdfBaseCSS,
  buildPdfHtml,
  buildComparisonPdfHtml,
  formatLabel,
  escHtml,
} = require("./server/pdf-export/templates");
const {
  getPuppeteerBrowser,
  pdfCacheGet,
  pdfCacheSet,
  renderHtmlToPdf,
} = require("./server/pdf-export/renderer");

const {
  responseCache,
  cacheGet,
  cacheSet,
  RESULT_CACHE_TTL,
  CACHE_TTL_LOOKUP,
  CACHE_TTL_LOOKUP_DEGRADED,
  CACHE_TTL_DEFAULT,
  rdapCacheGet,
  rdapCacheSet,
  whoisCacheGet,
  whoisCacheSet,
  WHOIS_NOT_FOUND_CACHE_TTL,
  quickIxCacheGet,
  quickIxCacheSet,
  bgproutesResultCache,
  aspaResultCache,
  validateResultCache,
  resultCacheGet,
  resultCacheSet,
} = require("./server/caches/response-caches");
// bgproutesVpCache/bgproutesVpCacheTs/BGPROUTES_VP_TTL removed 2026-07-16:
// confirmed dead code, never read anywhere in the file.

const { ensureManrsCache, checkManrsMembership } = require("./server/services/manrs");

// ============================================================
// subCableCache/globalFacCache removed 2026-07-16: confirmed dead code,
// never read anywhere in the file.

const { roaStore } = require("./server/caches/roa-store");

const {
  rpkiAspaMap,
  getRpkiAspaLastFetch,
  fetchRpkiAspaFeed,
  ensureAspaCache,
  lookupAspaFromRpki,
  validateRPKIWithCache,
  fetchRipeRpkiValidator,
  crossCheckRpki,
} = require("./server/services/rpki");

const { pdbSourceCache } = require("./server/caches/pdb-source-cache");
const {
  ripeStatCache,
  fetchRipeStatCached,
  fetchRipeStatCachedFromApi,
  fetchRipeStatCachedWithRetry,
  saveRipeStatCacheToDisk,
  loadRipeStatCacheFromDisk,
  Semaphore,
} = require("./server/services/ripe-stat");


const {
  fetchJSON,
  fetchJSONWithRetry,
  fetchHTML,
  postJSON,
  fetchBgproutesVisibility,
} = require("./server/services/http-helpers");
// checkRateLimit/rateLimitMap removed 2026-07-16: dead code, never called by
// the request handler (confirmed via the server.js structural exploration).

const { resolveASNames } = require("./server/resolve-as-names");



const {
  hasAsSet,
  hopCheck,
  collapsePrepends,
  verifyUpstream,
  verifyDownstream,
  detectValleys,
  buildAspaStore,
  calculateAspaReadinessScore,
} = require("./server/aspa-verification/engine");


const { fetchBgpHeNet } = require("./server/bgp-he-net");

const { fetchTopology } = require("./server/topology");

const { computeResilienceScore } = require("./server/resilience-score");
const { computeRouteLeakDetection } = require("./server/route-leak");
const { fetchWhois } = require("./server/whois");

// ============================================================
// HTTP Server
// ============================================================

const staticRoutes = require("./server/routes/static");
const searchRoutes = require("./server/routes/search");
const feedbackRoutes = require("./server/routes/feedback");
const liaRoutes = require("./server/routes/lia");
const healthRoute = require("./server/routes/health");
const aspaVerifyRoute = require("./server/routes/aspa-verify");
const aspaLegacyRoute = require("./server/routes/aspa-legacy");
const bgpRoute = require("./server/routes/bgp");
const validateRoute = require("./server/routes/validate");
const lookupRoute = require("./server/routes/lookup");
const relationshipsRoute = require("./server/routes/relationships");
const compareRoute = require("./server/routes/compare");
const quickIxRoute = require("./server/routes/quick-ix");
const peersFindRoute = require("./server/routes/peers-find");
const prefixDetailRoute = require("./server/routes/prefix-detail");
const ixDetailRoute = require("./server/routes/ix-detail");
const topologyRoute = require("./server/routes/topology");
const whoisRoute = require("./server/routes/whois");
const enrichRoute = require("./server/routes/enrich");
const proxyStubsRoute = require("./server/routes/proxy-stubs");
const changelogRoute = require("./server/routes/changelog");
const hijackAlertsRoute = require("./server/routes/hijack-alerts");
const pdfExportRoute = require("./server/routes/pdf-export");
const aspaAdoptionRoute = require("./server/routes/aspa-adoption");

const server = http.createServer(async (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");

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

  let url, reqPath;
  try {
    url = new URL(req.url, "http://localhost");
    reqPath = url.pathname;
  } catch (_urlErr) {
    res.writeHead(400);
    return res.end('Bad Request');
  }

  // Serve static files — host-based routing
  const host = (req.headers.host || '').split(':')[0];

  if (reqPath === "/" || reqPath === "/index.html") {
    return staticRoutes.handleRoot(req, res, host, reqPath);
  }

  // Direct access to editorial version
  if (reqPath === "/index-editorial.html") {
    return staticRoutes.handleIndexEditorial(req, res);
  }

  // Name Search (RIPE Stat + PeeringDB combined)
  if (reqPath === "/api/search") {
    return searchRoutes.handleSearch(req, res);
  }

  // GET /api/visitors — unique visitor count
  if (reqPath === "/api/visitors" && req.method === "GET") {
    return searchRoutes.handleVisitors(req, res);
  }

  // OPTIONS preflight (CORS)
  if (reqPath === "/api/feedback" && req.method === "OPTIONS") {
    return feedbackRoutes.handleOptions(req, res);
  }

  // POST /api/feedback — submit feedback entry
  if (reqPath === "/api/feedback" && req.method === "POST") {
    return feedbackRoutes.handlePost(req, res);
  }

  // GET /api/feedback?token=... — admin: fetch all entries as JSON
  if (reqPath === "/api/feedback" && req.method === "GET") {
    return feedbackRoutes.handleGet(req, res, url);
  }

  // Serve favicon
  if (reqPath === "/favicon.ico") {
    return staticRoutes.handleFavicon(req, res);
  }

  // Lia's Atlas Paradise - Easter egg page
  if (reqPath === "/lia" || reqPath === "/lia/") {
    return staticRoutes.handleLia(req, res);
  }

  // Lia's Atlas Paradise: Atlas probe coverage endpoint
  if (reqPath === "/api/atlas/coverage") {
    return liaRoutes.handleAtlasCoverage(req, res);
  }

  // Lia's Paradise: File parsing endpoint (for binary uploads)
  if (reqPath === "/api/lia/parse-file" && req.method === "POST") {
    return liaRoutes.handleParseFile(req, res);
  }

  // Lia's Paradise: Combined PeeringDB + Atlas coverage data
  if (reqPath === "/api/lia/coverage") {
    return liaRoutes.handleLiaCoverage(req, res);
  }

  res.setHeader("Content-Type", "application/json");

  // Health endpoint — extended with cache status, ASPA metrics, and local DB stats
  if (reqPath === "/api/health") {
    return healthRoute.handleHealth(req, res);
  }


  // ============================================================
  // ASPA Deep Verification endpoint: /api/aspa/verify?asn=X
  if (reqPath === "/api/aspa/verify") {
    return aspaVerifyRoute.handleAspaVerify(req, res, url);
  }

  // ASPA Check endpoint: /api/aspa?asn=X (existing, kept for compat)
  if (reqPath === "/api/aspa") {
    return aspaLegacyRoute.handleAspaLegacy(req, res, url);
  }

  // BGP endpoint (LOCAL DB): /api/bgp?asn=X (or prefix=X)
  if (reqPath === "/api/bgp") {
    return bgpRoute.handleBgp(req, res, url);
  }



  // Unified Validation endpoint: /api/validate?asn=X
  if (reqPath === "/api/validate") {
    return validateRoute.handleValidate(req, res, url);
  }

  // Main lookup endpoint: /api/lookup?asn=X
  if (reqPath === "/api/lookup") {
    return lookupRoute.handleLookup(req, res, url);
  }

  if (reqPath === "/api/relationships") {
    return relationshipsRoute.handleRelationships(req, res, url);
  }

  // Compare endpoint: /api/compare?asn1=X&asn2=Y
  if (reqPath === "/api/compare") {
    return compareRoute.handleCompare(req, res, url);
  }

  // Quick-IX endpoint: /api/quick-ix?asn=X
  if (reqPath === "/api/quick-ix") {
    return quickIxRoute.handleQuickIx(req, res, url);
  }

  // Peer Matching endpoint: /api/peers/find?ix=NAME&policy=open&min_speed=10000
  if (reqPath === "/api/peers/find") {
    return peersFindRoute.handlePeersFind(req, res, url);
  }

  // Prefix Detail endpoint: /api/prefix/detail?prefix=X
  if (reqPath === "/api/prefix/detail") {
    return prefixDetailRoute.handlePrefixDetail(req, res, url);
  }

  // IX Detail endpoint: /api/ix/detail?ix_id=X
  if (reqPath === "/api/ix/detail") {
    return ixDetailRoute.handleIxDetail(req, res, url);
  }

  // Feature 25: Topology endpoint
  if (reqPath === "/api/topology") {
    return topologyRoute.handleTopology(req, res, url);
  }

  // Feature 27: WHOIS endpoint
  if (reqPath === "/api/whois") {
    return whoisRoute.handleWhoisRoute(req, res, url);
  }

  // Feature 28b: Company enrichment via Wikipedia + website meta scraping
  if (reqPath === "/api/enrich") {
    return enrichRoute.handleEnrich(req, res, url);
  }


  // Feature 28: Submarine Cable overlay + other already-migrated routes,
  // proxied through to the separate Fastify API server.
  if (proxyStubsRoute.isProxyStubPath(reqPath)) {
    return proxyStubsRoute.handleProxyStub(req, res);
  }

  // Changelog page
  if (reqPath === "/changelog") {
    return changelogRoute.handleChangelog(req, res);
  }

  // CORS preflight for all /api/webhooks + /api/hijacks
  if (hijackAlertsRoute.matchesHijackGroup(reqPath) && req.method === "OPTIONS") {
    return hijackAlertsRoute.handleOptions(req, res);
  }

  // Webhook: Register. POST /api/webhooks?asn=X
  if (reqPath === "/api/webhooks" && req.method === "POST") {
    return hijackAlertsRoute.handleWebhookRegister(req, res);
  }

  // Webhook: List. GET /api/webhooks?asn=X
  if (reqPath === "/api/webhooks" && req.method === "GET") {
    return hijackAlertsRoute.handleWebhookList(req, res);
  }

  // Webhook: Delete. DELETE /api/webhooks/:id
  if (reqPath.startsWith("/api/webhooks/") && req.method === "DELETE" && !reqPath.includes("/test")) {
    return hijackAlertsRoute.handleWebhookDelete(req, res, reqPath);
  }

  // Webhook: Test. POST /api/webhooks/:id/test
  if (reqPath.match(/^\/api\/webhooks\/[^/]+\/test$/) && req.method === "POST") {
    return hijackAlertsRoute.handleWebhookTest(req, res, reqPath);
  }

  // Hijack Events: List. GET /api/hijacks?asn=X&limit=50&resolved=false
  if (reqPath === "/api/hijacks" && req.method === "GET") {
    return hijackAlertsRoute.handleHijacksList(req, res);
  }

  // Hijack Events: Resolve. POST /api/hijacks/:id/resolve
  if (reqPath.match(/^\/api\/hijacks\/[^/]+\/resolve$/) && req.method === "POST") {
    return hijackAlertsRoute.handleHijackResolve(req, res, reqPath);
  }

  // Hijack Subscribe (legacy)
  if (reqPath === "/api/hijack-subscribe" && req.method === "POST") {
    return hijackAlertsRoute.handleHijackSubscribe(req, res);
  }

  // Hijack Alerts (legacy endpoint)
  if (reqPath.startsWith("/api/hijack-alerts")) {
    return hijackAlertsRoute.handleHijackAlertsLegacy(req, res);
  }

  // PDF Export
  if (reqPath === "/api/export/pdf" && req.method === "GET") {
    return pdfExportRoute.handleExportPdf(req, res, url);
  }

  if (reqPath === "/api/export/pdf/compare" && req.method === "GET") {
    return pdfExportRoute.handleExportPdfCompare(req, res, url);
  }

  if (reqPath.startsWith("/api/export/") && req.method === "OPTIONS") {
    return pdfExportRoute.handleExportOptions(req, res);
  }

  // ASPA Adoption Tracker
  if (reqPath === "/aspa-adoption") {
    return aspaAdoptionRoute.handleDashboard(req, res);
  }

  if (reqPath === "/api/aspa-adoption-stats" && req.method === "GET") {
    return aspaAdoptionRoute.handleStats(req, res, url);
  }

  if (reqPath === "/api/aspa-adoption-stats/export" && req.method === "GET") {
    return aspaAdoptionRoute.handleStatsExport(req, res, url);
  }

  if (reqPath === "/api/ipv6-adoption-stats" && req.method === "GET") {
    return aspaAdoptionRoute.handleIpv6Stats(req, res, url);
  }


  // 404
  res.writeHead(404);
  res.end(
    JSON.stringify({
      error: "Not found. Endpoints: /api/health, /api/validate?asn=X, /api/lookup?asn=X, /api/aspa?asn=X, /api/aspa/verify?asn=X, /api/bgproutes?asn=X, /api/compare?asn1=X&asn2=Y, /api/peers/find?ix=NAME, /api/prefix/detail?prefix=X, /api/ix/detail?ix_id=X, /api/export/pdf?asn=X&format=report|executive|technical, /api/export/pdf/compare?asn1=X&asn2=Y, /api/aspa-adoption-stats?period=30d, /api/ipv6-adoption-stats",
    })
  );
});


const { atlasState, fetchAllAtlasProbes } = require("./server/atlas-probes");

const { pdbOrgState, fetchPdbOrgCountries } = require("./server/pdb-org-countries");

const PORT = process.env.PORT || 3101;

// ============================================================
// Startup Sequence — load disk caches first, then fetch fresh data
// ============================================================

// Phase 0: Load disk caches for fast restart (instant)
roaStore.loadFromDisk("/opt/peercortex-app/.roa-cache.json");
pdbSourceCache.loadFromDisk("/opt/peercortex-app/.pdb-source-cache.json");
loadRipeStatCacheFromDisk("/opt/peercortex-app/.ripe-stat-cache.json");

// Phase 1: Fetch fresh RPKI feed (ASPA + ROA) + Atlas probes + PDB org countries + MANRS participants
ensureManrsCache(); // fire-and-forget, 24h cache
Promise.all([fetchRpkiAspaFeed(), fetchAllAtlasProbes(), fetchPdbOrgCountries()]).then(() => {
  // Re-record ASPA adoption snapshot now that Atlas data is fully loaded
  recordAspaAdoptionSnapshot(rpkiAspaMap.size, roaStore.count, rpkiAspaMap);

  server.listen(PORT, "0.0.0.0", () => {
    console.log("PeerCortex v0.6.5 running on http://0.0.0.0:" + PORT);
    console.log("bgproutes.io API key: " + (BGPROUTES_API_KEY ? "configured" : "NOT configured"));
    console.log("PeeringDB API key: " + (PEERINGDB_API_KEY ? "configured" : "NOT configured"));
    console.log("RPKI ASPA objects: " + rpkiAspaMap.size);
    console.log("ROA store: " + roaStore.count + " entries (" + (roaStore.ready ? "ready" : "loading...") + ")");
    console.log("PDB source cache: net=" + pdbSourceCache.net.size + " ix=" + pdbSourceCache.netixlan.size + " fac=" + pdbSourceCache.netfac.size);
    console.log("RIPE Stat cache: " + ripeStatCache.size + " entries");
  });
});

// ============================================================
// bio-rd RIB WebSocket — live route streaming on /ws/rib
// ============================================================
let WebSocketServer = null;
try { WebSocketServer = require('ws').Server; } catch(_e) {}

if (WebSocketServer) {
  const ribWss = new WebSocketServer({ server, path: '/ws/rib' });
  ribWss.on('connection', function(ws) {
    let cancelStream = null;

    ws.on('message', function(raw) {
      try {
        const msg = JSON.parse(raw);
        if (msg.type === 'rib-subscribe') {
          if (cancelStream) { cancelStream(); cancelStream = null; }
          if (!risClient) {
            ws.send(JSON.stringify({ type: 'error', error: 'bio-rd RIS not configured' }));
            return;
          }
          const router = msg.router || 'default';
          cancelStream = risClient.observeRib(
            router,
            'default',
            function(update) { if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(update)); },
            function(err) { if (ws.readyState === ws.OPEN) ws.send(JSON.stringify({ type: 'error', error: err.message })); }
          );
        }
      } catch(e) {
        if (ws.readyState === ws.OPEN) ws.send(JSON.stringify({ type: 'error', error: e.message }));
      }
    });

    ws.on('close', function() {
      if (cancelStream) { cancelStream(); cancelStream = null; }
    });
  });
  console.log('[bio-rd] RIB WebSocket server listening on /ws/rib');
} else {
  console.log('[bio-rd] WebSocket server skipped (ws package not installed)');
}

// ============================================================
// Refresh timers — jittered to avoid thundering herd
// ============================================================

// RPKI feed (ASPA + ROA): every 4h ± 5min jitter
setInterval(() => {
  fetchRpkiAspaFeed();
}, 4 * 60 * 60 * 1000 + Math.floor(Math.random() * 10 * 60 * 1000) - 5 * 60 * 1000);

// Atlas probe cache: every 12h ± 10min jitter
setInterval(function() {
  fetchAllAtlasProbes();
}, 12 * 60 * 60 * 1000 + Math.floor(Math.random() * 20 * 60 * 1000) - 10 * 60 * 1000);

// Disk cache persistence: every 30 minutes
setInterval(function() {
  pdbSourceCache.saveToDisk("/opt/peercortex-app/.pdb-source-cache.json");
  saveRipeStatCacheToDisk("/opt/peercortex-app/.ripe-stat-cache.json");
}, 30 * 60 * 1000);

// Save caches on graceful shutdown
process.on("SIGTERM", function() {
  console.log("[SHUTDOWN] Saving caches to disk...");
  pdbSourceCache.saveToDisk("/opt/peercortex-app/.pdb-source-cache.json");
  saveRipeStatCacheToDisk("/opt/peercortex-app/.ripe-stat-cache.json");
  roaStore.saveToDisk("/opt/peercortex-app/.roa-cache.json");
  process.exit(0);
});
process.on("SIGINT", function() {
  console.log("[SHUTDOWN] Saving caches to disk...");
  pdbSourceCache.saveToDisk("/opt/peercortex-app/.pdb-source-cache.json");
  saveRipeStatCacheToDisk("/opt/peercortex-app/.ripe-stat-cache.json");
  roaStore.saveToDisk("/opt/peercortex-app/.roa-cache.json");
  process.exit(0);
});
