/* global React */
const { useState: useS_ld, useEffect: useE_ld, useMemo: useM_ld } = React;

/* ──────────────────────────────────────────────
   Live threat data — backed by REAL public feeds.
   Primary source: CISA Known Exploited Vulnerabilities Catalog
     https://www.cisa.gov/known-exploited-vulnerabilities-catalog
   The JSON feed is publicly served and CORS-friendly.
   Fallback: small embedded snapshot so the UI never shows a black box.
   ────────────────────────────────────────────── */

/* ──────────────────────────────────────────────
   Live threat data — backed by REAL public feeds.

   Primary source: cisagov/kev-data on GitHub — the OFFICIAL mirror that
   CISA itself maintains of the Known Exploited Vulnerabilities catalog.
   Synced within minutes of the canonical source at cisa.gov/kev.
   raw.githubusercontent.com serves with permissive CORS, so browsers can
   read it directly. The canonical cisa.gov URL does NOT send CORS headers,
   so it's only useful from a server-side scraper.

   Refs:
   - Mirror:  https://github.com/cisagov/kev-data
   - Source:  https://www.cisa.gov/known-exploited-vulnerabilities-catalog

   Fallback chain: GitHub mirror → alt branches → embedded snapshot.
   ───────────────────────────────────────────── */

const KEV_FEED_URLS = [
  "https://raw.githubusercontent.com/cisagov/kev-data/develop/known_exploited_vulnerabilities.json",
  "https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.json",
];

// Local snapshot fallback (used only if the network call fails).
// Dates are dynamic — set to N days ago relative to today — so "recent"
// counters and ticker stay meaningful even when offline.
function makeFallback() {
  const d = (days) => {
    const x = new Date(Date.now() - days * 86400000);
    return x.toISOString().slice(0, 10);
  };
  return {
    catalogVersion: "snapshot-rolling",
    dateReleased: new Date().toISOString(),
    count: 1287,
    vulnerabilities: [
      { cveID: "CVE-2024-49138", vendorProject: "Microsoft",   product: "Windows CLFS",      dateAdded: d(2),  shortDescription: "Elevation of privilege in CLFS driver actively exploited.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2024-43461", vendorProject: "Microsoft",   product: "MSHTML",            dateAdded: d(5),  shortDescription: "Spoofing vulnerability used in zero-day attack chain.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-38812", vendorProject: "VMware",      product: "vCenter Server",    dateAdded: d(11), shortDescription: "Heap overflow allowing remote code execution.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-30088", vendorProject: "Microsoft",   product: "Windows Kernel",    dateAdded: d(14), shortDescription: "Race condition leading to privilege escalation.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-21762", vendorProject: "Fortinet",    product: "FortiOS",           dateAdded: d(20), shortDescription: "Out-of-bounds write in SSL VPN exploited in the wild.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2024-3400",  vendorProject: "Palo Alto",   product: "GlobalProtect",     dateAdded: d(28), shortDescription: "Command injection in PAN-OS GlobalProtect feature.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2023-46805", vendorProject: "Ivanti",      product: "Connect Secure",    dateAdded: d(35), shortDescription: "Authentication bypass in Ivanti Connect Secure.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2024-27198", vendorProject: "JetBrains",   product: "TeamCity",          dateAdded: d(42), shortDescription: "Authentication bypass leading to admin takeover.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2024-1709",  vendorProject: "ConnectWise", product: "ScreenConnect",     dateAdded: d(48), shortDescription: "Authentication bypass and path traversal.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2024-23113", vendorProject: "Fortinet",    product: "FortiOS",           dateAdded: d(55), shortDescription: "Format string vulnerability in fgfmd daemon.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2023-44487", vendorProject: "IETF",        product: "HTTP/2",            dateAdded: d(62), shortDescription: "Rapid Reset — global DDoS amplification.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2023-22515", vendorProject: "Atlassian",   product: "Confluence",        dateAdded: d(70), shortDescription: "Broken access control in Confluence Data Center.", knownRansomwareCampaignUse: "Known" },
      // APAC
      { cveID: "CVE-2024-44068", vendorProject: "Samsung",     product: "Mobile Processor",  dateAdded: d(3),  shortDescription: "Use-after-free in m2m1shot driver — exploited in-the-wild.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-7589",  vendorProject: "Huawei",      product: "HG255s",            dateAdded: d(7),  shortDescription: "Authentication bypass on residential gateway.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-21887", vendorProject: "Ivanti",      product: "Connect Secure",    dateAdded: d(13), shortDescription: "Command injection — chained with auth bypass.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2023-50164", vendorProject: "Trend Micro", product: "Apex One",          dateAdded: d(18), shortDescription: "Origin validation error allowing RCE.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-3273",  vendorProject: "D-Link",      product: "NAS Devices",       dateAdded: d(24), shortDescription: "Hardcoded credentials enabling unauthenticated access.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-29973", vendorProject: "Synology",    product: "DiskStation",       dateAdded: d(31), shortDescription: "Command injection in remote management interface.", knownRansomwareCampaignUse: "Known" },
      { cveID: "CVE-2024-27130", vendorProject: "QNAP",        product: "QTS",               dateAdded: d(37), shortDescription: "Stack buffer overflow in share API.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2023-1389",  vendorProject: "TP-Link",     product: "Archer AX21",       dateAdded: d(44), shortDescription: "Command injection on country setter — Mirai-derived botnets.", knownRansomwareCampaignUse: "Unknown" },
      // EU
      { cveID: "CVE-2023-23752", vendorProject: "Sophos",      product: "Firewall",          dateAdded: d(9),  shortDescription: "Improper access control in management interface.", knownRansomwareCampaignUse: "Unknown" },
      { cveID: "CVE-2024-22024", vendorProject: "SAP",         product: "NetWeaver AS",      dateAdded: d(16), shortDescription: "XXE allowing privilege escalation.", knownRansomwareCampaignUse: "Unknown" },
      // ME
      { cveID: "CVE-2024-24919", vendorProject: "CheckPoint",  product: "Security Gateway",  dateAdded: d(6),  shortDescription: "Information disclosure on remote access VPN.", knownRansomwareCampaignUse: "Unknown" },
    ],
  };
}

// Vendor → approximate origin region on the world map (percent positions
// matching the dotted map in dashmock.jsx). Map is a stylized dotted bitmap
// 38 cols × 14 rows — land roughly: NA cols 2-13 rows 1-7; SA cols 5-8 rows
// 8-11; Eurasia cols 17-33 rows 0-7; Oceania cols 30-32 row 9-10. All ping
// coords are clamped to land regions of the bitmap.
const VENDOR_GEO = {
  // North America (continental US bias, on landmass)
  "Microsoft":   { x: 22, y: 42, region: "NA" },
  "Google":      { x: 14, y: 40, region: "NA" },
  "Apple":       { x: 13, y: 41, region: "NA" },
  "Cisco":       { x: 16, y: 42, region: "NA" },
  "Adobe":       { x: 14, y: 41, region: "NA" },
  "Oracle":      { x: 22, y: 44, region: "NA" },
  "IBM":         { x: 28, y: 39, region: "NA" },
  "VMware":      { x: 15, y: 42, region: "NA" },
  "Citrix":      { x: 28, y: 41, region: "NA" },
  "Atlassian":   { x: 26, y: 43, region: "NA" },
  "Mozilla":     { x: 13, y: 42, region: "NA" },
  "Fortinet":    { x: 15, y: 41, region: "NA" },
  "Palo Alto":   { x: 14, y: 42, region: "NA" },
  "PaloAlto":    { x: 14, y: 42, region: "NA" },
  "F5":          { x: 13, y: 39, region: "NA" },
  "Ivanti":      { x: 23, y: 42, region: "NA" },
  "SolarWinds":  { x: 24, y: 46, region: "NA" },
  "ConnectWise": { x: 27, y: 43, region: "NA" },
  "Progress":    { x: 28, y: 40, region: "NA" },
  "PaperCut":    { x: 24, y: 42, region: "NA" },
  "Apache":      { x: 25, y: 41, region: "NA" },
  "Cloudflare":  { x: 14, y: 40, region: "NA" },
  "Zimbra":      { x: 22, y: 41, region: "NA" },
  // Europe
  "JetBrains":   { x: 51, y: 31, region: "EU" },
  "SAP":         { x: 51, y: 30, region: "EU" },
  "Sophos":      { x: 49, y: 29, region: "EU" },
  "Atos":        { x: 50, y: 32, region: "EU" },
  "Dassault":    { x: 49, y: 31, region: "EU" },
  "Ericsson":    { x: 53, y: 26, region: "EU" },
  // Middle East
  "CheckPoint":  { x: 57, y: 36, region: "ME" },
  // Asia
  "Samsung":     { x: 83, y: 39, region: "APAC" },
  "Canon":       { x: 86, y: 41, region: "APAC" },
  "Sony":        { x: 87, y: 41, region: "APAC" },
  "Fujitsu":     { x: 87, y: 40, region: "APAC" },
  "Huawei":      { x: 78, y: 43, region: "APAC" },
  "TP-Link":     { x: 79, y: 43, region: "APAC" },
  "Trend Micro": { x: 81, y: 45, region: "APAC" },
  "D-Link":      { x: 81, y: 46, region: "APAC" },
  "Synology":    { x: 81, y: 45, region: "APAC" },
  "ASUS":        { x: 81, y: 45, region: "APAC" },
  "QNAP":        { x: 81, y: 46, region: "APAC" },
  // Catch-all / Open source
  "Linux":       { x: 50, y: 30, region: "EU" },
  "GNU":         { x: 50, y: 30, region: "EU" },
  "IETF":        { x: 22, y: 41, region: "NA" },
  "OpenSSL":     { x: 50, y: 31, region: "EU" },
};

// Region buckets cycled through for visual diversity on the threat map.
// Order matters — the round-robin starts from the first and walks forward.
const REGIONS = [
  { code: "NA",   x: 22, y: 43 },
  { code: "EU",   x: 51, y: 30 },
  { code: "APAC", x: 80, y: 43 },
  { code: "SA",   x: 19, y: 73 },  // South America (Brasil region)
  { code: "ME",   x: 57, y: 36 },
];

const ROTATION_HUBS = [
  { x: 22, y: 43 }, { x: 51, y: 30 }, { x: 80, y: 43 }, { x: 87, y: 41 },
  { x: 57, y: 36 }, { x: 81, y: 45 }, { x: 19, y: 73 }, { x: 53, y: 55 },
];

function geoForVendor(v, i) {
  if (!v) return ROTATION_HUBS[i % ROTATION_HUBS.length];
  // Try exact, then case-insensitive prefix match
  if (VENDOR_GEO[v]) return VENDOR_GEO[v];
  const key = Object.keys(VENDOR_GEO).find(
    (k) => v.toLowerCase().startsWith(k.toLowerCase())
  );
  return key ? VENDOR_GEO[key] : ROTATION_HUBS[i % ROTATION_HUBS.length];
}

// Severity rules: ransomware → high; <30d → med; else low.
function severityFor(item, now) {
  if (item.knownRansomwareCampaignUse === "Known") return "high";
  const added = new Date(item.dateAdded).getTime();
  const days = (now - added) / (1000 * 60 * 60 * 24);
  if (days <= 30) return "med";
  return "low";
}

/* ──────────────────────────────────────────────
   useLiveThreatData hook — cached in sessionStorage
   Auto-refreshes every REFRESH_MS while the tab is visible.
   ────────────────────────────────────────────── */
const REFRESH_MS    = 5 * 60 * 1000; // 5 minutes
const CACHE_TTL_MS  = 5 * 60 * 1000;

function useLiveThreatData() {
  const [state, setState] = useS_ld({
    loading: true,
    error: null,
    source: "loading",
    catalogVersion: null,
    dateReleased: null,
    total: 0,
    last30d: 0,
    last7d: 0,
    ransomware: 0,
    recent: [],
    fetchedAt: null,
  });

  useE_ld(() => {
    let cancelled = false;
    let timerId = 0;
    let controller = null;

    const consume = (data, source) => {
      if (cancelled) return;
      const vulns = Array.isArray(data.vulnerabilities) ? data.vulnerabilities : [];
      const now = Date.now();
      const d7  = now - 7  * 86400000;
      const d30 = now - 30 * 86400000;
      const last30d = vulns.filter((v) => new Date(v.dateAdded).getTime() >= d30).length;
      const last7d  = vulns.filter((v) => new Date(v.dateAdded).getTime() >= d7).length;
      const ransom  = vulns.filter((v) => v.knownRansomwareCampaignUse === "Known").length;
      const recent = [...vulns]
        .sort((a, b) => new Date(b.dateAdded) - new Date(a.dateAdded))
        .slice(0, 60);
      setState({
        loading: false,
        error: null,
        source,
        catalogVersion: data.catalogVersion || null,
        dateReleased: data.dateReleased || null,
        total: data.count || vulns.length,
        last30d, last7d, ransomware: ransom,
        recent,
        fetchedAt: now,
      });
    };

    const fetchNow = ({ useCache = true } = {}) => {
      // Honor cache (skip network when fresh enough)
      if (useCache) {
        try {
          const cached = JSON.parse(sessionStorage.getItem("cs.kev") || "null");
          if (cached && Date.now() - cached.t < CACHE_TTL_MS) {
            consume(cached.data, "cache");
            return;
          }
        } catch {}
      }

      controller && controller.abort();
      controller = new AbortController();
      const localCtl = controller;
      const timer = setTimeout(() => localCtl.abort(), 12000);

      // Try every URL in the chain, in order. Resolve on first that works.
      const tryUrls = async () => {
        let lastErr = null;
        for (const url of KEV_FEED_URLS) {
          try {
            const r = await fetch(url, { signal: localCtl.signal, cache: "no-store" });
            if (!r.ok) { lastErr = new Error("HTTP " + r.status + " from " + url); continue; }
            const data = await r.json();
            return { data, url };
          } catch (err) {
            lastErr = err;
          }
        }
        throw lastErr || new Error("all sources failed");
      };

      tryUrls()
        .then(({ data, url }) => {
          clearTimeout(timer);
          try { sessionStorage.setItem("cs.kev", JSON.stringify({ t: Date.now(), data })); } catch {}
          consume(data, "cisa");
        })
        .catch((err) => {
          clearTimeout(timer);
          if (cancelled) return;
          // Only fall back to snapshot on the FIRST failure; subsequent
          // auto-refresh failures keep the last good data intact.
          setState((s) => {
            if (s.loading) {
              const fb = makeFallback();
              const now = Date.now();
              const vulns = fb.vulnerabilities;
              const d7  = now - 7  * 86400000;
              const d30 = now - 30 * 86400000;
              return {
                loading: false,
                error: err.message || "network",
                source: "snapshot",
                catalogVersion: fb.catalogVersion,
                dateReleased: fb.dateReleased,
                total: fb.count,
                last30d: vulns.filter((v) => new Date(v.dateAdded).getTime() >= d30).length,
                last7d:  vulns.filter((v) => new Date(v.dateAdded).getTime() >= d7).length,
                ransomware: vulns.filter((v) => v.knownRansomwareCampaignUse === "Known").length,
                recent: [...vulns].sort((a, b) => new Date(b.dateAdded) - new Date(a.dateAdded)).slice(0, 60),
                fetchedAt: now,
              };
            }
            // Already have data — just note the error, keep showing previous numbers
            return { ...s, error: err.message || "network" };
          });
        });
    };

    // Initial sync
    fetchNow({ useCache: true });

    // Auto-refresh every REFRESH_MS, but only when tab is visible.
    // When tab returns to visible after being hidden longer than REFRESH_MS,
    // sync immediately so the user sees fresh data on focus.
    let lastSyncAt = Date.now();
    const tick = () => {
      if (document.visibilityState === "visible") {
        lastSyncAt = Date.now();
        fetchNow({ useCache: false });
      }
    };
    timerId = setInterval(tick, REFRESH_MS);

    const onVisibility = () => {
      if (document.visibilityState === "visible" && Date.now() - lastSyncAt > REFRESH_MS) {
        lastSyncAt = Date.now();
        fetchNow({ useCache: false });
      }
    };
    document.addEventListener("visibilitychange", onVisibility);

    return () => {
      cancelled = true;
      controller && controller.abort();
      clearInterval(timerId);
      document.removeEventListener("visibilitychange", onVisibility);
    };
  }, []);

  return state;
}

/* ──────────────────────────────────────────────
   forceResync() — expose a way to invalidate the cache + reload data.
   Used by the "resync" button next to the source label.
   ───────────────────────────────────────────── */
function forceResync() {
  try { sessionStorage.removeItem("cs.kev"); } catch {}
  location.reload();
}

function useCountUp(target, duration = 1100) {
  const [val, setVal] = useS_ld(0);
  useE_ld(() => {
    if (!Number.isFinite(target) || target <= 0) { setVal(0); return; }
    let raf = 0;
    const t0 = performance.now();
    const ease = (p) => 1 - Math.pow(1 - p, 3);
    const step = (now) => {
      const p = Math.min(1, (now - t0) / duration);
      setVal(Math.round(target * ease(p)));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [target, duration]);
  return val;
}

Object.assign(window, { useLiveThreatData, geoForVendor, severityFor, VENDOR_GEO, REGIONS, useCountUp, forceResync });
