#!/usr/bin/env node

import axios from 'axios';
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
import readline from 'readline';

dotenv.config({ path: '.env.local' });

const DB = await mysql.createConnection({
  host: process.env.DB_HOST,
  port: process.env.DB_PORT || 3306,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});

function formatDate(isoString) {
  if (!isoString) return null;
  const date = new Date(isoString);
  return date.toISOString().slice(0, 19).replace('T', ' ');
}

function getPreviousMonthID() {
  const now = new Date();
  now.setMonth(now.getMonth() - 1);
  const year = now.getFullYear();
  const month = now.toLocaleString('en-US', { month: 'short' }); // e.g. "Mar"
  return `${year}-${month}`;
}

function getLocalizedText(entry, fallback = '') {
  if (!entry || typeof entry !== 'object') return fallback;

  // Try to get English (US) first
  if (entry['en-US']?.Value) return entry['en-US'].Value;

  // Fallback: get first entry with a Value field
  for (const key in entry) {
    if (entry[key]?.Value) {
      return entry[key].Value;
    }
  }

  return fallback;
}


function extractDescription(notes = [], fallback = '') {
  const note = notes.find(n =>
    n.Title === 'Description' &&
    n.Type === 2 &&
    typeof n.Value === 'string' &&
    n.Value.trim().length > 0
  );
  return note?.Value || fallback;
}




function extractAffectedProducts(vuln) {
  const fixed = vuln.ProductStatuses?.Fixed ?? [];
  const known = vuln.ProductStatuses?.KnownAffected ?? [];
  const underInvestigation = vuln.ProductStatuses?.UnderInvestigation ?? [];
  return [...new Set([...fixed, ...known, ...underInvestigation])].join(', ');
}

async function fetchCVRFDoc(cvrfId) {
  const url = `https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/${cvrfId}`;
  try {
    const res = await axios.get(url, {
      headers: { 'Accept': 'application/json' }
    });
    return res.data;
  } catch (err) {
    console.error(`❌ Failed to fetch CVRF for ${cvrfId}:`, err.message);
    return null;
  }
}

async function storeVulnerability(v, published, modified) {
  const cve = v.CVE;
  console.log(`⏳ Saving ${cve}...`);
  const title = getLocalizedText(v.Title, '');
  const description = extractDescription(v.Notes, title);
  const affectedProducts = extractAffectedProducts(v);
  const severity = v.Threats?.find(t => t.Type === 'Severity')?.Description?.Value || '';
  const impact = v.Threats?.find(t => t.Type === 'Impact')?.Description?.Value || '';
  const exploitability = v.Threats?.find(t => t.Type === 'Exploitability')?.Description?.Value || '';
  const cvssSet = v.CVSSScoreSets?.[0];
  const cvssScore = cvssSet?.BaseScore || null;
  const cvssVector = cvssSet?.Vector || '';
  const cweList = Array.isArray(v.CWE) ? v.CWE : [];

  if (!title) {
    console.warn(`⚠️ No title found for ${cve}`);
    console.debug('Title raw:', JSON.stringify(v.Title, null, 2));
  }

  if (!description) {
    console.warn(`⚠️ No description for ${cve}`);
    console.debug('Notes raw:', JSON.stringify(v.Notes, null, 2));
  }


  try {
    // Insert or update the main CVE entry (no cwe column now)
    await DB.execute(
      `INSERT INTO microsoft_cves (cve_id, title, severity, published_date, last_modified_date,
        affected_products, description, exploitability, cvss_score, cvss_vector)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
       ON DUPLICATE KEY UPDATE last_modified_date = VALUES(last_modified_date),
                                affected_products = VALUES(affected_products),
                                severity = VALUES(severity),
                                description = VALUES(description),
                                exploitability = VALUES(exploitability),
                                cvss_score = VALUES(cvss_score),
                                cvss_vector = VALUES(cvss_vector)`,
      [cve, title, severity, published, modified, affectedProducts, description, exploitability, cvssScore, cvssVector]
    );

    // Insert CWE entries and links
    for (const { ID: cweId, Value: cweName } of cweList) {
      if (!cweId) continue;

      // Insert into cwe_entries if not already present
      await DB.execute(
        `INSERT IGNORE INTO cwe_entries (cwe_id, cwe_name) VALUES (?, ?)`,
        [cweId, cweName || null]
      );

      // Link CVE to CWE
      await DB.execute(
        `INSERT IGNORE INTO cve_cwe (cve_id, cwe_id) VALUES (?, ?)`,
        [cve, cweId]
      );
    }

    console.log(`✅ Saved ${cve} — ${severity}, ${cvssScore ?? '?'}`);
  } catch (err) {
    console.error(`❌ DB error for ${cve}:`, err.message);
  }
}



function waitForKeypress() {
  return new Promise((resolve) => {
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });

    rl.question('🔄 Press any key to continue to the next 10 CVEs...', () => {
      rl.close();
      resolve();
    });
  });
}

async function importPreviousMonth() {
  const cvrfId = getPreviousMonthID();
  console.log(`📡 Fetching CVEs for ${cvrfId}...`);

  const data = await fetchCVRFDoc(cvrfId);
  if (!data) {
    console.log("❌ No data returned from MSRC.");
    return;
  }

  const published = formatDate(data.DocumentTracking?.InitialReleaseDate);
  const modified = formatDate(data.DocumentTracking?.CurrentReleaseDate);

  const vulns = data.Vulnerability || [];
  console.log(`📋 Found ${vulns.length} vulnerabilities to import.`);

  const batchSize = 10;

  for (let i = 0; i < vulns.length; i += batchSize) {
    const batch = vulns.slice(i, i + batchSize);
    console.log(`🔄 Processing batch ${i / batchSize + 1} (${batch.length} items)...`);

    for (const v of batch) {
      await storeVulnerability(v, published, modified);
    }
  }

  console.log(`✅ Finished importing ${vulns.length} CVEs for ${cvrfId}`);
  await DB.end();
}
console.log("🚀 Starting MSRC sync now...");
await importPreviousMonth();

