/**
 * BGP Hijack Monitor
 *
 * Background job that monitors own ASNs for unexpected BGP origin changes.
 * Runs periodically (every 6 hours via systemd timer) to detect potential hijacks.
 *
 * Usage:
 *   node bgp-hijack-monitor.js
 *
 * Environment Variables:
 *   - DB_HOST: PostgreSQL host (default: 192.168.178.82)
 *   - DB_PORT: PostgreSQL port (default: 5432)
 *   - DB_NAME: Database name (default: llm_gateway)
 *   - DB_USER: Database user (default: llm)
 *   - DB_PASSWORD: Database password (default: llm_secure_2026)
 *   - OWN_ASNS: Comma-separated list of ASNs to monitor (e.g., "13335,15169,32787")
 *   - FINDINGS_TABLE: Table to write findings to (default: findings)
 */

const pg = require('pg');
const pool = new pg.Pool({
  host: process.env.DB_HOST || '192.168.178.82',
  port: parseInt(process.env.DB_PORT || '5432'),
  database: process.env.DB_NAME || 'llm_gateway',
  user: process.env.DB_USER || 'llm',
  password: process.env.DB_PASSWORD || 'llm_secure_2026',
  max: 5,
  idleTimeoutMillis: 10000,
  connectionTimeoutMillis: 5000,
});

const OWN_ASNS = (process.env.OWN_ASNS || '13335,15169').split(',').map(a => a.trim());
const FINDINGS_TABLE = process.env.FINDINGS_TABLE || 'findings';

/**
 * Get all prefixes announced by an ASN from BGP routes table
 */
async function getBaselinePrefixes(asn) {
  const client = await pool.connect();
  try {
    const query = `
      SELECT DISTINCT prefix, origin_asn
      FROM bgp_routes
      WHERE origin_asn = $1
      LIMIT 1000
    `;
    const result = await client.query(query, [asn]);
    return result.rows;
  } finally {
    client.release();
  }
}

/**
 * Get current BGP routes for a set of prefixes
 */
async function getCurrentRoutes(prefixes) {
  if (prefixes.length === 0) return [];

  const client = await pool.connect();
  try {
    const placeholders = prefixes.map((_, i) => `$${i + 1}`).join(',');
    const query = `
      SELECT prefix, origin_asn
      FROM bgp_routes
      WHERE prefix = ANY(ARRAY[${placeholders}]::CIDR[])
      AND last_seen > NOW() - INTERVAL '1 hour'
    `;
    const result = await client.query(query, prefixes);
    return result.rows;
  } finally {
    client.release();
  }
}

/**
 * Check for hijacks: unexpected origin ASNs for known prefixes
 */
async function checkForHijacks(asn) {
  console.log(`[BGP Hijack Monitor] Checking ASN ${asn} for unexpected origin changes...`);

  try {
    // Get baseline (expected) prefixes for this ASN
    const baseline = await getBaselinePrefixes(asn);
    if (baseline.length === 0) {
      console.log(`[BGP Hijack Monitor] No baseline prefixes found for ASN ${asn}`);
      return;
    }

    console.log(`[BGP Hijack Monitor] Baseline: ${baseline.length} prefixes for AS${asn}`);

    // Get current routes for these prefixes
    const prefixList = baseline.map(r => r.prefix);
    const current = await getCurrentRoutes(prefixList);

    // Group current routes by prefix
    const currentByPrefix = {};
    current.forEach(r => {
      if (!currentByPrefix[r.prefix]) {
        currentByPrefix[r.prefix] = [];
      }
      currentByPrefix[r.prefix].push(r.origin_asn);
    });

    // Compare: detect unexpected origin ASNs
    const hijackCandidates = [];
    baseline.forEach(expectedRoute => {
      const prefix = expectedRoute.prefix;
      const expectedAsn = expectedRoute.origin_asn;

      if (currentByPrefix[prefix]) {
        const currentAsns = currentByPrefix[prefix];

        // If the expected ASN is NOT in the current set of origin ASNs, it's a hijack
        if (!currentAsns.includes(expectedAsn)) {
          hijackCandidates.push({
            prefix,
            expectedAsn,
            foundAsns: currentAsns,
          });
        }

        // If there are MULTIPLE origin ASNs (not just the expected one), it's also suspicious (MOAS)
        if (currentAsns.length > 1) {
          hijackCandidates.push({
            prefix,
            expectedAsn,
            foundAsns: currentAsns,
            type: 'MOAS', // Multiple Origin ASNs
          });
        }
      }
    });

    if (hijackCandidates.length > 0) {
      console.log(`[BGP Hijack Monitor] ⚠️ DETECTED ${hijackCandidates.length} hijack candidates`);

      // Log each finding to the findings table
      const client = await pool.connect();
      try {
        for (const hijack of hijackCandidates) {
          const description = hijack.type === 'MOAS'
            ? `MOAS detected: Multiple origin ASNs (${hijack.foundAsns.join(', ')}) announcing ${hijack.prefix}`
            : `BGP Hijack: AS${hijack.expectedAsn} expected but AS${hijack.foundAsns.join(', AS')} found announcing ${hijack.prefix}`;

          const findingQuery = `
            INSERT INTO ${FINDINGS_TABLE}
            (timestamp, source, severity, asn, description, details)
            VALUES (NOW(), 'bgp_hijack_monitor', 'HIGH', $1, $2, $3)
            ON CONFLICT DO NOTHING
          `;

          const details = JSON.stringify({
            prefix: hijack.prefix,
            expected_asn: hijack.expectedAsn,
            found_asns: hijack.foundAsns,
            type: hijack.type || 'HIJACK',
            detected_at: new Date().toISOString(),
          });

          await client.query(findingQuery, [asn, description, details]);
        }
      } finally {
        client.release();
      }
    } else {
      console.log(`[BGP Hijack Monitor] ✓ No hijacks detected for AS${asn}`);
    }

  } catch (err) {
    console.error(`[BGP Hijack Monitor] Error checking ASN ${asn}:`, err.message);
  }
}

/**
 * Main: Check all monitored ASNs
 */
async function main() {
  console.log(`[BGP Hijack Monitor] Starting hijack detection for ASNs: ${OWN_ASNS.join(', ')}`);

  for (const asn of OWN_ASNS) {
    await checkForHijacks(asn);
  }

  console.log('[BGP Hijack Monitor] Hijack detection complete');
  await pool.end();
  process.exit(0);
}

// Error handling
process.on('error', (err) => {
  console.error('[BGP Hijack Monitor] Fatal error:', err);
  process.exit(1);
});

main().catch(err => {
  console.error('[BGP Hijack Monitor] Main loop error:', err);
  process.exit(1);
});
