/**
 * Approximate location from IP using a free, no-key service (ip-api.com).
 * Results are cached in Redis so a reconnect storm can't burn the rate limit,
 * and every failure degrades gracefully to an empty object — enrichment must
 * never block a customer from starting a chat.
 */
import { env } from '../config/env.js';
import { redis, KEYS } from '../redis/client.js';
import { logger } from '../utils/logger.js';
import { isPrivateIp } from './ip.js';

export interface GeoInfo {
  city?: string;
  region?: string;
  country?: string;
  countryCode?: string;
  postal?: string;
  lat?: number;
  lon?: number;
  timezone?: string;
  isp?: string;
  /** True when we could not resolve (private IP, timeout, rate limit). */
  unavailable?: boolean;
  reason?: string;
}

export async function lookupGeo(ip: string): Promise<GeoInfo> {
  if (!ip || isPrivateIp(ip)) {
    return { unavailable: true, reason: 'private or local IP' };
  }

  const cacheKey = KEYS.geo(ip);
  try {
    const cached = await redis.get(cacheKey);
    if (cached) return JSON.parse(cached) as GeoInfo;
  } catch (err) {
    logger.warn('geo cache read failed', { err: (err as Error).message });
  }

  let info: GeoInfo;
  try {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 2500);
    const fields = 'status,message,country,countryCode,regionName,city,zip,lat,lon,timezone,isp';
    const res = await fetch(`${env.GEOIP_ENDPOINT}/${encodeURIComponent(ip)}?fields=${fields}`, {
      signal: controller.signal,
    });
    clearTimeout(timer);

    if (!res.ok) throw new Error(`geo service returned ${res.status}`);
    const data = (await res.json()) as Record<string, any>;
    if (data.status !== 'success') throw new Error(data.message || 'lookup failed');

    info = {
      city: data.city || undefined,
      region: data.regionName || undefined,
      country: data.country || undefined,
      countryCode: data.countryCode || undefined,
      postal: data.zip || undefined,
      lat: typeof data.lat === 'number' ? data.lat : undefined,
      lon: typeof data.lon === 'number' ? data.lon : undefined,
      timezone: data.timezone || undefined,
      isp: data.isp || undefined,
    };
  } catch (err) {
    logger.warn('geo lookup failed', { ip, err: (err as Error).message });
    info = { unavailable: true, reason: (err as Error).message };
  }

  // Cache negatives briefly too, so an outage doesn't hammer the service.
  const ttl = info.unavailable ? 300 : env.GEOIP_CACHE_TTL;
  redis.set(cacheKey, JSON.stringify(info), 'EX', ttl).catch(() => undefined);
  return info;
}

/** "Bengaluru, Karnataka, India" — what the agent panel shows. */
export function formatGeo(geo: GeoInfo): string {
  const parts = [geo.city, geo.region, geo.country].filter(Boolean);
  return parts.length ? parts.join(', ') : 'Unknown location';
}
