import { test, expect } from "bun:test";

// broadcast.ts pulls in the runtime `config`, which requires DISCORD_GUILD_ID.
// Set it before the dynamic import so the module loads without a real .env.
process.env.DISCORD_GUILD_ID ||= "test-guild";
const { wireBroadcast, autoStartBroadcast } = await import("./broadcast.ts");

/** Minimal in-memory ScreenStreamer that records start/stop calls. */
function fakeStreamer(active = false) {
  const calls: string[] = [];
  let on = active;
  return {
    calls,
    kind: "selfbot" as const,
    isActive: () => on,
    async start() {
      calls.push("start");
      on = true;
      return "ok";
    },
    async stop() {
      calls.push("stop");
      on = false;
    },
  };
}

const ctx = { guildId: "g", voiceChannelId: "c" };

test("wireBroadcast reports live state to the brain", () => {
  const s = fakeStreamer(false);
  const session: any = {};
  wireBroadcast(session, s as any, ctx);
  expect(session.getBroadcasting()).toBe(false);
});

test("wireBroadcast lets the brain toggle the stream by voice", async () => {
  const s = fakeStreamer(false);
  const session: any = {};
  wireBroadcast(session, s as any, ctx);
  await session.onBroadcastAction("start"); // idle -> start
  expect(session.getBroadcasting()).toBe(true);
  await session.onBroadcastAction("start"); // already live -> no-op
  await session.onBroadcastAction("stop"); // stop
  expect(session.getBroadcasting()).toBe(false);
  expect(s.calls).toEqual(["start", "stop"]);
});

test("autoStartBroadcast starts the broadcast when idle", async () => {
  const s = fakeStreamer(false);
  const ok = await autoStartBroadcast(s as any, ctx, 2, 0);
  expect(ok).toBe(true);
  expect(s.calls).toEqual(["start"]);
});

test("autoStartBroadcast does not restart an already-live broadcast", async () => {
  const s = fakeStreamer(true);
  const ok = await autoStartBroadcast(s as any, ctx, 2, 0);
  expect(ok).toBe(true);
  expect(s.calls).toEqual([]);
});

test("autoStartBroadcast retries a transient failure and then succeeds", async () => {
  let on = false;
  const calls: string[] = [];
  const s: any = {
    isActive: () => on,
    async start() {
      calls.push("start");
      if (calls.length === 1) throw new Error("transient: voice not ready");
      on = true;
    },
    async stop() {},
  };
  const ok = await autoStartBroadcast(s, ctx, 2, 0);
  expect(ok).toBe(true);
  expect(calls).toEqual(["start", "start"]);
});

test("autoStartBroadcast returns false after exhausting retries (no throw)", async () => {
  let attempts = 0;
  const s: any = {
    isActive: () => false,
    async start() {
      attempts++;
      throw new Error("boom");
    },
    async stop() {},
  };
  const ok = await autoStartBroadcast(s, ctx, 2, 0);
  expect(ok).toBe(false);
  expect(attempts).toBe(2); // retried, then gave up without throwing
});
