/**
 * The migration gate, and the two errors it exists to prevent.
 *
 * **The six-times headline.** `SELECT SUM(birds_crossed)` over the 58 county
 * rows for the night of 2026-08-21 gives 2,360,086. The authoritative figure, on
 * the `US-CA` state row for the same night, is 393,290. Both numbers are
 * correct — a bird crossing four counties is counted in four of them — and only
 * one of them is a crossing count. A headline wrong by six times is the fire
 * layer's twenty-two orange marks in a different costume, and the only thing
 * standing between this build and it is that nothing here ever adds counties up.
 *
 * **The state row placed as a county.** `counties` upstream is 59 rows, not 58.
 * The 59th is `US-CA`, `kind='state'`, with NULL lat and lon, and in tonight's
 * granule it carries 793,141 birds aloft against the largest county's 82,549. A
 * per-county path that does not take it out puts a three-quarter-million-bird
 * blob at 0,0 — and it looks exactly like the other fifty-eight rows, which is
 * why it is refused on three independent grounds and asserted on all three.
 *
 * The third thing here is the **empty state**, which is not an edge case: 176 of
 * 297 granules in the store are daytime and hold 104 rows between them against
 * 7,719 at night, so the layer is absent about fourteen hours in every
 * twenty-four by construction. Every default frame `scripts/look.mjs` takes is a
 * daylight frame. The sentence is the layer, for most visitors, and it is tested
 * as such.
 */

import assert from "node:assert/strict";
import { describe, it } from "node:test";

import {
  BIRDS_MAX_SOLAR_ELEVATION_DEG,
  BIRDS_STATE_ROW_ID,
  countyRows,
  emptyBirdsPromotion,
  headingWords,
  isCountyRow,
  promoteBirds,
  quietMessage,
  statewideHeadline,
} from "../../server/birds.ts";
import type { BirdsBody, WireBirdCounty } from "../../server/wire.ts";

/**
 * The granule of 2026-08-23T03:20Z, as the store holds it — the six busiest
 * counties, plus the row that is not a county at all.
 *
 * Real numbers, from `migration` joined to `counties`. The `US-CA` row's
 * `birds_aloft` of 793,141 is what makes the exclusion visible: it is 9.6 times
 * the largest county in the same granule.
 */
const STATE_ROW: WireBirdCounty = {
  id: "US-CA",
  name: "California",
  lat: null as unknown as number,
  lon: null as unknown as number,
  areaKm2: null as unknown as number,
  aloft: 793141,
  altitude: 605,
  direction: 140.6,
  speed: 4.8,
};

const COUNTIES: WireBirdCounty[] = [
  { 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 },
  { 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 },
  { id: "US-CA-027", name: "Inyo County", lat: 36.56216, lon: -117.404209, areaKm2: 26488, aloft: 36214, altitude: 826, direction: 118.0, speed: 6.4 },
  { id: "US-CA-029", name: "Kern County", lat: 35.346629, lon: -118.729506, areaKm2: 21147, aloft: 30492, altitude: 469, direction: 122.3, speed: 7.3 },
  { id: "US-CA-073", name: "San Diego County", lat: 33.023604, lon: -116.776117, areaKm2: 11722, aloft: 29809, altitude: 760, direction: 144.5, speed: 6.6 },
  { id: "US-CA-039", name: "Madera County", lat: 37.209821, lon: -119.749802, areaKm2: 5576, aloft: 28061, altitude: 351, direction: 141.0, speed: 6.0 },
];

/** The state row for the night of 2026-08-21, verbatim. */
const LAST_NIGHT = {
  crossed: 393290.37,
  peakAloft: 1501193.14,
  peakAt: "2026-08-22T06:20:00Z",
  meanAltitude: 726.0,
  heading: "south-east",
};

/** What summing the 58 county rows for that night gives. The wrong answer. */
const COUNTY_SUM_TRAP = 2360086;

const NOW = Date.parse("2026-08-23T03:41:04Z");

function body(over: Partial<BirdsBody> = {}): BirdsBody {
  return {
    source: "cloud1",
    fetchedAt: "2026-08-23T03:41:04Z",
    observedAt: "2026-08-23T03:20:00Z",
    counties: [...COUNTIES, STATE_ROW],
    statewide: LAST_NIGHT,
    quiet: null,
    ttlSeconds: 600,
    ...over,
  };
}

// ---- The state row --------------------------------------------------------

describe("the row that is a state and not a county", () => {
  it("is refused on all three of the grounds that identify it", () => {
    assert.equal(isCountyRow(STATE_ROW), false);
    // By id…
    assert.equal(isCountyRow({ ...STATE_ROW, lat: 37, lon: -119, areaKm2: 423967 }), false);
    // …by a missing coordinate…
    assert.equal(isCountyRow({ ...COUNTIES[0]!, lat: null as unknown as number }), false);
    // …and by a missing area, because the motes scatter in a disc of it.
    assert.equal(isCountyRow({ ...COUNTIES[0]!, areaKm2: 0 }), false);
    assert.equal(isCountyRow(COUNTIES[0]), true);
    assert.equal(BIRDS_STATE_ROW_ID, "US-CA");
  });

  it("never reaches any per-county output", () => {
    const rows = countyRows([...COUNTIES, STATE_ROW]);
    assert.equal(rows.length, COUNTIES.length);
    assert.ok(!rows.some((row) => row.id === BIRDS_STATE_ROW_ID));

    const out = promoteBirds(body(), { nowMs: NOW, solarElevationDeg: -20 });
    assert.ok(out.field);
    assert.ok(!out.field.counties.some((c) => c.id === BIRDS_STATE_ROW_ID));
    // And the largest thing on the board is the largest COUNTY, not the state.
    const biggest = Math.max(...out.field.counties.map((c) => c.aloft));
    assert.equal(biggest, 82549);
  });
});

// ---- The headline ---------------------------------------------------------

describe("the statewide headline", () => {
  it("is the state row's own figure and never the sum of counties", () => {
    const head = statewideHeadline(body());
    assert.ok(head);
    assert.equal(Math.round(head.crossed), 393290);
    assert.notEqual(Math.round(head.crossed), COUNTY_SUM_TRAP);
    assert.equal(Math.round(head.peakAloft), 1501193);
  });

  it("is null rather than reconstructed when the state row is missing", () => {
    // A box up for less than one night has no crossing count, and the honest
    // answer is to have none — not to add the counties together and be wrong by
    // six times without saying so.
    assert.equal(statewideHeadline(body({ statewide: null })), null);
    const out = promoteBirds(body({ statewide: null }), { nowMs: NOW, solarElevationDeg: 20 });
    assert.ok(out.field?.quiet);
    assert.ok(!out.message.includes("Last night"));
  });

  it("says which way, in words the mean can support", () => {
    assert.equal(headingWords(130.3), "south-east");
    assert.equal(headingWords(0), "north");
    assert.equal(headingWords(359), "north");
    assert.equal(headingWords(181), "south");
  });
});

// ---- The empty sky --------------------------------------------------------

describe("the daytime sky", () => {
  it("returns an explicit empty-with-reason rather than an empty array", () => {
    // The sun is up. This is the case most visitors meet, and a bare `[]` beside
    // nothing else is indistinguishable from a dead feed.
    const out = promoteBirds(body(), { nowMs: NOW, solarElevationDeg: 34 });
    assert.ok(out.field);
    assert.deepEqual(out.field.counties, []);
    assert.ok(out.field.quiet, "quiet is not optional and must be filled in");
    assert.equal(out.field.quiet.reason, "daylight");
  });

  it("names last night's figures in the reason", () => {
    const out = promoteBirds(body(), { nowMs: NOW, solarElevationDeg: 34 });
    const message = out.field?.quiet?.message ?? "";
    assert.match(message, /BirdCast measures migration only after dark/);
    assert.match(message, /393,290/);
    assert.match(message, /1,501,193/);
    assert.match(message, /south-east/);
    assert.match(message, /726 metres/);
    // Los Angeles time, not UTC. 06:20Z is 23:20 the previous evening in PDT,
    // and a caption that said 06:20 would put the peak at breakfast.
    assert.match(message, /23:20/);
    assert.equal(out.message, message);
  });

  it("refuses daytime rows even when the feed sends them", () => {
    // 104 daytime rows exist in the store across 176 granules. BirdCast does not
    // measure by day, so a daytime row is a measurement of something the
    // instrument does not measure, and the sun wins over the feed.
    const justAfterDawn = BIRDS_MAX_SOLAR_ELEVATION_DEG + 0.1;
    const out = promoteBirds(body(), { nowMs: NOW, solarElevationDeg: justAfterDawn });
    assert.deepEqual(out.field?.counties, []);
    assert.equal(out.field?.quiet?.reason, "daylight");
    assert.equal(out.suppressed, 7, "…and counts every row it put back");
  });

  it("tells a quiet night from a daytime one and from a dead feed", () => {
    const night = promoteBirds(body({ counties: [STATE_ROW] }), { nowMs: NOW, solarElevationDeg: -20 });
    assert.equal(night.field?.quiet?.reason, "off-season");
    assert.match(night.field?.quiet?.message ?? "", /every county is quiet/);

    const dead = promoteBirds(body({ fetchedAt: new Date(0).toISOString() }), { nowMs: NOW });
    assert.equal(dead.field, null);
    assert.equal(dead.ageMs, null);
    assert.match(dead.message, /fact about this box/);
  });

  it("has an empty promotion that is a sentence and not a blank", () => {
    const empty = emptyBirdsPromotion();
    assert.equal(empty.source, "none");
    assert.equal(Date.parse(empty.fetchedAt), 0);
    assert.ok(empty.message.length > 40);
    assert.ok(quietMessage("no-data", null).length > 20);
  });
});

// ---- A night with something in it -----------------------------------------

describe("a night with birds over it", () => {
  it("draws the counties and says where they are going", () => {
    const out = promoteBirds(body(), { nowMs: NOW, solarElevationDeg: -20 });
    assert.equal(out.field?.quiet, null);
    assert.equal(out.field?.counties.length, 6);
    assert.match(out.message, /aloft over 6 counties/);
    assert.match(out.message, /drifting south-east/);
    // And it does NOT print a current statewide total, because the only way to
    // get one from what crosses the wire is to add the counties up.
    assert.ok(!out.message.includes(String(COUNTY_SUM_TRAP)));
  });

  it("averages bearings circularly, so the wrap does not point the state south", () => {
    const wrap: WireBirdCounty[] = [
      { ...COUNTIES[0]!, direction: 350, aloft: 1000 },
      { ...COUNTIES[1]!, direction: 10, aloft: 1000 },
    ];
    const out = promoteBirds(body({ counties: wrap }), { nowMs: NOW, solarElevationDeg: -20 });
    // An arithmetic mean of 350 and 10 is 180: due south, for a state flying
    // due north. Tonight's rows all sit in the 120-150 bucket and would survive
    // a naive mean, which is exactly why this would ship broken and stay broken.
    assert.match(out.message, /drifting north/);
    assert.ok(!out.message.includes("drifting south"));
  });

  it("survives a null, an undefined and a body full of nonsense", () => {
    for (const input of [null, undefined, 7, "birds", { source: "cloud1" }]) {
      const out = promoteBirds(input as unknown as BirdsBody, { nowMs: NOW });
      assert.notEqual(out.message, "");
    }
    const junk = promoteBirds(
      body({ counties: [null, { id: "x" }] as unknown as WireBirdCounty[] }),
      { nowMs: NOW, solarElevationDeg: -20 },
    );
    assert.deepEqual(junk.field?.counties, []);
  });
});
