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

// ---------------------------------------------------------------------------
// Mock tRPC client — must be declared before module import so Vitest replaces
// the real module at resolve time.
// ---------------------------------------------------------------------------
const mockUseQuery = vi.fn();

vi.mock("~/lib/trpc/client.js", () => ({
  trpc: {
    timeline: {
      getProjectContext: {
        useQuery: (input: unknown, opts: unknown) => mockUseQuery(input, opts),
      },
    },
  },
}));

const { useProjectDragContext } = await import("./useProjectDragContext.js");

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Build the shape returned by trpc.timeline.getProjectContext.useQuery */
function queryResult(data: unknown) {
  return { data };
}

/** Full project context data shape as returned by the API */
function buildContextData(
  overrides: {
    resourceIds?: string[];
    allResourceAllocations?: unknown[];
    assignments?: unknown[];
    demands?: unknown[];
    project?: unknown;
  } = {},
) {
  return {
    resourceIds: overrides.resourceIds ?? [],
    allResourceAllocations: overrides.allResourceAllocations ?? [],
    assignments: overrides.assignments ?? [],
    demands: overrides.demands ?? [],
    project: overrides.project ?? null,
  };
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("useProjectDragContext", () => {
  beforeEach(() => {
    mockUseQuery.mockReset();
    // Default: query returns no data (loading / disabled state)
    mockUseQuery.mockReturnValue(queryResult(undefined));
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  // -------------------------------------------------------------------------
  // Return shape
  // -------------------------------------------------------------------------
  describe("return shape", () => {
    it("returns an object with all five expected keys", () => {
      const { result } = renderHook(() => useProjectDragContext(null));
      expect(result.current).toHaveProperty("contextResourceIds");
      expect(result.current).toHaveProperty("contextAllocations");
      expect(result.current).toHaveProperty("projectAssignments");
      expect(result.current).toHaveProperty("projectDemands");
      expect(result.current).toHaveProperty("project");
    });

    it("contextResourceIds is an array", () => {
      const { result } = renderHook(() => useProjectDragContext(null));
      expect(Array.isArray(result.current.contextResourceIds)).toBe(true);
    });

    it("contextAllocations is an array", () => {
      const { result } = renderHook(() => useProjectDragContext(null));
      expect(Array.isArray(result.current.contextAllocations)).toBe(true);
    });

    it("projectAssignments is an array", () => {
      const { result } = renderHook(() => useProjectDragContext(null));
      expect(Array.isArray(result.current.projectAssignments)).toBe(true);
    });

    it("projectDemands is an array", () => {
      const { result } = renderHook(() => useProjectDragContext(null));
      expect(Array.isArray(result.current.projectDemands)).toBe(true);
    });
  });

  // -------------------------------------------------------------------------
  // Default / empty state when data is undefined
  // -------------------------------------------------------------------------
  describe("empty / loading state (data is undefined)", () => {
    it("returns empty contextResourceIds when data is undefined", () => {
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.contextResourceIds).toEqual([]);
    });

    it("returns empty contextAllocations when data is undefined", () => {
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.contextAllocations).toEqual([]);
    });

    it("returns empty projectAssignments when data is undefined", () => {
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.projectAssignments).toEqual([]);
    });

    it("returns empty projectDemands when data is undefined", () => {
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.projectDemands).toEqual([]);
    });

    it("returns null project when data is undefined", () => {
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.project).toBeNull();
    });
  });

  // -------------------------------------------------------------------------
  // Populated data is surfaced correctly
  // -------------------------------------------------------------------------
  describe("populated data mapping", () => {
    it("surfaces contextResourceIds from data.resourceIds", () => {
      mockUseQuery.mockReturnValue(
        queryResult(buildContextData({ resourceIds: ["res-1", "res-2"] })),
      );
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.contextResourceIds).toEqual(["res-1", "res-2"]);
    });

    it("surfaces contextAllocations from data.allResourceAllocations", () => {
      const alloc = { id: "alloc-1", hours: 8 };
      mockUseQuery.mockReturnValue(
        queryResult(buildContextData({ allResourceAllocations: [alloc] })),
      );
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.contextAllocations).toEqual([alloc]);
    });

    it("surfaces projectAssignments from data.assignments", () => {
      const assignment = { id: "asgn-1", resourceId: "res-1" };
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ assignments: [assignment] })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.projectAssignments).toEqual([assignment]);
    });

    it("surfaces projectDemands from data.demands", () => {
      const demand = { id: "dem-1", roleId: "role-1" };
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ demands: [demand] })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.projectDemands).toEqual([demand]);
    });

    it("surfaces project from data.project", () => {
      const project = { id: "proj-1", name: "My Project" };
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ project })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.project).toEqual(project);
    });

    it("project is null when data.project is null", () => {
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ project: null })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.project).toBeNull();
    });
  });

  // -------------------------------------------------------------------------
  // Query enabled flag
  // -------------------------------------------------------------------------
  describe("query enabled flag", () => {
    it("passes enabled: false when projectId is null", () => {
      renderHook(() => useProjectDragContext(null));
      const opts = mockUseQuery.mock.calls[0]![1] as { enabled: boolean };
      expect(opts.enabled).toBe(false);
    });

    it("passes enabled: false when projectId is empty string", () => {
      renderHook(() => useProjectDragContext(""));
      const opts = mockUseQuery.mock.calls[0]![1] as { enabled: boolean };
      expect(opts.enabled).toBe(false);
    });

    it("passes enabled: true when projectId is a non-empty string and enabled=true", () => {
      renderHook(() => useProjectDragContext("proj-42", true));
      const opts = mockUseQuery.mock.calls[0]![1] as { enabled: boolean };
      expect(opts.enabled).toBe(true);
    });

    it("passes enabled: false when projectId is set but enabled=false is passed", () => {
      renderHook(() => useProjectDragContext("proj-42", false));
      const opts = mockUseQuery.mock.calls[0]![1] as { enabled: boolean };
      expect(opts.enabled).toBe(false);
    });

    it("defaults enabled to true when the second argument is omitted", () => {
      renderHook(() => useProjectDragContext("proj-99"));
      const opts = mockUseQuery.mock.calls[0]![1] as { enabled: boolean };
      expect(opts.enabled).toBe(true);
    });
  });

  // -------------------------------------------------------------------------
  // Query staleTime
  // -------------------------------------------------------------------------
  describe("query options", () => {
    it("passes staleTime: 10_000 to the query", () => {
      renderHook(() => useProjectDragContext("proj-1"));
      const opts = mockUseQuery.mock.calls[0]![1] as { staleTime: number };
      expect(opts.staleTime).toBe(10_000);
    });

    it("passes the projectId as input to the query", () => {
      renderHook(() => useProjectDragContext("proj-abc"));
      const input = mockUseQuery.mock.calls[0]![0] as { projectId: string };
      expect(input.projectId).toBe("proj-abc");
    });
  });

  // -------------------------------------------------------------------------
  // Multiple resources / large datasets
  // -------------------------------------------------------------------------
  describe("multiple resources", () => {
    it("handles multiple resource IDs correctly", () => {
      const resourceIds = ["r1", "r2", "r3", "r4"];
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ resourceIds })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.contextResourceIds).toHaveLength(4);
      expect(result.current.contextResourceIds).toEqual(resourceIds);
    });

    it("handles multiple assignments correctly", () => {
      const assignments = [
        { id: "a1", resourceId: "r1" },
        { id: "a2", resourceId: "r2" },
      ];
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ assignments })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.projectAssignments).toHaveLength(2);
    });

    it("handles multiple demands correctly", () => {
      const demands = [
        { id: "d1", roleId: "role-1" },
        { id: "d2", roleId: "role-2" },
        { id: "d3", roleId: "role-3" },
      ];
      mockUseQuery.mockReturnValue(queryResult(buildContextData({ demands })));
      const { result } = renderHook(() => useProjectDragContext("proj-1"));
      expect(result.current.projectDemands).toHaveLength(3);
    });
  });

  // -------------------------------------------------------------------------
  // null projectId always produces empty results
  // -------------------------------------------------------------------------
  describe("null projectId always produces empty results", () => {
    it("contextResourceIds is empty for null projectId regardless of mock data", () => {
      // Even if the mock returns data, the hook itself uses null projectId
      mockUseQuery.mockReturnValue(queryResult(undefined));
      const { result } = renderHook(() => useProjectDragContext(null));
      expect(result.current.contextResourceIds).toEqual([]);
      expect(result.current.contextAllocations).toEqual([]);
      expect(result.current.projectAssignments).toEqual([]);
      expect(result.current.projectDemands).toEqual([]);
      expect(result.current.project).toBeNull();
    });
  });
});
