/**
 * The two sky feeds, and the one property they exist to guarantee.
 *
 * Most of this file is about **which keys reach the wire**, which is unusual for
 * a feed test and is the whole point of this one. `radar.sqlite`'s `echo_cells`
 * table carries a column called `distance_km`, and it is measured from a private
 * house — the same leak class `server/src/test/fires.test.ts` was written
 * against, in a store nobody would think to check because it is full of weather.
 *
 * The structural defence is that this box has no database: cloud-1 serves a
 * projection with a hand-written column list, so there is nothing here to be
 * careless with. The tests below are the second line, and they take the same
 * shape the fire test does — the adopted key set **equals** an allowlist, so a
 * field added upstream fails here rather than arriving in a browser. An
 * `assert.ok(!keys.has("distance_km"))` would have passed for every column
 * nobody thought to name, which is precisely the class of column that gets added
 * later.
 *
 * The rest is the ordinary feed contract, plus one thing neither flights nor
 * fires has: **an empty body is the normal body**. California is under rain a
 * mean 0.596% of the time and BirdCast measures only after dark, so a quiet
 * answer here is a finding and not a fault, and `quiet` is not optional on the
 * birds body for exactly that reason.
 */

import assert from "node:assert/strict";
import { after, beforeEach, describe, it } from "node:test";
import { buildApp } from "../app.ts";
import { loadConfig } from "../config.ts";
import type { BirdsBody, RadarBody } from "../../../src/server/wire.ts";

/**
 * Every key `RadarBody.field` may carry. The lattice, and the two panel
 * counters, and nothing else.
 *
 * Adding to this list is a **privacy decision**, not a refactor. The question to
 * answer first is "can this be inverted, joined or differenced into the distance
 * from a rain cell to somebody's front door" — because `echo_cells.distance_km`
 * is literally that, already computed, sitting one careless SELECT away on the
 * machine at the other end of this fetch.
 */
const FIELD_KEYS = [
  "cellLat", "cellLng", "cols", "dbz", "minLat", "minLng",
  "observedAt", "rows", "stations", "stationsDown", "wetFraction",
] as const;

/** The same, for a radar station. */
const STATION_KEYS = ["id", "lat", "lon", "operability", "type"] as const;

/** And for a county. BirdCast is public, but the rule is the rule. */
const COUNTY_KEYS = [
  "aloft", "altitude", "areaKm2", "direction", "id", "lat", "lon", "name", "speed",
] as const;

/** Home-relative names, in every spelling the two sides use. None may appear. */
const FORBIDDEN = ["distance_km", "distanceKm", "bearing_deg", "bearingDeg", "threat", "home", "px"];

const realFetch = globalThis.fetch;
let calls: string[] = [];
let cellsBody: unknown = null;
let stationsBody: unknown = null;
let countiesBody: unknown = null;
let nightBody: unknown = null;

/**
 * A projection response shaped as cloud-1 would serve one — including the
 * columns this build must never adopt, and one it simply does not read.
 */
function upstreamCells(): unknown {
  return {
    observedAt: "2026-08-23T03:55Z",
    wetFraction: 0.01941,
    readAt: "2026-08-23T04:00:21.000Z",
    attribution: ["NEXRAD base reflectivity via NOAA/NWS and Iowa State University IEM"],
    cells: [
      // Riverside, in the monsoon. Real.
      { lat: 33.875, lon: -117.375, dbz: 61.5, px: 302, distance_km: 18.4, provenance: "us-nws/iem-n0q" },
      { lat: 33.625, lon: -117.375, dbz: 44.0, px: 91, distance_km: 26.1 },
      { lat: 34.125, lon: -117.625, dbz: 33.0, px: 40, distance_km: 31.7 },
    ],
  };
}

function upstreamStations(): unknown {
  return {
    attribution: ["Radar status from NOAA/NWS"],
    stations: [
      { id: "KSOX", lat: 33.81773, lon: -117.63599, type: "WSR-88D", operability: "RDA - Maintenance Action Required", alarms: "Communication", power_w: 620 },
      { id: "KVTX", lat: 34.41166, lon: -119.1786, type: "WSR-88D", operability: "RDA - Maintenance Action Mandatory" },
      { id: "KHNX", lat: 36.31416, lon: -119.63213, type: "WSR-88D", operability: "RDA - On-line" },
    ],
  };
}

function upstreamCounties(): unknown {
  return {
    observedAt: "2026-08-23T03:20:00Z",
    attribution: ["Nocturnal migration from BirdCast (Cornell Lab of Ornithology / Colorado State)"],
    counties: [
      { id: "US-CA-019", name: "Fresno County", lat: 36.761006, lon: -119.655019, areaKm2: 15569, aloft: 82549, altitude: 333, direction: 140.0, speed: 6.9, mtr: 372.1, vid: 5.3 },
      { id: "US-CA-107", name: "Tulare County", lat: 36.228834, lon: -118.781055, areaKm2: 12531, aloft: 52108, altitude: 446, direction: 130.1, speed: 7.5 },
      // The row that is a state pretending to be a county: no coordinate, no
      // area, and 9.6 times the largest county's birds.
      { id: "US-CA", name: "California", lat: null, lon: null, areaKm2: null, aloft: 793141, altitude: 605, direction: 140.6, speed: 4.8 },
    ],
  };
}

function upstreamNight(): unknown {
  return {
    attribution: ["Nocturnal migration from BirdCast (Cornell Lab of Ornithology / Colorado State)"],
    night: {
      crossed: 393290.37,
      peakAloft: 1501193.14,
      peakAt: "2026-08-22T06:20:00Z",
      meanAltitude: 726.0,
      heading: "south-east",
      granules: 65,
    },
  };
}

globalThis.fetch = (async (input: unknown) => {
  const url = String(input);
  calls.push(url);
  const body = url.includes("/cells")
    ? cellsBody
    : url.includes("/stations")
      ? stationsBody
      : url.includes("/counties")
        ? countiesBody
        : nightBody;
  if (body === null) return new Response("nope", { status: 503 });
  return new Response(JSON.stringify(body), {
    status: 200,
    headers: { "content-type": "application/json" },
  });
}) as unknown as typeof globalThis.fetch;

after(() => {
  globalThis.fetch = realFetch;
});

beforeEach(() => {
  calls = [];
  cellsBody = upstreamCells();
  stationsBody = upstreamStations();
  countiesBody = upstreamCounties();
  nightBody = upstreamNight();
});

function appWith(env: Record<string, string>) {
  const config = loadConfig(env);
  config.logLevel = "silent";
  return buildApp(config);
}

const CLOUD1 = {
  TERA_RADAR_SOURCE: "cloud1",
  TERA_RADAR_URL: "http://127.0.0.1:9/api/radar",
  TERA_BIRDS_SOURCE: "cloud1",
  TERA_BIRDS_URL: "http://127.0.0.1:9/api/birds",
};

async function radar(app: ReturnType<typeof buildApp>) {
  const res = await app.inject({ method: "GET", url: "/api/v1/radar" });
  assert.equal(res.statusCode, 200);
  return { body: res.json() as RadarBody, headers: res.headers };
}

async function birds(app: ReturnType<typeof buildApp>) {
  const res = await app.inject({ method: "GET", url: "/api/v1/birds" });
  assert.equal(res.statusCode, 200);
  return { body: res.json() as BirdsBody, headers: res.headers };
}

/** Every key anywhere in a body, however deep. */
function keysOf(value: unknown, into = new Set<string>()): Set<string> {
  if (Array.isArray(value)) {
    for (const item of value) keysOf(item, into);
  } else if (value !== null && typeof value === "object") {
    for (const [key, inner] of Object.entries(value)) {
      into.add(key);
      keysOf(inner, into);
    }
  }
  return into;
}

// ---- Off by default -------------------------------------------------------

describe("a box with no sky source", () => {
  it("serves a real empty sky rather than an invented shower", async () => {
    const app = appWith({});
    after(() => app.close());

    const { body } = await radar(app);
    assert.equal(body.source, "none");
    assert.equal(body.field, null);
    // The epoch, not "now". "Now" would claim this was fetched a moment ago; it
    // was never fetched, and a board with nothing on it has to be able to say
    // which of those two it is looking at.
    assert.equal(Date.parse(body.fetchedAt), 0);
    assert.equal(calls.length, 0, "and nothing was asked of anybody");
  });

  it("serves an empty migration body that says why it is empty", async () => {
    const app = appWith({});
    after(() => app.close());

    const { body } = await birds(app);
    assert.equal(body.source, "none");
    assert.deepEqual(body.counties, []);
    assert.equal(body.statewide, null);
    // The difference from fires: an empty fire body needs no explanation, and an
    // empty migration body does, because the layer is absent fourteen hours in
    // every twenty-four by construction.
    assert.ok(body.quiet, "quiet is not optional");
    assert.equal(body.quiet.reason, "no-data");
    assert.match(body.quiet.message, /fact about this box/);
    assert.equal(calls.length, 0);
  });

  it("adds no demotion line, because off is a choice and not a misconfiguration", async () => {
    const config = loadConfig({});
    assert.deepEqual(config.degraded, []);
    assert.equal(config.radar.source, "none");
    assert.equal(config.birds.source, "none");
  });

  it("demotes cloud1 with no URL, and says so in one sentence", async () => {
    const config = loadConfig({ TERA_RADAR_SOURCE: "cloud1", TERA_BIRDS_SOURCE: "cloud1" });
    assert.equal(config.radar.source, "none");
    assert.equal(config.birds.source, "none");
    assert.equal(config.degraded.length, 2);
    assert.match(config.degraded.join(" "), /TERA_RADAR_URL/);
    assert.match(config.degraded.join(" "), /TERA_BIRDS_URL/);
  });
});

// ---- The leak -------------------------------------------------------------

describe("what may cross the wire", () => {
  it("adopts exactly the named cell and station keys and nothing else", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());

    const { body } = await radar(app);
    assert.ok(body.field, "the fixture is a real Riverside storm and must promote");
    const keys = keysOf(body);
    for (const forbidden of FORBIDDEN) {
      assert.ok(!keys.has(forbidden), `${forbidden} reached the wire`);
    }
    // The lattice is built on the server, so a cell never crosses as a cell: the
    // field is one row-major array of numbers with a shape beside it. That is a
    // stronger property than filtering a cell's columns — there is no object for
    // a new column to attach to — and this is what asserts it stayed true.
    assert.deepEqual(
      Object.keys(body.field).sort(),
      [...FIELD_KEYS].sort(),
      "the field carries exactly the keys the wire contract declares",
    );
    assert.deepEqual(
      [...keys].filter((key) => (STATION_KEYS as readonly string[]).includes(key)),
      [],
      "a station survives only as a count — `stations` and `stationsDown`",
    );
    assert.ok(!keys.has("alarms"), "…and its alarm summary never leaves cloud-1");
    assert.ok(!keys.has("power_w"));
  });

  it("never names distance_km in a request path", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());
    await radar(app);
    await birds(app);
    assert.ok(calls.length >= 4);
    for (const url of calls) {
      for (const forbidden of FORBIDDEN) {
        assert.ok(!url.includes(forbidden), `${url} names ${forbidden}`);
      }
    }
  });

  it("adopts exactly the named county keys", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());

    const { body } = await birds(app);
    assert.equal(body.counties.length, 2, "the state row is dropped on the way in");
    for (const county of body.counties) {
      assert.deepEqual([...Object.keys(county)].sort(), [...COUNTY_KEYS].sort());
    }
    const keys = keysOf(body.counties);
    assert.ok(!keys.has("mtr"), "a column this build does not read must not be adopted");
    assert.ok(!keys.has("vid"));
    assert.ok(!body.counties.some((c) => c.id === "US-CA"));
  });

  it("takes last night from the state row and never from a sum", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());
    const { body } = await birds(app);
    assert.ok(body.statewide);
    assert.equal(Math.round(body.statewide.crossed), 393290);
    // Summing the counties for that night gives 2,360,086. Nothing here does.
    assert.notEqual(Math.round(body.statewide.crossed), 2360086);
    assert.ok(!("granules" in body.statewide), "and only the five fields the wire declares");
  });
});

// ---- Both halves, or neither ---------------------------------------------

describe("a projection that half answers", () => {
  it("returns null when either half of the radar fetch fails", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());

    stationsBody = null;
    const first = await radar(app);
    // A lattice with a silently empty station list would draw every coverage
    // hole as clear sky, which is the strongest possible version of the failure
    // this whole layer exists to prevent.
    assert.equal(first.body.field, null);
    assert.equal(Date.parse(first.body.fetchedAt), 0);
  });

  it("returns null when either half of the birds fetch fails", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());

    nightBody = null;
    const { body } = await birds(app);
    // Without last night's figures the daylight sky has nothing to say but
    // "nothing is aloft", which is the blank panel this layer exists to avoid.
    assert.deepEqual(body.counties, []);
    assert.equal(body.statewide, null);
    assert.ok(body.quiet);
  });

  it("keeps serving the last whole body when the upstream goes away", async () => {
    const app = appWith({ ...CLOUD1, TERA_RADAR_TTL: "0" });
    after(() => app.close());

    const good = await radar(app);
    assert.ok(good.body.field);
    const stamp = good.body.fetchedAt;

    cellsBody = null;
    // Past the floored TTL, so the next request really does try again.
    await new Promise((resolve) => setTimeout(resolve, 5));
    const stale = await radar(app);
    assert.ok(stale.body.field, "ten-minute-old radar beats no radar, and beats a 503 by more");
    assert.equal(stale.body.fetchedAt, stamp);
  });

  it("floors the TTL, so TERA_RADAR_TTL=0 is not a fetch pair per request", async () => {
    const config = loadConfig({ ...CLOUD1, TERA_RADAR_TTL: "0", TERA_BIRDS_TTL: "0" });
    const app = buildApp({ ...config, logLevel: "silent" });
    after(() => app.close());

    const first = await radar(app);
    assert.ok(first.body.ttlSeconds >= 120);
    const second = await birds(app);
    assert.ok(second.body.ttlSeconds >= 300);
  });
});

// ---- The lattice ----------------------------------------------------------

describe("the lattice the server builds", () => {
  it("is the product's own grid clipped to the state board", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());

    const { body } = await radar(app);
    assert.ok(body.field);
    assert.equal(body.field.rows, 38);
    assert.equal(body.field.cols, 42);
    assert.equal(body.field.dbz.length, 38 * 42);
    assert.equal(body.field.cellLat, 0.25);
    assert.equal(body.field.stations, 3);
    // Two of the three carry a maintenance work order and both are transmitting.
    assert.equal(body.field.stationsDown, 0);
    assert.ok(JSON.stringify(body).length < 40_000, "the whole body is small enough to cache");
  });

  it("draws nothing when statewide coverage is under the floor", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());

    cellsBody = { ...(upstreamCells() as Record<string, unknown>), wetFraction: 0.0001 };
    const { body } = await radar(app);
    assert.equal(body.field, null, "promotion is decided from coverage, not from cell count");
    assert.notEqual(Date.parse(body.fetchedAt), 0, "…and the feed is alive, which the age says");
  });

  it("is publicly cacheable, because a projection cannot leak what it never sent", async () => {
    const app = appWith(CLOUD1);
    after(() => app.close());
    const { headers } = await radar(app);
    assert.match(String(headers["cache-control"]), /public/);
    const bird = await birds(app);
    assert.match(String(bird.headers["cache-control"]), /public/);
  });
});
