/**
 * Local Database Client for PeerCortex
 * Replaces external API calls with local PostgreSQL queries
 * BGP + RPKI + Threat Intel + RDAP caching
 */

const { Pool } = require('pg');

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

// RDAP Cache (in-memory for this session)
const rdapCache = new Map();
const RDAP_CACHE_TTL = 3600000; // 1 hour

// ══════════════════════════════════════════════════════════════
// BGP FUNCTIONS
// ══════════════════════════════════════════════════════════════

async function getBgpStatus(prefix) {
  try {
    const result = await pool.query(
      `SELECT DISTINCT origin_asn, MAX(visibility_percent) as visibility_percent, MAX(last_seen) as last_seen
       FROM bgp_routes
       WHERE prefix = $1::cidr
       GROUP BY origin_asn`,
      [prefix]
    );

    if (result.rows.length === 0) {
      return {
        announced: false,
        origin_asns: [],
        visibility_percent: 0,
        last_seen: new Date().toISOString(),
        source: 'local_bgp',
      };
    }

    return {
      announced: true,
      origin_asns: result.rows.map(r => r.origin_asn),
      visibility_percent: Math.max(...result.rows.map(r => parseFloat(r.visibility_percent) || 0)),
      last_seen: result.rows[0].last_seen || new Date().toISOString(),
      source: 'local_bgp',
    };
  } catch (error) {
    console.error('[Local DB] BGP Status Error:', error.message);
    return null;
  }
}

async function getAnnouncedPrefixes(asn) {
  try {
    const result = await pool.query(
      `SELECT prefix, origin_asn, visibility_percent, last_seen
       FROM bgp_routes
       WHERE origin_asn = $1
       ORDER BY visibility_percent DESC
       LIMIT 100`,
      [asn]
    );
    return result.rows;
  } catch (error) {
    console.error('[Local DB] Announced Prefixes Error:', error.message);
    return [];
  }
}

async function checkBgpHijack(prefix) {
  try {
    const result = await pool.query(
      `SELECT DISTINCT origin_asn FROM bgp_routes WHERE prefix = $1::cidr`,
      [prefix]
    );
    return result.rows.length > 1 ? result.rows.map(r => r.origin_asn) : [];
  } catch (error) {
    console.error('[Local DB] Hijack Check Error:', error.message);
    return [];
  }
}

// ══════════════════════════════════════════════════════════════
// RPKI FUNCTIONS
// ══════════════════════════════════════════════════════════════

async function validateRpki(prefix, originAsn) {
  try {
    const prefixParts = prefix.split('/');
    if (prefixParts.length !== 2) {
      return { status: 'unknown', description: 'Invalid CIDR format' };
    }

    const prefixLength = parseInt(prefixParts[1]);

    // Query for covering ROAs
    const result = await pool.query(
      `SELECT * FROM rpki_roas
       WHERE $1::cidr << (prefix || '/' || max_length)::cidr
       AND origin_asn = $2
       AND expires > NOW()
       LIMIT 10`,
      [prefix, originAsn]
    );

    if (result.rows.length === 0) {
      const anyRoa = await pool.query(
        `SELECT 1 FROM rpki_roas WHERE $1::cidr << prefix AND expires > NOW() LIMIT 1`,
        [prefix]
      );

      if (anyRoa.rows.length > 0) {
        return {
          status: 'invalid',
          prefix,
          asn: originAsn,
          description: `RPKI INVALID: ROAs exist but origin ASN ${originAsn} not authorized`,
        };
      }

      return {
        status: 'not-found',
        prefix,
        asn: originAsn,
        description: 'No matching ROA found (unprotected)',
      };
    }

    const roa = result.rows[0];
    if (prefixLength > roa.max_length) {
      return {
        status: 'invalid',
        prefix,
        asn: originAsn,
        max_length: roa.max_length,
        description: `RPKI INVALID: Prefix length ${prefixLength} > max_length ${roa.max_length}`,
      };
    }

    return {
      status: 'valid',
      prefix,
      asn: originAsn,
      max_length: roa.max_length,
      expires: roa.expires,
      description: `RPKI VALID: Origin ASN ${originAsn} authorized`,
    };
  } catch (error) {
    console.error('[Local DB] RPKI Validation Error:', error.message);
    return { status: 'unknown', description: 'RPKI validation error' };
  }
}

async function getRoasForAsn(asn) {
  try {
    const result = await pool.query(
      `SELECT prefix, max_length, expires FROM rpki_roas
       WHERE origin_asn = $1 AND expires > NOW()
       ORDER BY prefix`,
      [asn]
    );
    return result.rows;
  } catch (error) {
    console.error('[Local DB] ROAs for ASN Error:', error.message);
    return [];
  }
}

// ══════════════════════════════════════════════════════════════
// THREAT INTEL FUNCTIONS
// ══════════════════════════════════════════════════════════════

async function getThreatIntel(ip) {
  try {
    const result = await pool.query(
      `SELECT ip_address, threat_level, confidence_score, source, details, cached_at
       FROM threat_intel
       WHERE ip_address = $1::inet
       AND expires_at > NOW()
       LIMIT 1`,
      [ip]
    );

    return result.rows.length > 0 ? result.rows[0] : null;
  } catch (error) {
    console.error('[Local DB] Threat Intel Error:', error.message);
    return null;
  }
}

async function isMaliciousIp(ip) {
  try {
    const result = await pool.query(
      `SELECT 1 FROM threat_intel
       WHERE ip_address = $1::inet
       AND threat_level IN ('CRITICAL', 'HIGH')
       AND expires_at > NOW()
       LIMIT 1`,
      [ip]
    );
    return result.rows.length > 0;
  } catch (error) {
    console.error('[Local DB] Malicious IP Check Error:', error.message);
    return false;
  }
}

// ══════════════════════════════════════════════════════════════
// RDAP CACHING (in-memory)
// ══════════════════════════════════════════════════════════════

function getRdapCached(resource) {
  const cached = rdapCache.get(resource);
  if (cached && Date.now() - cached.timestamp < RDAP_CACHE_TTL) {
    console.log(`[RDAP Cache] HIT: ${resource}`);
    return cached.data;
  }
  if (cached) rdapCache.delete(resource);
  return null;
}

function setRdapCached(resource, data) {
  rdapCache.set(resource, { data, timestamp: Date.now() });
  console.log(`[RDAP Cache] SET: ${resource} (TTL: 1h)`);
}

// ══════════════════════════════════════════════════════════════
// RIPE STAT API WRAPPER (Drop-in replacements for external API calls)
// ══════════════════════════════════════════════════════════════

async function getRipeStatAnnouncedPrefixes(asn) {
  try {
    const prefixes = await getAnnouncedPrefixes(asn);
    return {
      status: 'ok',
      cached: false,
      data: {
        resource: `AS${asn}`,
        prefixes: prefixes.map(p => ({
          prefix: p.prefix,
          origin_asn: p.origin_asn,
        })),
      },
    };
  } catch (error) {
    console.error('[Local RIPE Stat] Announced Prefixes Error:', error.message);
    return { status: 'ok', data: { resource: `AS${asn}`, prefixes: [] } };
  }
}

async function getRipeStatAsnNeighbours(asn) {
  try {
    const result = await pool.query(
      `SELECT DISTINCT origin_asn FROM bgp_routes LIMIT 200`
    );

    const neighbours = result.rows.map(r => ({
      asn: r.origin_asn,
      type: 'unknown',
      prefixes: 0,
    }));

    return {
      status: 'ok',
      cached: false,
      data: {
        resource: `AS${asn}`,
        neighbours: neighbours,
      },
    };
  } catch (error) {
    console.error('[Local RIPE Stat] ASN Neighbours Error:', error.message);
    return { status: 'ok', data: { resource: `AS${asn}`, neighbours: [] } };
  }
}

async function getRipeStatAsOverview(asn) {
  try {
    const prefixes = await getAnnouncedPrefixes(asn);
    const roasCount = await pool.query(
      `SELECT COUNT(*) as count FROM rpki_roas WHERE origin_asn = $1 AND expires > NOW()`,
      [asn]
    );

    return {
      status: 'ok',
      cached: false,
      data: {
        resource: `AS${asn}`,
        asn: asn,
        holder: 'Unknown',
        announced_prefixes_count: prefixes.length,
        description: [{ descr: 'Local Database ASN Overview' }],
        type: 'asn',
        rpki_status: roasCount.rows[0].count > 0 ? 'signed' : 'not-signed',
      },
    };
  } catch (error) {
    console.error('[Local RIPE Stat] AS Overview Error:', error.message);
    return { status: 'ok', data: { resource: `AS${asn}`, announced_prefixes_count: 0 } };
  }
}

async function getRipeStatVisibility(asn) {
  try {
    const result = await pool.query(
      `SELECT COALESCE(AVG(visibility_percent), 0) as avg_visibility
       FROM bgp_routes
       WHERE origin_asn = $1`,
      [asn]
    );

    return {
      status: 'ok',
      cached: false,
      data: {
        resource: `AS${asn}`,
        visibility: {
          ipv4: {
            ris_peers_seeing: Math.round(result.rows[0].avg_visibility),
            total_ris_peers: 100,
            sees_ris_peers: Math.round(result.rows[0].avg_visibility),
          },
          ipv6: {
            ris_peers_seeing: 0,
            total_ris_peers: 100,
            sees_ris_peers: 0,
          },
        },
      },
    };
  } catch (error) {
    console.error('[Local RIPE Stat] Visibility Error:', error.message);
    return { status: 'ok', data: { resource: `AS${asn}`, visibility: {} } };
  }
}

async function getRipeStatPrefixSizeDistribution(asn) {
  try {
    const result = await pool.query(
      `SELECT masklen(prefix) as prefix_len
       FROM bgp_routes
       WHERE origin_asn = $1
       ORDER BY masklen(prefix)`,
      [asn]
    );

    const distribution = {};
    result.rows.forEach(row => {
      const len = row.prefix_len;
      distribution[len] = (distribution[len] || 0) + 1;
    });

    return {
      status: 'ok',
      cached: false,
      data: {
        resource: `AS${asn}`,
        ipv4_prefix_size: Object.keys(distribution)
          .map(len => ({ prefix_length: parseInt(len), count: distribution[len] }))
          .sort((a, b) => a.prefix_length - b.prefix_length),
        ipv6_prefix_size: [],
      },
    };
  } catch (error) {
    console.error('[Local RIPE Stat] Prefix Size Distribution Error:', error.message);
    return { status: 'ok', data: { resource: `AS${asn}`, ipv4_prefix_size: [] } };
  }
}

// ══════════════════════════════════════════════════════════════
// STATS & HEALTH CHECK
// ══════════════════════════════════════════════════════════════

async function getLocalDbStats() {
  try {
    const bgp = await pool.query(`SELECT COUNT(*) as count FROM bgp_routes`);
    const rpki = await pool.query(`SELECT COUNT(*) as count FROM rpki_roas WHERE expires > NOW()`);
    const threat = await pool.query(`SELECT COUNT(*) as count FROM threat_intel WHERE expires_at > NOW()`);

    return {
      bgp_routes: parseInt(bgp.rows[0].count),
      rpki_roas: parseInt(rpki.rows[0].count),
      threat_intel: parseInt(threat.rows[0].count),
      rdap_cache_entries: rdapCache.size,
    };
  } catch (error) {
    console.error('[Local DB] Stats Error:', error.message);
    return null;
  }
}

async function cleanup() {
  await pool.end();
}

// ══════════════════════════════════════════════════════════════
// EXPORTS
// ══════════════════════════════════════════════════════════════

module.exports = {
  // BGP
  getBgpStatus,
  getAnnouncedPrefixes,
  checkBgpHijack,

  // RPKI
  validateRpki,
  getRoasForAsn,

  // Threat Intel
  getThreatIntel,
  isMaliciousIp,

  // RIPE Stat API Wrappers
  getRipeStatAnnouncedPrefixes,
  getRipeStatAsnNeighbours,
  getRipeStatAsOverview,
  getRipeStatVisibility,
  getRipeStatPrefixSizeDistribution,

  // RDAP Cache
  getRdapCached,
  setRdapCached,

  // Health
  getLocalDbStats,
  cleanup,
};
