import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useConfigItem } from "../useConfigItem";

describe("useConfigItem", () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
    vi.clearAllMocks();
  });

  it("loads data and sets loading state", async () => {
    const fetchFn = vi.fn().mockResolvedValue("hello");
    const saveFn = vi.fn().mockResolvedValue(undefined);

    const { result } = renderHook(() => useConfigItem<string, string>({ fetchFn, saveFn }));

    expect(result.current.loading).toBe(true);
    await act(async () => {
      await Promise.resolve();
    });

    expect(fetchFn).toHaveBeenCalled();
    expect(result.current.data).toBe("hello");
    expect(result.current.loading).toBe(false);
  });

  it("sets error if fetch rejects", async () => {
    const fetchFn = vi.fn().mockRejectedValue(new Error("nope"));
    const saveFn = vi.fn().mockResolvedValue(undefined);

    const { result } = renderHook(() => useConfigItem<string, string>({ fetchFn, saveFn }));

    await act(async () => {
      await Promise.resolve();
    });

    expect(result.current.error).toBe("nope");
    expect(result.current.loading).toBe(false);
  });

  it("save updates data when mergeOnSave is provided", async () => {
    const fetchFn = vi.fn().mockResolvedValue({ value: 1 });
    const saveFn = vi.fn().mockResolvedValue(undefined);

    const { result } = renderHook(() =>
      useConfigItem<{ value: number }, { delta: number }>({
        fetchFn,
        saveFn,
        mergeOnSave: (prev, update) =>
          prev ? { ...prev, value: prev.value + update.delta } : prev,
      })
    );

    await act(async () => {
      await Promise.resolve();
    });

    expect(result.current.data).toEqual({ value: 1 });

    await act(async () => {
      await result.current.save({ delta: 2 });
    });

    expect(saveFn).toHaveBeenCalledWith({ delta: 2 });
    expect(result.current.data).toEqual({ value: 3 });
  });

  it("saveError is set when save fails", async () => {
    const fetchFn = vi.fn().mockResolvedValue("ok");
    const saveFn = vi.fn().mockRejectedValue(new Error("save failed"));

    const { result } = renderHook(() => useConfigItem<string, string>({ fetchFn, saveFn }));

    await act(async () => {
      await Promise.resolve();
    });

    await act(async () => {
      await expect(result.current.save("test")).rejects.toThrow("save failed");
    });

    expect(result.current.saveError).toBe("save failed");
  });
});
