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

import type { DashboardServiceItem } from "@/components/dashboard/services/services-types";

import { useDashboardServicesData } from "@/hooks/use-dashboard-services-data";

const { useServicesMock, deleteServiceMock, toggleServiceMock, duplicateServiceMock, reorderServicesMock, buildReorderItemsMock } =
  vi.hoisted(() => ({
    useServicesMock: vi.fn(),
    deleteServiceMock: vi.fn(),
    toggleServiceMock: vi.fn(),
    duplicateServiceMock: vi.fn(),
    reorderServicesMock: vi.fn(),
    buildReorderItemsMock: vi.fn(),
  }));

vi.mock("@/components/dashboard/services/services-api", () => ({
  useServices: useServicesMock,
  deleteService: deleteServiceMock,
  toggleService: toggleServiceMock,
  duplicateService: duplicateServiceMock,
  reorderServices: reorderServicesMock,
  buildReorderItems: buildReorderItemsMock,
}));

const first: DashboardServiceItem = {
  id: 1,
  title: "Financial Consulting",
  short_description: "Advisory service",
  description: "Detailed description",
  icon: "icon-chart",
  image: "services/1.jpg",
  is_active: true,
  order: 0,
};
const second: DashboardServiceItem = {
  ...first,
  id: 2,
  title: "Research",
  short_description: "Market research surveys",
  order: 1,
};

beforeEach(() => {
  vi.clearAllMocks();
  useServicesMock.mockReturnValue({
    services: [first, second],
    isLoading: false,
    error: undefined,
    isValidating: false,
    refetch: vi.fn(),
  });
  deleteServiceMock.mockResolvedValue(undefined);
  toggleServiceMock.mockResolvedValue(undefined);
  duplicateServiceMock.mockResolvedValue(undefined);
  reorderServicesMock.mockResolvedValue(undefined);
});

describe("useDashboardServicesData", () => {
  it("sorts, searches, and paginates live services", () => {
    useServicesMock.mockReturnValue({
      services: [second, first],
      isLoading: false,
      error: undefined,
      isValidating: false,
      refetch: vi.fn(),
    });

    const { result } = renderHook(() =>
      useDashboardServicesData({ page: 1, perPage: 1, search: "financial" }),
    );

    expect(result.current.data).toEqual([first]);
    expect(result.current.totalItems).toBe(1);
  });

  it("returns all services sorted by order when no search is provided", () => {
    useServicesMock.mockReturnValue({
      services: [second, first],
      isLoading: false,
      error: undefined,
      isValidating: false,
      refetch: vi.fn(),
    });

    const { result } = renderHook(() =>
      useDashboardServicesData({ page: 1, perPage: 10 }),
    );

    expect(result.current.data).toEqual([first, second]);
    expect(result.current.totalItems).toBe(2);
  });

  it("exposes loading and error states from the query", () => {
    useServicesMock.mockReturnValue({
      services: undefined,
      isLoading: true,
      error: undefined,
      isValidating: false,
      refetch: vi.fn(),
    });

    const { result } = renderHook(() => useDashboardServicesData());

    expect(result.current.isLoading).toBe(true);
    expect(result.current.error).toBeUndefined();
  });

  it("exposes the error from the query", () => {
    const error = new Error("Network failure");
    useServicesMock.mockReturnValue({
      services: undefined,
      isLoading: false,
      error,
      isValidating: false,
      refetch: vi.fn(),
    });

    const { result } = renderHook(() => useDashboardServicesData());

    expect(result.current.error).toBe(error);
  });

  it("reorders using the full collection", async () => {
    buildReorderItemsMock.mockReturnValue([
      { id: 2, order: 0 },
      { id: 1, order: 1 },
    ]);

    const { result } = renderHook(() => useDashboardServicesData());

    await act(() => result.current.moveService(2, "up"));

    expect(buildReorderItemsMock).toHaveBeenCalledWith([first, second], 2, "up");
    expect(reorderServicesMock).toHaveBeenCalledWith([
      { id: 2, order: 0 },
      { id: 1, order: 1 },
    ]);
  });

  it("skips reorder when buildReorderItems returns null", async () => {
    buildReorderItemsMock.mockReturnValue(null);

    const { result } = renderHook(() => useDashboardServicesData());

    await act(() => result.current.moveService(1, "up"));

    expect(reorderServicesMock).not.toHaveBeenCalled();
  });

  it("removes a service and clears pending state on success", async () => {
    const { result } = renderHook(() => useDashboardServicesData());

    expect(result.current.pendingServiceId).toBeNull();

    await act(() => result.current.removeService(1));

    expect(deleteServiceMock).toHaveBeenCalledWith(1);
    expect(result.current.pendingServiceId).toBeNull();
  });

  it("clears pending state and rethrows on rejected delete", async () => {
    deleteServiceMock.mockRejectedValue(new Error("Delete failed"));

    const { result } = renderHook(() => useDashboardServicesData());

    await expect(act(() => result.current.removeService(1))).rejects.toThrow("Delete failed");

    expect(result.current.pendingServiceId).toBeNull();
  });

  it("toggles a service status", async () => {
    const { result } = renderHook(() => useDashboardServicesData());

    await act(() => result.current.toggleService(1));

    expect(toggleServiceMock).toHaveBeenCalledWith(1);
    expect(result.current.pendingServiceId).toBeNull();
  });

  it("duplicates a service", async () => {
    const { result } = renderHook(() => useDashboardServicesData());

    await act(() => result.current.copyService(1));

    expect(duplicateServiceMock).toHaveBeenCalledWith(1);
    expect(result.current.pendingServiceId).toBeNull();
  });

  it("sets pendingServiceId while an action is running and clears after", async () => {
    let resolvePromise!: () => void;
    deleteServiceMock.mockImplementation(
      () => new Promise<void>((resolve) => { resolvePromise = resolve; }),
    );

    const { result } = renderHook(() => useDashboardServicesData());

    act(() => { result.current.removeService(1); });

    // After act triggers the action, pending is set before the promise resolves
    expect(result.current.pendingServiceId).toBe(1);

    resolvePromise();
    await act(async () => {});

    expect(result.current.pendingServiceId).toBeNull();
  });

  it("normalizes page and perPage to positive values", () => {
    useServicesMock.mockReturnValue({
      services: [first, second],
      isLoading: false,
      error: undefined,
      isValidating: false,
      refetch: vi.fn(),
    });

    const { result } = renderHook(() =>
      useDashboardServicesData({ page: 0, perPage: -5 }),
    );

    expect(result.current.currentPage).toBe(1);
    expect(result.current.data.length).toBeLessThanOrEqual(8);
  });
});
