import { EventEmitter } from "node:events";
import { describe, expect, it, vi } from "vitest";
import type { AppUpdater, UpdateInfo } from "electron-updater";
import { isSmokeRun } from "../desktop/runtimeFlags.js";
import { configureUpdateManager, updateDisabledReason } from "../desktop/updateManager.js";

class FakeUpdater extends EventEmitter {
  autoDownload = false;
  autoInstallOnAppQuit = false;
  checkForUpdates = vi.fn(async () => null);
  quitAndInstall = vi.fn();
}

describe("desktop update manager", () => {
  it("recognizes both supported smoke triggers in one shared helper", () => {
    expect(isSmokeRun([], {})).toBe(false);
    expect(isSmokeRun(["electron", ".", "--smoke"], {})).toBe(true);
    expect(isSmokeRun([], { DESKTOP_SMOKE: "1" })).toBe(true);
  });

  it("only enables update-capable installed targets", () => {
    const base = {
      isPackaged: true,
      platform: "win32" as const,
      isPortable: false,
      isAppImage: false,
      isSmoke: false,
      disabled: false,
    };
    expect(updateDisabledReason(base)).toBeNull();
    expect(updateDisabledReason({ ...base, isPortable: true })).toBe("portable Windows build");
    expect(updateDisabledReason({ ...base, platform: "linux", isAppImage: true })).toBeNull();
    expect(updateDisabledReason({ ...base, platform: "linux" })).toBe("non-AppImage Linux build");
    expect(updateDisabledReason({ ...base, platform: "darwin" })).toBe("unsigned macOS build");
    expect(updateDisabledReason({ ...base, isPackaged: false })).toBe("development build");
  });

  it("checks on schedule and installs a downloaded update after confirmation", async () => {
    const updater = new FakeUpdater();
    const scheduled: Array<() => void> = [];
    const timers: NodeJS.Timeout[] = [];
    const makeTimer = (callback: () => void) => {
      scheduled.push(callback);
      const timer = { unref: vi.fn() } as unknown as NodeJS.Timeout;
      timers.push(timer);
      return timer;
    };
    const confirmRestart = vi.fn(async () => true);
    const dispose = configureUpdateManager({
      updater: updater as unknown as AppUpdater,
      confirmRestart,
      logError: vi.fn(),
      setTimeout: makeTimer,
      setInterval: makeTimer,
      clearTimeout: vi.fn(),
      clearInterval: vi.fn(),
    });

    expect(updater.autoDownload).toBe(true);
    expect(updater.autoInstallOnAppQuit).toBe(true);
    scheduled[0]!();
    await vi.waitFor(() => expect(updater.checkForUpdates).toHaveBeenCalledOnce());
    updater.emit("update-downloaded", { version: "0.2.0" } as UpdateInfo);
    await vi.waitFor(() => expect(confirmRestart).toHaveBeenCalledWith("0.2.0"));
    expect(updater.quitAndInstall).toHaveBeenCalledWith(false, true);

    dispose();
    expect(updater.listenerCount("error")).toBe(0);
    expect(updater.listenerCount("update-downloaded")).toBe(0);
    expect(timers).toHaveLength(2);
  });

  it("logs a rejected check only through the updater error event", async () => {
    const updater = new FakeUpdater();
    const failure = new Error("offline");
    updater.checkForUpdates.mockRejectedValueOnce(failure);
    const scheduled: Array<() => void> = [];
    const timer = { unref: vi.fn() } as unknown as NodeJS.Timeout;
    const logError = vi.fn();
    const dispose = configureUpdateManager({
      updater: updater as unknown as AppUpdater,
      confirmRestart: vi.fn(async () => false),
      logError,
      setTimeout: (callback) => { scheduled.push(callback); return timer; },
      setInterval: (callback) => { scheduled.push(callback); return timer; },
      clearTimeout: vi.fn(),
      clearInterval: vi.fn(),
    });

    scheduled[0]!();
    await vi.waitFor(() => expect(updater.checkForUpdates).toHaveBeenCalledOnce());
    expect(logError).not.toHaveBeenCalled();
    updater.emit("error", failure);
    expect(logError).toHaveBeenCalledOnce();
    expect(logError).toHaveBeenCalledWith(failure);
    dispose();
  });
});
