import * as satellite from 'satellite.js';
import type { Satellite, SatellitePosition } from '../types';

// CelesTrak TLE groups to fetch — relevant to Middle East theater
const CELESTRAK_GROUPS: { group: string; category: Satellite['category'] }[] = [
  { group: 'military', category: 'reconnaissance' },
  { group: 'gps-ops', category: 'navigation' },
  { group: 'geo', category: 'communications' },
  { group: 'weather', category: 'weather' },
  { group: 'stations', category: 'other' },
];

// Category override by satellite name keywords
function refineSatCategory(name: string, defaultCat: Satellite['category']): Satellite['category'] {
  const n = name.toUpperCase();
  if (n.includes('SBIRS') || n.includes('NROL') || n.includes('USA') || n.includes('KEYHOLE') || n.includes('LACROSSE')) return 'reconnaissance';
  if (n.includes('WGS') || n.includes('AEHF') || n.includes('MUOS') || n.includes('STARLINK') || n.includes('MILSTAR')) return 'communications';
  if (n.includes('GPS') || n.includes('NAVSTAR') || n.includes('GALILEO') || n.includes('BEIDOU') || n.includes('GLONASS')) return 'navigation';
  if (n.includes('GOES') || n.includes('METOP') || n.includes('NOAA') || n.includes('METEOR') || n.includes('DMSP')) return 'weather';
  if (n.includes('ISS')) return 'other';
  return defaultCat;
}

// Parse 3-line TLE format (name + line1 + line2)
function parseTLE(text: string, defaultCategory: Satellite['category']): Satellite[] {
  const lines = text.trim().split('\n').map(l => l.trim()).filter(l => l.length > 0);
  const sats: Satellite[] = [];

  for (let i = 0; i < lines.length - 2; i++) {
    // TLE line 1 starts with "1 ", line 2 starts with "2 "
    if (lines[i + 1].startsWith('1 ') && lines[i + 2].startsWith('2 ')) {
      const name = lines[i];
      const tle1 = lines[i + 1];
      const tle2 = lines[i + 2];

      // Extract NORAD catalog number from line 1 (columns 3-7)
      const noradId = parseInt(tle1.substring(2, 7).trim(), 10);
      if (isNaN(noradId)) continue;

      sats.push({
        noradId,
        name,
        tle1,
        tle2,
        category: refineSatCategory(name, defaultCategory),
      });

      i += 2; // skip the 2 TLE lines
    }
  }

  return sats;
}

// Middle East bounding box for filtering LEO satellites
// Only keep satellites whose ground track passes near the region (lat 15-45, lon 25-65)
function isNearMiddleEast(sat: Satellite): boolean {
  try {
    const satrec = satellite.twoline2satrec(sat.tle1, sat.tle2);
    const now = new Date();
    // Check current position and ±45min positions
    for (const offsetMin of [0, -45, 45, -90, 90]) {
      const t = new Date(now.getTime() + offsetMin * 60_000);
      const pv = satellite.propagate(satrec, t);
      if (!pv || typeof pv.position === 'boolean' || !pv.position) continue;
      const gmst = satellite.gstime(t);
      const geo = satellite.eciToGeodetic(pv.position, gmst);
      const lat = satellite.degreesLat(geo.latitude);
      const lng = satellite.degreesLong(geo.longitude);
      // Generous bounding: lat -5 to 55, lon 15 to 75
      if (lat >= -5 && lat <= 55 && lng >= 15 && lng <= 75) return true;
    }
    return false;
  } catch {
    return false;
  }
}

// Satellite cache — avoid re-fetching within 10 minutes
let satCache: { sats: Satellite[]; ts: number } | null = null;
const SAT_CACHE_TTL = 10 * 60_000;

async function fetchSatellitesFromBackend(region: 'iran' | 'korea' = 'iran'): Promise<Satellite[]> {
  try {
    const res = await fetch(`/api/kcg/satellites?region=${region}`, { credentials: 'include' });
    if (!res.ok) return [];
    const data = await res.json();
    return (data.satellites ?? []) as Satellite[];
  } catch {
    return [];
  }
}

async function fetchSatelliteTLEFromCelesTrak(): Promise<Satellite[]> {
  const allSats: Satellite[] = [];
  const seenIds = new Set<number>();

  for (const { group, category } of CELESTRAK_GROUPS) {
    try {
      const url = `/api/celestrak/NORAD/elements/gp.php?GROUP=${group}&FORMAT=tle`;
      const res = await fetch(url);
      if (!res.ok) {
        console.warn(`CelesTrak ${group}: ${res.status}`);
        continue;
      }
      const text = await res.text();
      const parsed = parseTLE(text, category);
      for (const sat of parsed) {
        if (!seenIds.has(sat.noradId)) {
          seenIds.add(sat.noradId);
          allSats.push(sat);
        }
      }
    } catch (err) {
      console.warn(`CelesTrak ${group} fetch failed:`, err);
    }
  }

  return allSats;
}

function filterSatellitesByRegion(allSats: Satellite[], isNearFn: (sat: Satellite) => boolean): Satellite[] {
  const filtered: Satellite[] = [];
  for (const sat of allSats) {
    try {
      const satrec = satellite.twoline2satrec(sat.tle1, sat.tle2);
      const pv = satellite.propagate(satrec, new Date());
      if (!pv || typeof pv.position === 'boolean' || !pv.position) continue;
      const geo = satellite.eciToGeodetic(pv.position, satellite.gstime(new Date()));
      const altKm = geo.height;

      if (altKm > 5000) {
        filtered.push(sat);
      } else {
        if (isNearFn(sat)) {
          filtered.push(sat);
        }
      }
    } catch {
      // skip bad TLE
    }
  }
  return filtered.slice(0, 100);
}

export async function fetchSatelliteTLE(): Promise<Satellite[]> {
  if (satCache && Date.now() - satCache.ts < SAT_CACHE_TTL) {
    return satCache.sats;
  }

  // 백엔드 API 우선
  let allSats = await fetchSatellitesFromBackend('iran');

  // 백엔드 실패 시 CelesTrak 직접 호출 fallback
  if (allSats.length === 0) {
    allSats = await fetchSatelliteTLEFromCelesTrak();
  }

  if (allSats.length === 0) {
    console.warn('CelesTrak: no data fetched, using fallback');
    return FALLBACK_SATELLITES;
  }

  const capped = filterSatellitesByRegion(allSats, isNearMiddleEast);
  satCache = { sats: capped, ts: Date.now() };
  console.log(`Satellites: loaded ${capped.length} (from ${allSats.length} total)`);
  return capped;
}

// ═══ Korea region satellite fetch ═══
function isNearKorea(sat: Satellite): boolean {
  try {
    const satrec = satellite.twoline2satrec(sat.tle1, sat.tle2);
    const now = new Date();
    for (const offsetMin of [0, -45, 45, -90, 90]) {
      const t = new Date(now.getTime() + offsetMin * 60_000);
      const pv = satellite.propagate(satrec, t);
      if (!pv || typeof pv.position === 'boolean' || !pv.position) continue;
      const gmst = satellite.gstime(t);
      const geo = satellite.eciToGeodetic(pv.position, gmst);
      const lat = satellite.degreesLat(geo.latitude);
      const lng = satellite.degreesLong(geo.longitude);
      if (lat >= 10 && lat <= 50 && lng >= 110 && lng <= 150) return true;
    }
    return false;
  } catch {
    return false;
  }
}

let satCacheKorea: { sats: Satellite[]; ts: number } | null = null;

export async function fetchSatelliteTLEKorea(): Promise<Satellite[]> {
  if (satCacheKorea && Date.now() - satCacheKorea.ts < SAT_CACHE_TTL) {
    return satCacheKorea.sats;
  }

  // 백엔드 API 우선
  let allSats = await fetchSatellitesFromBackend('korea');

  // 백엔드 실패 시 CelesTrak 직접 호출 fallback
  if (allSats.length === 0) {
    allSats = await fetchSatelliteTLEFromCelesTrak();
  }

  if (allSats.length === 0) return FALLBACK_SATELLITES;

  const capped = filterSatellitesByRegion(allSats, isNearKorea);
  satCacheKorea = { sats: capped, ts: Date.now() };
  console.log(`Satellites Korea: loaded ${capped.length} (from ${allSats.length} total)`);
  return capped;
}

// Fallback satellites if CelesTrak is unreachable
const FALLBACK_SATELLITES: Satellite[] = [
  { noradId: 25544, name: 'ISS (ZARYA)', category: 'other',
    tle1: '1 25544U 98067A   26060.50000000  .00016717  00000-0  10270-3 0  9999',
    tle2: '2 25544  51.6400 210.0000 0005000 350.0000  10.0000 15.49000000 10000' },
  { noradId: 37481, name: 'SBIRS GEO-1', category: 'reconnaissance',
    tle1: '1 37481U 11019A   26060.50000000  .00000010  00000-0  00000-0 0  9999',
    tle2: '2 37481   3.5000  60.0000 0003000  90.0000 270.0000  1.00270000 10000' },
  { noradId: 44478, name: 'WGS-10', category: 'communications',
    tle1: '1 44478U 19060A   26060.50000000  .00000010  00000-0  00000-0 0  9999',
    tle2: '2 44478   0.1000  60.0000 0002000 270.0000  90.0000  1.00270000 10000' },
  { noradId: 55268, name: 'GPS III-06', category: 'navigation',
    tle1: '1 55268U 23008A   26060.50000000  .00000010  00000-0  00000-0 0  9999',
    tle2: '2 55268  55.0000  60.0000 0050000 100.0000 260.0000  2.00600000 10000' },
  { noradId: 43689, name: 'MetOp-C', category: 'weather',
    tle1: '1 43689U 18096A   26060.50000000  .00000400  00000-0  20000-3 0  9999',
    tle2: '2 43689  98.7000 110.0000 0002000  90.0000 270.0000 14.21000000 10000' },
];

// Cache satrec objects (expensive to create)
const satrecCache = new Map<number, ReturnType<typeof satellite.twoline2satrec>>();

function getSatrec(sat: Satellite) {
  let rec = satrecCache.get(sat.noradId);
  if (!rec) {
    rec = satellite.twoline2satrec(sat.tle1, sat.tle2);
    satrecCache.set(sat.noradId, rec);
  }
  return rec;
}

// Cache ground tracks — only recompute every 60s
const trackCache = new Map<number, { time: number; track: [number, number][] }>();
const TRACK_CACHE_MS = 60_000;

export function propagateSatellite(
  sat: Satellite,
  time: Date,
  trackMinutes: number = 90,
): SatellitePosition | null {
  try {
    const satrec = getSatrec(sat);
    const posVel = satellite.propagate(satrec, time);
    if (!posVel || typeof posVel.position === 'boolean' || !posVel.position) return null;

    const pos = posVel.position;
    const gmst = satellite.gstime(time);
    const geo = satellite.eciToGeodetic(pos, gmst);

    const lat = satellite.degreesLat(geo.latitude);
    const lng = satellite.degreesLong(geo.longitude);
    const altitude = geo.height;

    // Ground track — use cache if fresh enough
    const cached = trackCache.get(sat.noradId);
    let groundTrack: [number, number][];

    if (cached && Math.abs(cached.time - time.getTime()) < TRACK_CACHE_MS) {
      groundTrack = cached.track;
    } else {
      groundTrack = [];
      const steps = 20; // reduced from 60
      const stepMs = (trackMinutes * 60 * 1000) / steps;

      for (let i = -steps / 2; i <= steps / 2; i++) {
        const t = new Date(time.getTime() + i * stepMs);
        const pv = satellite.propagate(satrec, t);
        if (!pv || typeof pv.position === 'boolean' || !pv.position) continue;
        const g = satellite.gstime(t);
        const gd = satellite.eciToGeodetic(pv.position, g);
        groundTrack.push([
          satellite.degreesLat(gd.latitude),
          satellite.degreesLong(gd.longitude),
        ]);
      }
      trackCache.set(sat.noradId, { time: time.getTime(), track: groundTrack });
    }

    return {
      noradId: sat.noradId,
      name: sat.name,
      lat,
      lng,
      altitude,
      category: sat.category,
      groundTrack,
    };
  } catch {
    return null;
  }
}

export function propagateAll(
  satellites: Satellite[],
  time: Date,
): SatellitePosition[] {
  return satellites
    .map(s => propagateSatellite(s, time))
    .filter((p): p is SatellitePosition => p !== null);
}
