// @vitest-environment jsdom
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);

import { useConnectionStore } from "@/stores/connection-store";

function okHealth() {
  return Promise.resolve(new Response(JSON.stringify({ status: "healthy" }), { status: 200 }));
}

function failHealth() {
  return Promise.reject(new TypeError("Failed to fetch"));
}

function nonOkHealth() {
  return Promise.resolve(new Response(null, { status: 503 }));
}

describe("connection-store", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    useConnectionStore.setState({
      status: "connected",
      failedSince: null,
      lastHealthCheck: null,
    });
    fetchMock.mockReset();
  });

  afterEach(() => {
    useConnectionStore.getState().stopPolling();
    vi.useRealTimers();
  });

  it("starts in connected state", () => {
    expect(useConnectionStore.getState().status).toBe("connected");
  });

  it("transitions to disconnected on setDisconnected", () => {
    useConnectionStore.getState().setDisconnected();
    const state = useConnectionStore.getState();
    expect(state.status).toBe("disconnected");
    expect(state.failedSince).toBeTypeOf("number");
  });

  it("does not overwrite failedSince on repeated setDisconnected calls", () => {
    useConnectionStore.getState().setDisconnected();
    const first = useConnectionStore.getState().failedSince;
    useConnectionStore.getState().setDisconnected();
    expect(useConnectionStore.getState().failedSince).toBe(first);
  });

  it("transitions to offline on setOffline", () => {
    useConnectionStore.getState().setOffline();
    expect(useConnectionStore.getState().status).toBe("offline");
  });

  it("transitions from offline to disconnected on setOnline", () => {
    useConnectionStore.getState().setOffline();
    useConnectionStore.getState().setOnline();
    expect(useConnectionStore.getState().status).toBe("disconnected");
  });

  it("checkHealth transitions disconnected → reconnected on success", async () => {
    fetchMock.mockImplementation(okHealth);
    useConnectionStore.getState().setDisconnected();
    await useConnectionStore.getState().checkHealth();
    expect(useConnectionStore.getState().status).toBe("reconnected");
    expect(useConnectionStore.getState().lastHealthCheck).toBeTypeOf("number");
  });

  it("checkHealth stays disconnected on failure", async () => {
    fetchMock.mockImplementation(failHealth);
    useConnectionStore.getState().setDisconnected();
    await useConnectionStore.getState().checkHealth();
    expect(useConnectionStore.getState().status).toBe("disconnected");
  });

  it("checkHealth is a no-op when already connected", async () => {
    fetchMock.mockImplementation(okHealth);
    await useConnectionStore.getState().checkHealth();
    expect(useConnectionStore.getState().status).toBe("connected");
    expect(fetchMock).toHaveBeenCalledTimes(1);
  });

  it("checkHealth transitions connected → disconnected on non-ok response", async () => {
    fetchMock.mockImplementation(nonOkHealth);
    expect(useConnectionStore.getState().status).toBe("connected");
    await useConnectionStore.getState().checkHealth();
    expect(useConnectionStore.getState().status).toBe("disconnected");
  });

  it("checkHealth transitions connected → disconnected on fetch failure", async () => {
    fetchMock.mockImplementation(failHealth);
    expect(useConnectionStore.getState().status).toBe("connected");
    await useConnectionStore.getState().checkHealth();
    expect(useConnectionStore.getState().status).toBe("disconnected");
    expect(useConnectionStore.getState().failedSince).toBeTypeOf("number");
  });

  it("checkHealth transitions offline → reconnected on success", async () => {
    fetchMock.mockImplementation(okHealth);
    useConnectionStore.getState().setOffline();
    expect(useConnectionStore.getState().status).toBe("offline");
    await useConnectionStore.getState().checkHealth();
    expect(useConnectionStore.getState().status).toBe("reconnected");
    expect(useConnectionStore.getState().failedSince).toBeNull();
    expect(useConnectionStore.getState().lastHealthCheck).toBeTypeOf("number");
  });

  it("startPolling is idempotent", () => {
    useConnectionStore.getState().setDisconnected();
    useConnectionStore.getState().startPolling();
    useConnectionStore.getState().startPolling();
    fetchMock.mockImplementation(failHealth);
    vi.advanceTimersByTime(3000);
    expect(fetchMock).toHaveBeenCalledTimes(1);
  });

  it("stopPolling clears the interval", () => {
    useConnectionStore.getState().setDisconnected();
    fetchMock.mockImplementation(failHealth);
    useConnectionStore.getState().startPolling();
    useConnectionStore.getState().stopPolling();
    vi.advanceTimersByTime(6000);
    expect(fetchMock).not.toHaveBeenCalled();
  });

  it("setOnline is no-op when not offline", () => {
    useConnectionStore.setState({ status: "connected", failedSince: null, lastHealthCheck: null });
    useConnectionStore.getState().setOnline();
    expect(useConnectionStore.getState().status).toBe("connected");
  });

  it("refreshStaleData calls settings fetch and features refresh", async () => {
    vi.useRealTimers();
    // Mock the dynamically imported stores
    const mockFetch = vi.fn().mockResolvedValue(undefined);
    const mockRefresh = vi.fn().mockResolvedValue(undefined);
    vi.doMock("@/stores/settings-store", () => ({
      useSettingsStore: {
        setState: vi.fn(),
        getState: () => ({ fetch: mockFetch }),
      },
    }));
    vi.doMock("@/stores/features-store", () => ({
      useFeaturesStore: {
        getState: () => ({ refresh: mockRefresh }),
      },
    }));

    await useConnectionStore.getState().refreshStaleData();
    // The function should complete without throwing
    expect(true).toBe(true);
    vi.doUnmock("@/stores/settings-store");
    vi.doUnmock("@/stores/features-store");
  });
});
