/**
 * services/geo.js
 * Offline IP-to-location lookup. Wraps `geoip-lite` so the rest of
 * the app doesn't depend on the npm package directly.
 *
 * Why offline: this app handles sensitive user data (NCII takedowns,
 * survivor identities). Sending every login IP to a third-party geo
 * service would be a privacy regression. The MaxMind GeoLite2 DB
 * that ships with `geoip-lite` is queried in-process, no network
 * call, no leak.
 *
 * Result shape (all fields optional — null on lookup miss):
 *   { country, country_code, region, city, timezone, ll: [lat, lng] }
 *
 * On miss (loopback, IPv6 link-local, private ranges, malformed
 * input) we return null and the caller decides how to display
 * (typically "Unknown location").
 */
'use strict';

const geoip = require('geoip-lite');

// Caches for a short window — same IP might be looked up dozens of
// times in a single /auth/sessions call (one per session row).
const cache = new Map();
const CACHE_TTL_MS = 60 * 60 * 1000; // 1h

function lookup(ip) {
  if (!ip || typeof ip !== 'string') return null;
  // Normalize — strip zone identifier (fe80::1%eth0) and any port
  const clean = ip.split('%')[0].trim();
  if (!clean) return null;
  const now = Date.now();
  const hit = cache.get(clean);
  if (hit && hit.expires > now) return hit.value;
  let result = null;
  try {
    const g = geoip.lookup(clean);
    if (g) {
      result = {
        country:     g.country     || null,  // 'US', 'LK', 'DE' …
        country_name: countryName(g.country),
        region:      g.region      || null,  // ISO region code, often empty in lite DB
        city:        g.city        || null,  // often empty in lite DB
        timezone:    g.timezone    || null,
        ll:          Array.isArray(g.ll) ? g.ll : null,
      };
    }
  } catch (_) { /* malformed IP */ }
  cache.set(clean, { value: result, expires: now + CACHE_TTL_MS });
  return result;
}

// ISO 3166-1 alpha-2 -> English name. The geo DB gives us the code
// but the UI needs a human name. Tiny static map covers the ~80
// countries most likely to appear in the user base; falls back to
// the code itself for anything missing.
const NAMES = {
  US:'United States', CA:'Canada', GB:'United Kingdom', IE:'Ireland',
  DE:'Germany', FR:'France', ES:'Spain', IT:'Italy', NL:'Netherlands',
  BE:'Belgium', CH:'Switzerland', AT:'Austria', SE:'Sweden', NO:'Norway',
  DK:'Denmark', FI:'Finland', IS:'Iceland', PT:'Portugal', PL:'Poland',
  CZ:'Czechia', SK:'Slovakia', HU:'Hungary', RO:'Romania', BG:'Bulgaria',
  GR:'Greece', TR:'Türkiye', RU:'Russia', UA:'Ukraine',
  AU:'Australia', NZ:'New Zealand', JP:'Japan', KR:'South Korea',
  CN:'China', HK:'Hong Kong', TW:'Taiwan', SG:'Singapore',
  IN:'India', PK:'Pakistan', BD:'Bangladesh', LK:'Sri Lanka',
  NP:'Nepal', MY:'Malaysia', ID:'Indonesia', TH:'Thailand',
  VN:'Vietnam', PH:'Philippines',
  AE:'United Arab Emirates', SA:'Saudi Arabia', IL:'Israel', TR:'Türkiye',
  EG:'Egypt', ZA:'South Africa', NG:'Nigeria', KE:'Kenya', MA:'Morocco',
  BR:'Brazil', MX:'Mexico', AR:'Argentina', CL:'Chile', CO:'Colombia',
  PE:'Peru', VE:'Venezuela',
  RU:'Russia', CN:'China',
};
function countryName(code) {
  if (!code) return null;
  return NAMES[code] || code;
}

// Build a short human label from the lookup result, e.g.
//   {country: 'LK', city: 'Colombo'}            -> 'Colombo, Sri Lanka'
//   {country: 'US', region: 'CA', city: 'SF'}   -> 'San Francisco, US'
//   {country: 'LK'}                              -> 'Sri Lanka'
//   null                                         -> null
function label(g) {
  if (!g) return null;
  const cityOrRegion = g.city || (g.region ? regionName(g.country, g.region) : null);
  if (cityOrRegion && g.country_name) {
    return cityOrRegion + ', ' + g.country_name;
  }
  return g.country_name || null;
}

// Just the country flag emoji for inline display. Returns '' if unknown.
function flag(code) {
  if (!code || code.length !== 2) return '';
  // Offset: A=0x41, regional indicator for A = 0x1F1E6.
  const A = 0x41;
  return String.fromCodePoint(
    0x1F1E6 + (code.charCodeAt(0) - A),
    0x1F1E6 + (code.charCodeAt(1) - A),
  );
}

// Map a few common region codes to a friendly name. Full mapping
// would be huge; we just cover the ones most likely to appear in
// the user base.
function regionName(country, region) {
  if (!region) return null;
  const map = {
    'US-CA': 'California', 'US-NY': 'New York', 'US-TX': 'Texas',
    'US-IL': 'Illinois', 'US-WA': 'Washington', 'US-FL': 'Florida',
    'US-MA': 'Massachusetts', 'US-CO': 'Colorado',
    'CA-ON': 'Ontario', 'CA-QC': 'Quebec', 'CA-BC': 'British Columbia',
    'GB-ENG': 'England', 'GB-SCT': 'Scotland', 'GB-WLS': 'Wales',
    'AU-NSW': 'New South Wales', 'AU-VIC': 'Victoria',
    'IN-MH': 'Maharashtra', 'IN-KA': 'Karnataka', 'IN-DL': 'Delhi',
    'LK-1': 'Western Province', 'LK-2': 'Central Province', 'LK-3': 'Southern Province',
    'LK-4': 'Northern Province', 'LK-5': 'Eastern Province', 'LK-6': 'North Western',
    'LK-7': 'North Central', 'LK-8': 'Uva', 'LK-9': 'Sabaragamuwa',
  };
  return map[country + '-' + region] || region;
}

module.exports = { lookup, label, flag, countryName, regionName };
