/**
 * routes/traffic.js
 * Parse IIS W3SVC access logs for incoming IP traffic.
 * Geo-lookup using ip-api.com (free, no key needed, 45 req/min).
 * Block/unblock IPs via Windows Firewall (netsh).
 * Mount at /api/traffic in server.js.
 */
const express      = require('express');
const { execSync } = require('child_process');
const fs           = require('fs');
const path         = require('path');
const http         = require('http');
const router       = express.Router();
const authMw       = require('../middleware/auth');
const { writeAudit } = require('./audit');

const ps = (cmd, timeout = 15000) =>
  execSync(`powershell -ExecutionPolicy Bypass -Command "${cmd}"`, { timeout }).toString().trim();

// ── Geo-lookup with simple in-process cache ───────────────
const _geoCache = {};
function geoLookup(ip) {
  if (_geoCache[ip]) return Promise.resolve(_geoCache[ip]);
  // Skip private / loopback addresses
  if (/^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|127\.|::1|fe80)/i.test(ip)) {
    _geoCache[ip] = { country: 'Local', cc: '', city: '' };
    return Promise.resolve(_geoCache[ip]);
  }
  return new Promise((resolve) => {
    const req = http.get(`http://ip-api.com/json/${ip}?fields=country,countryCode,city,status`, (res) => {
      let data = '';
      res.on('data', d => data += d);
      res.on('end', () => {
        try {
          const j = JSON.parse(data);
          const geo = j.status === 'success'
            ? { country: j.country, cc: j.countryCode, city: j.city }
            : { country: 'Unknown', cc: '', city: '' };
          _geoCache[ip] = geo;
          resolve(geo);
        } catch { resolve({ country: 'Unknown', cc: '', city: '' }); }
      });
    });
    req.on('error', () => resolve({ country: 'Unknown', cc: '', city: '' }));
    req.setTimeout(3000, () => { req.destroy(); resolve({ country: 'Unknown', cc: '', city: '' }); });
  });
}

// ── Find IIS log directories ──────────────────────────────
function getIISLogPaths() {
  try {
    const raw = ps(`
      Import-Module WebAdministration -ErrorAction SilentlyContinue;
      Get-Website | Select-Object -ExpandProperty logFile |
      Select-Object -ExpandProperty directory | Sort-Object -Unique
    `);
    if (!raw) return ['C:\\inetpub\\logs\\LogFiles'];
    return raw.split('\n').map(s => s.trim().replace('%SystemDrive%', process.env.SystemDrive || 'C:')).filter(Boolean);
  } catch {
    return ['C:\\inetpub\\logs\\LogFiles'];
  }
}

// ── Parse IIS W3C log files ───────────────────────────────
function parseIISLogs(maxLines = 50000) {
  const ipStats = {}; // ip -> { hits, bytes, lastSeen, methods }
  const logDirs = getIISLogPaths();

  for (const baseDir of logDirs) {
    if (!fs.existsSync(baseDir)) continue;
    // Walk sub-directories like W3SVC1, W3SVC2 ...
    let entries;
    try { entries = fs.readdirSync(baseDir, { withFileTypes: true }); } catch { continue; }

    for (const entry of entries) {
      if (!entry.isDirectory()) continue;
      const siteDir = path.join(baseDir, entry.name);
      let files;
      try { files = fs.readdirSync(siteDir).filter(f => f.endsWith('.log')).sort().reverse(); } catch { continue; }

      let linesRead = 0;
      for (const file of files.slice(0, 7)) { // at most last 7 log files per site
        const fpath = path.join(siteDir, file);
        let content;
        try { content = fs.readFileSync(fpath, 'utf8'); } catch { continue; }

        const lines = content.split('\n');
        let fields = [];

        for (const line of lines) {
          if (linesRead >= maxLines) break;
          if (line.startsWith('#Fields:')) {
            fields = line.replace('#Fields:', '').trim().split(' ');
            continue;
          }
          if (line.startsWith('#') || !line.trim()) continue;
          linesRead++;

          const parts = line.trim().split(' ');
          if (!fields.length || parts.length < fields.length) continue;

          const get = (name) => {
            const i = fields.indexOf(name);
            return i >= 0 ? parts[i] : '-';
          };

          const ip     = get('c-ip');
          const method = get('cs-method');
          const bytes  = parseInt(get('sc-bytes')) || 0;
          const date   = get('date');
          const time   = get('time');

          if (!ip || ip === '-' || ip === '::1' || ip === '127.0.0.1') continue;

          if (!ipStats[ip]) ipStats[ip] = { hits: 0, bytes: 0, lastSeen: '', methods: {} };
          ipStats[ip].hits++;
          ipStats[ip].bytes += bytes;
          if (date && time) ipStats[ip].lastSeen = date + ' ' + time;
          if (method && method !== '-') ipStats[ip].methods[method] = (ipStats[ip].methods[method] || 0) + 1;
        }
        if (linesRead >= maxLines) break;
      }
    }
  }

  return Object.entries(ipStats)
    .map(([ip, s]) => ({
      ip,
      hits: s.hits,
      bytes: s.bytes,
      lastSeen: s.lastSeen,
      topMethod: Object.entries(s.methods).sort((a, b) => b[1] - a[1])[0]?.[0] || '-'
    }))
    .sort((a, b) => b.hits - a.hits)
    .slice(0, 200);
}

// ── Get blocked IPs from Windows Firewall ─────────────────
function getBlockedIPs() {
  try {
    const raw = ps(`
      Get-NetFirewallRule -DisplayName 'ServerPanel-Block-*' -ErrorAction SilentlyContinue |
      ForEach-Object {
        $addr = ($_ | Get-NetFirewallAddressFilter).RemoteAddress
        [PSCustomObject]@{ name=$_.DisplayName; ip=$addr; enabled=$_.Enabled }
      } | ConvertTo-Json
    `);
    if (!raw) return [];
    const list = JSON.parse(raw);
    return (Array.isArray(list) ? list : [list]).map(r => ({
      name: r.name, ip: Array.isArray(r.ip) ? r.ip.join(', ') : String(r.ip), enabled: r.enabled
    }));
  } catch { return []; }
}

// ── GET /api/traffic — parse logs + geo-lookup top IPs ────
router.get('/', authMw, async (req, res) => {
  try {
    const rows      = parseIISLogs();
    const blocked   = getBlockedIPs();
    const blockedIPs = new Set(blocked.flatMap(b => b.ip.split(',').map(s => s.trim())));

    // Geo-lookup top 50 IPs (rate-limit safe for ip-api.com free tier)
    const top50 = rows.slice(0, 50);
    const geos  = await Promise.all(top50.map(r => geoLookup(r.ip)));

    const result = rows.map((r, i) => ({
      ...r,
      ...(i < 50 ? geos[i] : { country: '', cc: '', city: '' }),
      blocked: blockedIPs.has(r.ip)
    }));

    res.json({ traffic: result, blocked });
  } catch (e) { res.status(500).json({ error: e.message }); }
});

// ── GET /api/traffic/blocked — list all blocked IPs ───────
router.get('/blocked', authMw, (req, res) => {
  try { res.json(getBlockedIPs()); }
  catch (e) { res.status(500).json({ error: e.message }); }
});

// ── POST /api/traffic/block — add firewall block rule ─────
router.post('/block', authMw, (req, res) => {
  const { ip, reason } = req.body;
  if (!ip) return res.status(400).json({ error: 'IP address required' });
  // Basic IP validation (v4 and v6)
  if (!/^[\d.:/a-fA-F]+$/.test(ip)) return res.status(400).json({ error: 'Invalid IP address' });
  try {
    const ruleName = `ServerPanel-Block-${ip.replace(/[:/]/g, '-')}`;
    ps(`New-NetFirewallRule -DisplayName '${ruleName}' -Direction Inbound -Action Block -RemoteAddress '${ip}' -Protocol Any -Enabled True`);
    writeAudit(req, 'traffic.block', `Blocked IP ${ip}${reason ? ' (' + reason + ')' : ''}`);
    res.json({ ok: true });
  } catch (e) { res.status(500).json({ error: e.message }); }
});

// ── DELETE /api/traffic/block — remove firewall block rule ─
router.delete('/block', authMw, (req, res) => {
  const { ip } = req.body;
  if (!ip) return res.status(400).json({ error: 'IP address required' });
  try {
    const ruleName = `ServerPanel-Block-${ip.replace(/[:/]/g, '-')}`;
    ps(`Remove-NetFirewallRule -DisplayName '${ruleName}' -ErrorAction SilentlyContinue`);
    writeAudit(req, 'traffic.unblock', `Unblocked IP ${ip}`);
    res.json({ ok: true });
  } catch (e) { res.status(500).json({ error: e.message }); }
});

module.exports = router;