/**
 * IP Filter Middleware — Phase 6
 *
 * IP-based access control with blacklist, whitelist, priority modes, and temporary bans.
 */

import { isIP } from "node:net";

// In-memory IP lists
let _config = {
  enabled: false,
  mode: "blacklist", // "blacklist" | "whitelist" | "whitelist-priority"
  blacklist: new Set(),
  whitelist: new Set(),
  tempBans: new Map(), // IP → { until: timestamp, reason: string }
};

/**
 * Configure the IP filter
 */
export function configureIPFilter(config) {
  if (config.enabled !== undefined) _config.enabled = config.enabled;
  if (config.mode) _config.mode = config.mode;
  if (config.blacklist) _config.blacklist = new Set(config.blacklist);
  if (config.whitelist) _config.whitelist = new Set(config.whitelist);
}

/**
 * Get current IP filter config (for API)
 */
export function getIPFilterConfig() {
  return {
    enabled: _config.enabled,
    mode: _config.mode,
    blacklist: Array.from(_config.blacklist),
    whitelist: Array.from(_config.whitelist),
    tempBans: Array.from(_config.tempBans.entries()).map(([ip, info]) => ({
      ip,
      until: new Date(info.until).toISOString(),
      reason: info.reason,
      remainingMs: Math.max(0, info.until - Date.now()),
    })),
  };
}

/**
 * Check if an IP is allowed
 * @param {string} ip
 * @returns {{ allowed: boolean, reason?: string }}
 */
export function checkIP(ip) {
  if (!_config.enabled) return { allowed: true };
  if (!ip) return { allowed: true };

  const normalizedIP = normalizeIP(ip);

  // Check temp bans first (highest priority)
  const ban = _config.tempBans.get(normalizedIP);
  if (ban) {
    if (Date.now() < ban.until) {
      return { allowed: false, reason: `Temporarily banned: ${ban.reason}` };
    }
    _config.tempBans.delete(normalizedIP); // Expired
  }

  switch (_config.mode) {
    case "whitelist":
      // Only whitelisted IPs allowed
      if (!matchesAny(normalizedIP, _config.whitelist)) {
        return { allowed: false, reason: "IP not in whitelist" };
      }
      return { allowed: true };

    case "whitelist-priority":
      // Whitelist overrides blacklist
      if (matchesAny(normalizedIP, _config.whitelist)) {
        return { allowed: true };
      }
      if (matchesAny(normalizedIP, _config.blacklist)) {
        return { allowed: false, reason: "IP blacklisted" };
      }
      return { allowed: true };

    case "blacklist":
    default:
      // Blacklisted IPs blocked
      if (matchesAny(normalizedIP, _config.blacklist)) {
        return { allowed: false, reason: "IP blacklisted" };
      }
      return { allowed: true };
  }
}

/**
 * Temporarily ban an IP
 */
export function tempBanIP(ip, durationMs, reason = "Automated ban") {
  const normalizedIP = normalizeIP(ip);
  _config.tempBans.set(normalizedIP, {
    until: Date.now() + durationMs,
    reason,
  });
}

/**
 * Remove a temporary ban
 */
export function removeTempBan(ip) {
  _config.tempBans.delete(normalizeIP(ip));
}

/**
 * Add IP to blacklist
 */
export function addToBlacklist(ip) {
  _config.blacklist.add(normalizeIP(ip));
}

/**
 * Remove IP from blacklist
 */
export function removeFromBlacklist(ip) {
  _config.blacklist.delete(normalizeIP(ip));
}

/**
 * Add IP to whitelist
 */
export function addToWhitelist(ip) {
  _config.whitelist.add(normalizeIP(ip));
}

/**
 * Remove IP from whitelist
 */
export function removeFromWhitelist(ip) {
  _config.whitelist.delete(normalizeIP(ip));
}

/**
 * Express/Next.js middleware factory
 */
export function createIPFilterMiddleware() {
  return (req, res, next) => {
    const ip = extractClientIP(req);
    const { allowed, reason } = checkIP(ip);
    if (!allowed) {
      const statusCode = 403;
      if (res.status) {
        // Express
        return res.status(statusCode).json({ error: reason || "Access denied" });
      }
      // Raw response
      res.writeHead(statusCode, { "Content-Type": "application/json" });
      return res.end(JSON.stringify({ error: reason || "Access denied" }));
    }
    if (next) next();
  };
}

/**
 * For Next.js App Router — check IP from request object
 */
export function checkRequestIP(request) {
  const ip =
    pickFirstValidIp(request.headers?.get?.("cf-connecting-ip")) ||
    pickFirstValidIp(request.headers?.get?.("x-forwarded-for")) ||
    pickFirstValidIp(request.headers?.get?.("x-real-ip")) ||
    normalizeIP(request.ip || "") ||
    "unknown";
  return checkIP(ip);
}

// ─── Internal Helpers ───────────────────────────────────────────────────────

function normalizeIP(ip) {
  if (!ip) return "";
  // Remove IPv6 prefix from IPv4-mapped addresses
  return ip.replace(/^::ffff:/, "").trim();
}

function pickFirstValidIp(rawValue) {
  if (typeof rawValue !== "string" || rawValue.trim().length === 0) return null;
  const candidates = rawValue.split(",");
  for (const candidate of candidates) {
    const normalized = normalizeIP(candidate);
    if (normalized && isIP(normalized) !== 0) {
      return normalized;
    }
  }
  return null;
}

function matchesAny(ip, ipSet) {
  // Direct match
  if (ipSet.has(ip)) return true;

  // CIDR match
  for (const entry of ipSet) {
    if (entry.includes("/") && matchesCIDR(ip, entry)) return true;
    // Wildcard match (e.g., "192.168.*.*")
    if (entry.includes("*") && matchesWildcard(ip, entry)) return true;
  }
  return false;
}

function matchesCIDR(ip, cidr) {
  try {
    const [range, bits] = cidr.split("/");
    const mask = parseInt(bits, 10);
    if (isNaN(mask) || mask < 0 || mask > 32) return false;
    const ipNum = ipToNum(ip);
    const rangeNum = ipToNum(range);
    if (ipNum === null || rangeNum === null) return false;
    const maskBits = (-1 << (32 - mask)) >>> 0;
    return (ipNum & maskBits) === (rangeNum & maskBits);
  } catch {
    return false;
  }
}

function ipToNum(ip) {
  const parts = ip.split(".");
  if (parts.length !== 4) return null;
  let num = 0;
  for (const p of parts) {
    const n = parseInt(p, 10);
    if (isNaN(n) || n < 0 || n > 255) return null;
    num = (num << 8) | n;
  }
  return num >>> 0;
}

function matchesWildcard(ip, pattern) {
  const ipParts = ip.split(".");
  const patParts = pattern.split(".");
  if (ipParts.length !== 4 || patParts.length !== 4) return false;
  return ipParts.every((part, i) => patParts[i] === "*" || part === patParts[i]);
}

function extractClientIP(req) {
  const headers = req.headers || {};
  return (
    pickFirstValidIp(headers["cf-connecting-ip"]) ||
    pickFirstValidIp(headers["x-forwarded-for"]) ||
    pickFirstValidIp(headers["x-real-ip"]) ||
    pickFirstValidIp(req.socket?.remoteAddress) ||
    pickFirstValidIp(req.ip) ||
    "unknown"
  );
}

/**
 * Reset config (for testing)
 */
export function resetIPFilter() {
  _config = {
    enabled: false,
    mode: "blacklist",
    blacklist: new Set(),
    whitelist: new Set(),
    tempBans: new Map(),
  };
}
