import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

// Mock fetch globally before imports
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);

// Mock open, clack, and hardware modules
vi.mock("open", () => ({ default: vi.fn().mockResolvedValue(undefined) }));
vi.mock("@clack/prompts", () => ({
  spinner: vi.fn(() => ({ start: vi.fn(), stop: vi.fn() })),
  select: vi.fn(),
  isCancel: vi.fn().mockReturnValue(false),
  cancel: vi.fn(),
  outro: vi.fn(),
  log: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), step: vi.fn() },
  intro: vi.fn(),
}));
vi.mock("../hardware.js", () => ({
  detectHardware: vi.fn().mockResolvedValue({ tier: "gpu", totalGb: 32, gpuName: "RTX 4090", gpuVramGb: 24 }),
}));
vi.mock("../banner.js", () => ({
  printBuildthisBanner: vi.fn(),
}));

const { probeRunningInstance, getProviderOptions } = await import("../bootstrap.js");

describe("probeRunningInstance", () => {
  beforeEach(() => {
    vi.resetAllMocks();
    vi.stubGlobal("fetch", mockFetch);
  });

  it("returns true when health endpoint responds with 200", async () => {
    mockFetch.mockResolvedValue({ ok: true, status: 200 });
    const result = await probeRunningInstance(3100);
    expect(result).toBe(true);
    expect(mockFetch).toHaveBeenCalledWith(
      "http://127.0.0.1:3100/api/health",
      expect.objectContaining({ signal: expect.any(Object) })
    );
  });

  it("returns false when fetch throws (connection refused)", async () => {
    mockFetch.mockRejectedValue(new Error("ECONNREFUSED"));
    const result = await probeRunningInstance(3100);
    expect(result).toBe(false);
  });

  it("returns false when health endpoint responds with non-200 status", async () => {
    mockFetch.mockResolvedValue({ ok: false, status: 503 });
    const result = await probeRunningInstance(3100);
    expect(result).toBe(false);
  });

  it("returns false when AbortError is thrown (timeout)", async () => {
    const abortError = new DOMException("The operation was aborted", "AbortError");
    mockFetch.mockRejectedValue(abortError);
    const result = await probeRunningInstance(3100);
    expect(result).toBe(false);
  });
});

describe("getProviderOptions", () => {
  it("includes local AI option when tier is apple_silicon", () => {
    const options = getProviderOptions("apple_silicon");
    const values = options.map((o) => o.value);
    expect(values).toContain("local");
  });

  it("includes local AI option when tier is gpu", () => {
    const options = getProviderOptions("gpu");
    const values = options.map((o) => o.value);
    expect(values).toContain("local");
  });

  it("excludes local AI option when tier is cpu_only", () => {
    const options = getProviderOptions("cpu_only");
    const values = options.map((o) => o.value);
    expect(values).not.toContain("local");
  });

  it("always includes puter, google, apikey, and skip options", () => {
    for (const tier of ["apple_silicon", "gpu", "cpu_only"] as const) {
      const options = getProviderOptions(tier);
      const values = options.map((o) => o.value);
      expect(values).toContain("puter");
      expect(values).toContain("google");
      expect(values).toContain("apikey");
      expect(values).toContain("skip");
    }
  });
});
