"""
Empirical test for OWASP research §3:
Does CVSS base severity predict real-world exploitation (CISA KEV membership)?

Ground truth (realised exploitation) = NVD 'hasKev' flag (CVE is in the CISA
Known Exploited Vulnerabilities catalog). Population = all CVEs scored with
CVSS v3 in the NVD. We pull only totalResults counts (resultsPerPage=1), so no
bulk download and no sampling-on-outcome: this is the *entire* population by band.
"""
import json, time, urllib.request, urllib.error, math

NVD = "https://services.nvd.nist.gov/rest/json/cves/2.0"
SEVS = ["CRITICAL", "HIGH", "MEDIUM", "LOW"]

def get(url, tries=5):
    for i in range(tries):
        try:
            req = urllib.request.Request(url, headers={"User-Agent": "aivistix-research/1.0"})
            with urllib.request.urlopen(req, timeout=60) as r:
                return json.loads(r.read().decode())
        except urllib.error.HTTPError as e:
            wait = 8 * (i + 1)
            print(f"  HTTP {e.code}; backoff {wait}s")
            time.sleep(wait)
        except Exception as e:
            print(f"  err {e}; retry")
            time.sleep(8)
    raise SystemExit("NVD request failed after retries")

def total(params):
    d = get(f"{NVD}?{params}&resultsPerPage=1")
    return d["totalResults"]

pop, kev = {}, {}
print("Population by CVSS v3 severity (all NVD):")
for s in SEVS:
    pop[s] = total(f"cvssV3Severity={s}")
    print(f"  {s}: {pop[s]:,}")
    time.sleep(6.5)

print("KEV (exploited-in-wild) by CVSS v3 severity:")
for s in SEVS:
    kev[s] = total(f"hasKev&cvssV3Severity={s}")
    print(f"  {s}: {kev[s]:,}")
    time.sleep(6.5)

pop_total = sum(pop.values())
kev_total = sum(kev.values())

# --- Analysis ---
print("\n===== RESULTS =====")
print(f"Total CVEs with CVSS v3: {pop_total:,}")
print(f"Total KEV CVEs with CVSS v3: {kev_total:,}")
base_rate = kev_total / pop_total
print(f"Overall exploitation (KEV) rate: {base_rate*100:.3f}%\n")

rows = []
for s in SEVS:
    p_kev_given_sev = kev[s] / pop[s] if pop[s] else 0          # precision of band
    share_of_exploited = kev[s] / kev_total if kev_total else 0  # recall contribution
    lift = p_kev_given_sev / base_rate if base_rate else 0
    rows.append((s, pop[s], kev[s], p_kev_given_sev, share_of_exploited, lift))
    print(f"{s:8} pop={pop[s]:>7,}  kev={kev[s]:>5,}  "
          f"P(KEV|sev)={p_kev_given_sev*100:5.2f}%  "
          f"share_of_exploited={share_of_exploited*100:5.1f}%  lift={lift:4.1f}x")

# "If you only patched Critical, what fraction of exploited CVEs would you miss?"
miss_if_only_critical = 1 - (kev["CRITICAL"] / kev_total)
crit_precision = kev["CRITICAL"] / pop["CRITICAL"]
print(f"\nIf you patched ONLY Critical CVEs, you would MISS {miss_if_only_critical*100:.1f}% "
      f"of actually-exploited (KEV) CVEs.")
print(f"Even among Critical CVEs, only {crit_precision*100:.2f}% are known-exploited "
      f"(i.e. {100-crit_precision*100:.2f}% of Criticals were never exploited).")

# Chi-square test of independence: severity (4) x exploited (2)
chi2 = 0.0
N = pop_total
for s in SEVS:
    for exploited, obs in [(True, kev[s]), (False, pop[s] - kev[s])]:
        row_tot = kev_total if exploited else (pop_total - kev_total)
        col_tot = pop[s]
        exp = row_tot * col_tot / N
        if exp > 0:
            chi2 += (obs - exp) ** 2 / exp
dof = (len(SEVS) - 1) * (2 - 1)
cramers_v = math.sqrt(chi2 / (N * min(len(SEVS) - 1, 1)))
print(f"\nChi-square(severity x exploited) = {chi2:,.1f}, dof={dof}")
print(f"Cramer's V = {cramers_v:.3f}  (0=no association, 1=perfect)")

# Save machine-readable results
out = {
    "retrieved": time.strftime("%Y-%m-%d"),
    "source": "NVD REST API v2.0 (services.nvd.nist.gov), CVSS v3 severity x hasKev",
    "population_total_cvss_v3": pop_total,
    "kev_total_cvss_v3": kev_total,
    "overall_kev_rate": base_rate,
    "by_severity": [
        {"severity": s, "population": pop[s], "kev": kev[s],
         "p_kev_given_severity": kev[s]/pop[s] if pop[s] else 0,
         "share_of_exploited": kev[s]/kev_total if kev_total else 0,
         "lift_vs_base": (kev[s]/pop[s])/base_rate if pop[s] and base_rate else 0}
        for s in SEVS
    ],
    "miss_if_only_critical": miss_if_only_critical,
    "critical_precision": crit_precision,
    "chi_square": chi2, "dof": dof, "cramers_v": cramers_v,
}
with open("kev_results.json", "w") as f:
    json.dump(out, f, indent=2)
print("\nSaved kev_results.json")
