// Regression: when the Go-Live stream ends on its own (Discord closes it, the
// voice UDP drops, or ffmpeg exits) instead of via stop(), the capture ffmpeg
// MUST be killed and voice left. Otherwise the x11grab->nvenc encoder keeps
// running forever, feeding a pipe nobody reads, pinning a CPU core while no
// media is actually transmitted (observed live: 0 UDP sockets, 100% CPU).
import { test, expect, mock } from "bun:test";

test("a self-ended stream tears down the capture pipeline (no ffmpeg leak)", async () => {
  const kill = mock(() => {});
  const leaveVoice = mock(() => {});
  const destroy = mock(() => {});

  // Controllable Go-Live: resolve endStream() to simulate Discord closing it.
  let endStream!: () => void;
  const playPromise = new Promise<void>((r) => {
    endStream = r;
  });

  mock.module("node:child_process", () => ({
    spawn: () => ({ stdout: {}, stderr: { on() {} }, kill }),
  }));
  mock.module("discord.js-selfbot-v13", () => ({
    Client: class {
      destroy = destroy;
      async login() {}
    },
  }));
  mock.module("@dank074/discord-video-stream", () => ({
    Streamer: class {
      client: any;
      constructor(c: any) {
        this.client = c;
      }
      async joinVoice() {}
      leaveVoice = leaveVoice;
      // The Go-Live readiness signal start() now waits on: the stream
      // connection's WebRTC reaching "connected".
      get voiceConnection() {
        return { streamConnection: { webRtcConn: { ready: true } } };
      }
    },
    prepareStream: () => ({ command: { on() {} }, output: {} }),
    playStream: () => playPromise,
  }));

  const { SelfbotStreamer } = await import("./selfbot.ts");
  const s = new SelfbotStreamer({
    selfbotToken: "token",
    streamToken: "stream-token", // dedicated broadcast account -> broadcast allowed
    botToken: "",
    vncDisplay: ":1",
    vncResolution: "1920x1080",
    vncFramerate: 60,
    vncBitrateKbps: 8000,
    streamHw: true,
    streamReadyTimeoutMs: 2000,
  } as any);

  await s.start({ guildId: "g", voiceChannelId: "v" } as any);
  expect(s.isActive()).toBe(true);

  // Discord closes the Go-Live on its own (not a stop() call).
  endStream();
  await new Promise((r) => setTimeout(r, 0));

  expect(kill).toHaveBeenCalled(); // capture ffmpeg killed -> no CPU-burning orphan
  expect(leaveVoice).toHaveBeenCalled(); // voice connection released
  expect(s.isActive()).toBe(false);
}, 30000);

test("a Go-Live that never connects is reported as failed, not live, and is torn down", async () => {
  const kill = mock(() => {});
  const leaveVoice = mock(() => {});
  const destroy = mock(() => {});

  // playStream never resolves (the stream is "running" locally) but the WebRTC
  // never reaches "connected" -> start() must time out rather than claim live.
  const neverEnds = new Promise<void>(() => {});

  mock.module("node:child_process", () => ({
    spawn: () => ({ stdout: {}, stderr: { on() {} }, kill }),
  }));
  mock.module("discord.js-selfbot-v13", () => ({
    Client: class {
      destroy = destroy;
      async login() {}
    },
  }));
  mock.module("@dank074/discord-video-stream", () => ({
    Streamer: class {
      client: any;
      constructor(c: any) {
        this.client = c;
      }
      async joinVoice() {}
      leaveVoice = leaveVoice;
      // Stream connection exists but its WebRTC never connects.
      get voiceConnection() {
        return { streamConnection: { webRtcConn: { ready: false } } };
      }
    },
    prepareStream: () => ({ command: { on() {} }, output: {} }),
    playStream: () => neverEnds,
  }));

  const { SelfbotStreamer } = await import("./selfbot.ts");
  const s = new SelfbotStreamer({
    selfbotToken: "token",
    streamToken: "stream-token", // dedicated broadcast account -> broadcast allowed
    botToken: "",
    vncDisplay: ":1",
    vncResolution: "1920x1080",
    vncFramerate: 60,
    vncBitrateKbps: 8000,
    streamHw: true,
    streamReadyTimeoutMs: 800,
  } as any);

  const msg = await s.start({ guildId: "g", voiceChannelId: "v" } as any);

  expect(s.isActive()).toBe(false); // never claimed live
  expect(msg).toContain("방송을 시작하지 못했습니다"); // explicit failure status
  expect(kill).toHaveBeenCalled(); // local ffmpeg torn down on failure
  expect(leaveVoice).toHaveBeenCalled();
}, 30000);
