/**
 * Which satellites this box serves — which is either all of them or none.
 *
 * The shortest service in the server, and the shape is worth noticing because of
 * what it *lacks*. There is no region parameter. Flights and weather are both
 * keyed per region because an aeroplane at 3,000 m over Oakland is not visible
 * from Long Beach and a rain shower is not either; a satellite at 550 km is
 * above the horizon for a circle roughly two thousand kilometres across, and the
 * two cities this repo ships are six hundred apart. One catalogue covers both,
 * and it would cover a continent.
 *
 * So the cache has exactly one key, the client is handed the whole catalogue, and
 * **the client decides what is above its own horizon** — which it must do anyway,
 * continuously, because the answer changes every few seconds as things move. A
 * server that filtered by region would be doing a worse version of a calculation
 * the client cannot avoid doing, and would break the property that makes this
 * cacheable at all: one body, every viewer, no variation.
 *
 * ### Six hours, and why that is not laziness
 *
 * `flights/index.ts` clamps its live TTL between 5 and 15 seconds. This one
 * defaults to six hours and is floored at one, and both numbers are chosen the
 * same way: how often does the upstream change, and what does asking cost?
 *
 * CelesTrak regenerates element sets a handful of times a day. A TLE stays
 * accurate to within a few kilometres for days either side of its epoch — that
 * is the entire design of the format. Fetching it every minute would produce
 * identical bytes, at the expense of one person's bandwidth, and would get this
 * box rate-limited into serving an empty sky. The floor exists because
 * `TERA_SATELLITES_TTL=0` reads like "as fresh as possible" and means "one
 * multi-megabyte fetch of eight groups per inbound request", which is the same
 * trap `TERA_FLIGHTS_TTL=0` used to be.
 */

import { fetchCatalogue, type SatelliteSnapshot } from "./celestrak.ts";
import { createUpstream } from "../upstream.ts";
import type { Config } from "../config.ts";
import type { SatellitesBody } from "../../../src/server/wire.ts";

export interface SatellitesService {
  current(): Promise<SatellitesBody>;
}

export interface SatellitesLog {
  warn(msg: string): void;
}

/** See the note above. One catalogue, one key. */
const CATALOGUE_KEY = "catalogue";

/**
 * An hour is already twelve times more often than the data changes. Below that,
 * a caller is spending somebody else's bandwidth to receive the same bytes.
 */
const MIN_TTL_SECONDS = 3600;

/**
 * The body a box with no satellite source serves: real and empty, never invented.
 * See the note on `SatellitesBody` in `wire.ts` for why there is no synthetic
 * constellation to fall back to the way there is a synthetic sky.
 *
 * `fetchedAt` is the Unix epoch rather than "now", because "now" would be a
 * claim that this catalogue was fetched a moment ago. It was never fetched.
 */
function emptyBody(ttlSeconds: number): SatellitesBody {
  return {
    source: "none",
    fetchedAt: new Date(0).toISOString(),
    satellites: [],
    ttlSeconds,
  };
}

export function createSatellitesService(config: Config, log: SatellitesLog): SatellitesService {
  const { source, contact, ttlSeconds } = config.satellites;
  const ttl = Math.max(MIN_TTL_SECONDS, ttlSeconds);

  const upstream = createUpstream<SatelliteSnapshot>({
    label: "satellites:celestrak",
    ttlSeconds: ttl,
    log,
  });

  return {
    async current(): Promise<SatellitesBody> {
      if (source === "none") return emptyBody(ttl);

      const snapshot = await upstream.get(CATALOGUE_KEY, () => fetchCatalogue(contact, log));
      if (snapshot === null) return emptyBody(ttl);

      return {
        source,
        fetchedAt: new Date(snapshot.fetchedAt).toISOString(),
        satellites: snapshot.satellites,
        ttlSeconds: ttl,
        attribution: ["Orbital element sets from CelesTrak"],
      };
    },
  };
}
