const { fetchRipeStatCached } = require("../../services/ripe-stat");
const { fetchPeeringDB } = require("../../services/peeringdb");
const { ensureAspaCache } = require("../../services/rpki");
const { validateResultCache, resultCacheGet, resultCacheSet, RESULT_CACHE_TTL } = require("../../caches/response-caches");
const { computeBogonResult } = require("./bogon");
const checks = require("./checks");

// Calculate overall health score (0-100)
const SCORE_WEIGHTS = [
  { key: "bogon", weight: 15 },
  { key: "irr", weight: 10 },
  { key: "rpki_completeness", weight: 15 },
  { key: "abuse_contact", weight: 5 },
  { key: "blocklist", weight: 15 },
  { key: "manrs", weight: 5 },
  { key: "rdns", weight: 5 },
  { key: "visibility", weight: 10 },
  { key: "rpsl", weight: 5 },
  { key: "ix_route_server", weight: 5 },
  { key: "resource_cert", weight: 10 },
];

function scoreChecks(validations) {
  var totalWeight = 0;
  var earnedScore = 0;
  var checkResults = [];

  SCORE_WEIGHTS.forEach(function(c) {
    var v = validations[c.key];
    var points = 0;
    if (v && v.status === "info") {
      // "info" = unable to verify (e.g. API auth required) — exclude from scoring
      checkResults.push({ check: c.key, weight: c.weight, earned: 0, status: "info" });
      return;
    }
    if (v && v.status === "pass") points = c.weight;
    else if (v && v.status === "warning") points = Math.round(c.weight * 0.5);
    totalWeight += c.weight;
    earnedScore += points;
    checkResults.push({ check: c.key, weight: c.weight, earned: points, status: v ? v.status : "error" });
  });

  var healthScore = totalWeight > 0 ? Math.round((earnedScore / totalWeight) * 100) : 0;
  return { healthScore: healthScore, checkResults: checkResults };
}

// Enrich geolocation (Bug 4 fix: handle anycast/CDN/global networks)
function enrichGeolocation(validations, facCountries, net) {
  if (!validations.geolocation || validations.geolocation.status === "error") return;
  var uniqueFacCountries = {};
  facCountries.forEach(function(c) { uniqueFacCountries[c] = true; });
  var facCountryCount = Object.keys(uniqueFacCountries).length;
  validations.geolocation.pdb_facility_countries = Object.keys(uniqueFacCountries);
  var geoSet = {};
  (validations.geolocation.geo_countries || []).forEach(function(c) { geoSet[c] = true; });
  var geoCountryCount = Object.keys(geoSet).length;
  var mismatches = Object.keys(geoSet).filter(function(c) { return !uniqueFacCountries[c] && facCountryCount > 0; });
  validations.geolocation.country_mismatches = mismatches;

  // Detect global/anycast networks: 5+ facility countries OR Content/NSP type
  var netInfoType = (net.info_type || "").toLowerCase();
  var isGlobalNetwork = facCountryCount >= 5 || netInfoType === "content" || netInfoType === "nsp";
  if (isGlobalNetwork) {
    // Global/anycast/CDN network: geo mismatches are expected, not anomalies
    validations.geolocation.status = "pass";
    if (geoCountryCount === 0) {
      validations.geolocation.note = "Global network (" + facCountryCount + " countries, type: " + (net.info_type || "N/A") + ") - no MaxMind geolocation data available";
    } else {
      validations.geolocation.note = "Global/anycast network - multi-country presence expected (" + facCountryCount + " facility countries, type: " + (net.info_type || "N/A") + ")";
    }
    validations.geolocation.country_mismatches = [];
  } else if (facCountryCount <= 2 && geoCountryCount >= 10) {
    // Actual anomaly: small network appearing in many countries
    validations.geolocation.status = "warning";
    validations.geolocation.note = "Prefixes geolocated in " + geoCountryCount + " countries but only " + facCountryCount + " facility countries - possible hijack or misconfiguration";
  }
}

// Build relationships from neighbour data
function buildRelationships(neighbourData) {
  var relNeighbours = neighbourData && neighbourData.data && neighbourData.data.neighbour_counts
    ? neighbourData.data.neighbour_counts : {};
  var relList = neighbourData && neighbourData.data && neighbourData.data.neighbours
    ? neighbourData.data.neighbours : [];
  var relUpstreams = relList.filter(function(n) { return n.type === "left"; })
    .sort(function(a, b) { return (b.power || 0) - (a.power || 0); })
    .slice(0, 20)
    .map(function(n) { return { asn: n.asn, power: n.power || 0, v4_peers: n.v4_peers || 0, v6_peers: n.v6_peers || 0 }; });
  var relDownstreams = relList.filter(function(n) { return n.type === "right"; })
    .sort(function(a, b) { return (b.power || 0) - (a.power || 0); })
    .slice(0, 20)
    .map(function(n) { return { asn: n.asn, power: n.power || 0, v4_peers: n.v4_peers || 0, v6_peers: n.v6_peers || 0 }; });
  var relPeers = relList.filter(function(n) { return n.type === "uncertain"; })
    .sort(function(a, b) { return (b.power || 0) - (a.power || 0); })
    .slice(0, 30)
    .map(function(n) { return { asn: n.asn, power: n.power || 0, v4_peers: n.v4_peers || 0, v6_peers: n.v6_peers || 0 }; });

  return {
    counts: { upstreams: relNeighbours.left || relUpstreams.length, downstreams: relNeighbours.right || relDownstreams.length, peers: relNeighbours.unique || relPeers.length, uncertain: relNeighbours.uncertain || 0 },
    upstreams: relUpstreams,
    downstreams: relDownstreams,
    top_peers: relPeers,
    source: "RIPE Stat asn-neighbours",
    note: "left=upstream providers, right=downstream customers, uncertain=peers. Sorted by power score.",
  };
}

// Unified Validation endpoint: /api/validate?asn=X
// Runs ALL validations in parallel, returns comprehensive report
async function handleValidate(req, res, url) {
  const rawAsn = (url.searchParams.get("asn") || "").replace(/[^0-9]/g, "");
  if (!rawAsn) {
    res.writeHead(400);
    return res.end(JSON.stringify({ error: "Missing or invalid ASN parameter" }));
  }
  const cachedValidate = resultCacheGet(validateResultCache, rawAsn);
  if (cachedValidate !== undefined) {
    res.writeHead(200, { "Content-Type": "application/json", "X-Cache": "HIT" });
    return res.end(JSON.stringify(cachedValidate));
  }
  const start = Date.now();
  const targetAsn = parseInt(rawAsn);

  try {
    // Phase 1: Fetch core data — 5s cap prevents large ASNs from blocking Phase 2
    const [prefixData, pdbNet, neighbourData, overviewData] = await Promise.all([
      fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
      fetchPeeringDB("/net?asn=" + rawAsn),
      fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
      fetchRipeStatCached("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + rawAsn, { timeout: 5000 }),
    ]);

    const allPrefixes = (prefixData && prefixData.data && prefixData.data.prefixes ? prefixData.data.prefixes : []).map(function(p) { return p.prefix; });
    // Use all prefixes for RPKI validation (local lookup is fast, no API calls)
    const samplePrefixes = allPrefixes;
    const net = pdbNet && pdbNet.data && pdbNet.data[0] ? pdbNet.data[0] : {};
    const netId = net.id;
    const neighbours = neighbourData && neighbourData.data && neighbourData.data.neighbours ? neighbourData.data.neighbours : [];

    const bogonResult = computeBogonResult(allPrefixes, neighbours, prefixData, neighbourData);

    // Phase 2: All API-dependent validations in parallel
    await ensureAspaCache(); // Ensure ROA data is loaded before RPKI-dependent checks
    var validationPromises = {
      irr: checks.checkIRR(rawAsn),
      rpki_completeness: checks.checkRpkiCompleteness(rawAsn, allPrefixes),
      abuse_contact: checks.checkAbuseContact(rawAsn),
      blocklist: checks.checkBlocklist(samplePrefixes),
      manrs: checks.checkManrs(rawAsn),
      rdns: checks.checkRdns(samplePrefixes),
      visibility: checks.checkVisibility(rawAsn, samplePrefixes),
      communities: checks.checkCommunities(samplePrefixes),
      geolocation: checks.checkGeolocation(samplePrefixes),
      rpsl: checks.checkRpsl(rawAsn),
      ix_route_server: checks.checkIxRouteServer(rawAsn),
      resource_cert: checks.checkResourceCert(rawAsn, allPrefixes),
    };

    const facCountriesPromise = checks.fetchFacCountries(netId);

    // Run all validations in parallel — 5s cap per check, total validate bounded to ~10s
    var keys = Object.keys(validationPromises);
    var promises = keys.map(function(k) {
      return Promise.race([
        validationPromises[k],
        new Promise(function(resolve) { setTimeout(function() { resolve({ status: "info", message: "timed out" }); }, 5000); }),
      ]);
    });
    var settled = await Promise.allSettled(promises);
    var facCountries = await facCountriesPromise;

    var validations = {};
    keys.forEach(function(key, i) {
      if (settled[i].status === "fulfilled") {
        validations[key] = settled[i].value;
      } else {
        validations[key] = { status: "error", error: settled[i].reason ? String(settled[i].reason) : "Unknown error" };
      }
    });

    enrichGeolocation(validations, facCountries, net);
    validations.bogon = bogonResult;

    const { healthScore, checkResults } = scoreChecks(validations);
    const duration = Date.now() - start;
    const relationships = buildRelationships(neighbourData);

    const validateResult = {
      meta: { query: "AS" + rawAsn, duration_ms: duration, timestamp: new Date().toISOString(), total_prefixes: allPrefixes.length, prefixes_sampled: samplePrefixes.length },
      asn: targetAsn,
      name: net.name || (overviewData && overviewData.data ? overviewData.data.holder : "") || "Unknown",
      health_score: healthScore,
      score_breakdown: checkResults,
      validations: validations,
      relationships: relationships,
    };
    // Cache 0-prefix results only briefly (90s) — they may be due to temporary API failures
    // Full results with prefixes are cached for the standard 15 minutes
    const validateCacheTTL = allPrefixes.length === 0 ? 90 * 1000 : RESULT_CACHE_TTL;
    resultCacheSet(validateResultCache, rawAsn, validateResult, validateCacheTTL);
    return res.end(JSON.stringify(validateResult, null, 2));
  } catch (err) {
    res.writeHead(500);
    return res.end(JSON.stringify({ error: "Validation failed", message: err.message }));
  }
}

module.exports = { handleValidate };
