/**
 * The anonymous visitor gets a working studio, and the reason is a *pair* of
 * gates rather than one.
 *
 * This is a regression test for something that shipped. `/etc/tera-api.env` on
 * cloud-2 sets `TERA_DEVICES_SOURCE=sim`, so `/api/v1/health` reports
 * `sources.devices: "sim"` and `access.feeds.devices` is `true`. `main.ts`
 * passed that one fact into `createDeviceSource` as `serverHasDevices` and
 * never passed the viewer, so an anonymous visitor took the API strategy
 * against a members-only route, was refused with a 401 on every attempt, and
 * `apiSource` rendered `atRest()` — a rack of powered-off instruments — for the
 * life of the tab, backing off exponentially against a request that could not
 * ever pass. The panel next to it says the studio is simulated locally.
 *
 * Three assertions, and the first two are the interesting ones:
 *
 *  1. `capabilitiesFor("anon").liveDevices` is false, so the tier has an
 *     opinion at all — before this it had none.
 *  2. The call site in `main.ts` reads *both* facts. Asserted against the
 *     source text on purpose: the defect was never in `adapter.ts`, which has
 *     always chosen correctly given what it was told, and a unit test of the
 *     adapter would have stayed green through the entire outage.
 *  3. With both gates applied, an anonymous viewer on a `devices: "sim"`
 *     deployment gets readings that *change* — the thing "at rest forever"
 *     was not.
 */

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
import { capabilitiesFor, type Feeds } from "../access.ts";
import { createDeviceSource } from "../devices/adapter.ts";
import type { DeviceDeclaration } from "../devices/types.ts";

/** What cloud-2 reports today: a real device source, wired and named. */
const PRODUCTION_FEEDS: Feeds = {
  weather: true,
  flights: true,
  devices: true,
  satellites: false,
  markers: true,
  fires: true,
};

const MIC: DeviceDeclaration = {
  id: "mic-1",
  kind: "mic",
  label: "Desk mic",
  assetId: "tera:device.mic.desk",
  anchor: { levelId: "l1", propId: "mic-prop", seatId: "desk-01" },
  capabilities: ["power", "mute", "gain", "level"],
  provenance: "simulated",
  disclosure: "Simulated studio hardware. Demonstration data, never presence data.",
};

/**
 * The expression `main.ts` evaluates, restated once so the test can run it.
 * Assertion 2 is what keeps the two copies honest.
 */
function serverHasDevicesFor(tier: "anon" | "member" | "god", feeds: Feeds): boolean {
  return feeds.devices !== false && capabilitiesFor(tier).liveDevices;
}

describe("the anonymous studio", () => {
  it("does not grant an anonymous viewer the members-only device route", () => {
    assert.equal(capabilitiesFor("anon").liveDevices, false);
    assert.equal(capabilitiesFor("member").liveDevices, true);
    assert.equal(capabilitiesFor("god").liveDevices, true);
  });

  it("asks both questions at the call site in main.ts", () => {
    const source = readFileSync(new URL("../main.ts", import.meta.url), "utf8");
    const line = source
      .split("\n")
      .find((l) => l.includes("serverHasDevices:"));
    assert.ok(line, "main.ts no longer passes serverHasDevices");
    assert.match(line, /access\.feeds\?\.devices !== false/);
    assert.match(line, /access\.can\.liveDevices/);
  });

  it("gives an anon viewer on a devices:'sim' deployment readings that change", () => {
    const serverHasDevices = serverHasDevicesFor("anon", PRODUCTION_FEEDS);
    assert.equal(serverHasDevices, false, "anon must not take the API strategy");

    const source = createDeviceSource({
      declarations: [MIC],
      // A client is present — this is a real deployment — and it is the tier,
      // not its absence, that has to keep us off the route.
      client: {
        watchDevices: () => ({ current: () => null, refresh: () => {}, stop: () => {} }),
        commandDevice: () => Promise.resolve(null),
      },
      officeId: "mateo-court",
      serverHasDevices,
      seed: 8731,
      fixedStepSeconds: 0.05,
    });

    const first = JSON.stringify(source.current());
    // 200 ms of wall clock, at the simulator's own fixed step.
    source.tick(0.2);
    const later = JSON.stringify(source.current());
    source.stop();

    assert.notEqual(
      first,
      later,
      "an anonymous studio must be alive, not a rack of instruments at rest",
    );
  });
});
