import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import type { ColumnDef, ColumnPreferences } from "@nexus/shared";

// ---------------------------------------------------------------------------
// Mock the tRPC client so the hook can be tested without a real server
// ---------------------------------------------------------------------------
const mockGetColumnPreferences = vi.fn();
const mockSetColumnPreferences = vi.fn();

vi.mock("~/lib/trpc/client.js", () => ({
  trpc: {
    user: {
      getColumnPreferences: {
        useQuery: () => ({
          data: mockGetColumnPreferences(),
        }),
      },
      setColumnPreferences: {
        useMutation: () => ({
          mutate: mockSetColumnPreferences,
        }),
      },
    },
  },
}));

// Import after the mock is registered
const { useColumnConfig } = await import("./useColumnConfig.js");

// ---------------------------------------------------------------------------
// localStorage stub
// ---------------------------------------------------------------------------
function createLocalStorageStub() {
  let store: Record<string, string> = {};
  return {
    getItem: vi.fn((key: string) => store[key] ?? null),
    setItem: vi.fn((key: string, value: string) => {
      store[key] = value;
    }),
    removeItem: vi.fn((key: string) => {
      delete store[key];
    }),
    clear: vi.fn(() => {
      store = {};
    }),
    _store: () => store,
  };
}

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
const builtinColumns: ColumnDef[] = [
  { key: "name", label: "Name", defaultVisible: true, hideable: false },
  { key: "age", label: "Age", defaultVisible: true, hideable: true },
  { key: "status", label: "Status", defaultVisible: false, hideable: true },
];

const customColumns: ColumnDef[] = [
  { key: "custom1", label: "Custom 1", defaultVisible: true, hideable: true, isCustom: true },
  { key: "custom2", label: "Custom 2", defaultVisible: false, hideable: true, isCustom: true },
];

describe("useColumnConfig", () => {
  let localStorageStub: ReturnType<typeof createLocalStorageStub>;

  beforeEach(() => {
    localStorageStub = createLocalStorageStub();
    vi.spyOn(window, "localStorage", "get").mockReturnValue(localStorageStub as unknown as Storage);
    // Default: no server preferences
    mockGetColumnPreferences.mockReturnValue(undefined);
    mockSetColumnPreferences.mockReset();
  });

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

  // -------------------------------------------------------------------------
  // allColumns
  // -------------------------------------------------------------------------
  describe("allColumns", () => {
    it("combines builtinColumns and customColumns", () => {
      const { result } = renderHook(() =>
        useColumnConfig("resources", builtinColumns, customColumns),
      );
      expect(result.current.allColumns).toHaveLength(builtinColumns.length + customColumns.length);
    });

    it("returns only builtinColumns when customColumns is omitted", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));
      expect(result.current.allColumns).toEqual(builtinColumns);
    });

    it("puts builtinColumns before customColumns", () => {
      const { result } = renderHook(() =>
        useColumnConfig("resources", builtinColumns, customColumns),
      );
      const keys = result.current.allColumns.map((c) => c.key);
      expect(keys).toEqual(["name", "age", "status", "custom1", "custom2"]);
    });
  });

  // -------------------------------------------------------------------------
  // visibleKeys — default fallback (no localStorage, no server prefs)
  // -------------------------------------------------------------------------
  describe("visibleKeys — defaults", () => {
    it("includes columns with defaultVisible=true", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));
      expect(result.current.visibleKeys).toContain("name");
      expect(result.current.visibleKeys).toContain("age");
    });

    it("excludes columns with defaultVisible=false", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));
      expect(result.current.visibleKeys).not.toContain("status");
    });

    it("always includes non-hideable columns when a saved list is present", () => {
      // alwaysVisible is only force-injected when merging with a saved key list.
      // When falling back to defaults, only columns with defaultVisible=true are shown.
      const cols: ColumnDef[] = [
        { key: "id", label: "ID", defaultVisible: true, hideable: false },
        { key: "opt", label: "Opt", defaultVisible: false, hideable: true },
        { key: "extra", label: "Extra", defaultVisible: false, hideable: true },
      ];
      // Provide a saved list that omits "id" — the hook must still inject it
      localStorageStub.getItem.mockReturnValue(JSON.stringify(["opt"]));
      const { result } = renderHook(() => useColumnConfig("resources", cols));
      expect(result.current.visibleKeys).toContain("id");
      expect(result.current.visibleKeys).toContain("opt");
      expect(result.current.visibleKeys).not.toContain("extra");
    });

    it("shows only defaultVisible=true columns when no saved list exists", () => {
      const cols: ColumnDef[] = [
        { key: "id", label: "ID", defaultVisible: false, hideable: false },
        { key: "opt", label: "Opt", defaultVisible: false, hideable: true },
      ];
      const { result } = renderHook(() => useColumnConfig("resources", cols));
      // No saved list → falls back to defaultVisible; neither column qualifies
      expect(result.current.visibleKeys).toHaveLength(0);
    });
  });

  // -------------------------------------------------------------------------
  // visibleKeys — localStorage takes priority
  // -------------------------------------------------------------------------
  describe("visibleKeys — localStorage persistence", () => {
    it("uses the saved keys from localStorage on mount", () => {
      localStorageStub.getItem.mockReturnValue(JSON.stringify(["status"]));

      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      // "name" is non-hideable so it must always appear; "status" is from localStorage
      expect(result.current.visibleKeys).toContain("name");
      expect(result.current.visibleKeys).toContain("status");
    });

    it("filters out stale keys that no longer exist in allColumns", () => {
      localStorageStub.getItem.mockReturnValue(JSON.stringify(["age", "obsoleteKey"]));

      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      expect(result.current.visibleKeys).not.toContain("obsoleteKey");
      expect(result.current.visibleKeys).toContain("age");
    });
  });

  // -------------------------------------------------------------------------
  // visibleKeys — server prefs fallback
  // -------------------------------------------------------------------------
  describe("visibleKeys — server preferences fallback", () => {
    it("uses server preferences when localStorage is empty", () => {
      const serverPrefs: ColumnPreferences = {
        resources: { visible: ["status", "age"] },
      };
      mockGetColumnPreferences.mockReturnValue(serverPrefs);

      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      expect(result.current.visibleKeys).toContain("status");
      expect(result.current.visibleKeys).toContain("age");
      // non-hideable always present
      expect(result.current.visibleKeys).toContain("name");
    });
  });

  // -------------------------------------------------------------------------
  // visibleColumns
  // -------------------------------------------------------------------------
  describe("visibleColumns", () => {
    it("returns only columns whose keys are in visibleKeys", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));
      const visibleSet = new Set(result.current.visibleKeys);
      for (const col of result.current.visibleColumns) {
        expect(visibleSet.has(col.key)).toBe(true);
      }
    });

    it("orders visibleColumns to match the order of visibleKeys", () => {
      localStorageStub.getItem.mockReturnValue(JSON.stringify(["age", "name"]));

      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      const colKeys = result.current.visibleColumns.map((c) => c.key);
      // "name" is non-hideable and always inserted first in visibleKeys logic,
      // then "age" from localStorage; ordering must follow visibleKeys
      expect(colKeys.indexOf("name")).toBeLessThan(colKeys.indexOf("age"));
    });
  });

  // -------------------------------------------------------------------------
  // setVisible
  // -------------------------------------------------------------------------
  describe("setVisible", () => {
    it("updates visibleKeys immediately (optimistic)", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      act(() => {
        result.current.setVisible(["name", "status"]);
      });

      expect(result.current.visibleKeys).toContain("status");
    });

    it("persists the new visible keys to localStorage", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      act(() => {
        result.current.setVisible(["name", "age"]);
      });

      expect(localStorageStub.setItem).toHaveBeenCalledWith(
        "colvis_resources",
        JSON.stringify(["name", "age"]),
      );
    });

    it("calls the server mutation with the correct payload", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      act(() => {
        result.current.setVisible(["name", "age"]);
      });

      expect(mockSetColumnPreferences).toHaveBeenCalledWith({
        view: "resources",
        visible: ["name", "age"],
      });
    });

    it("reflects the new keys in visibleColumns after setVisible", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      act(() => {
        result.current.setVisible(["name", "status"]);
      });

      const keys = result.current.visibleColumns.map((c) => c.key);
      expect(keys).toContain("status");
      expect(keys).not.toContain("age");
    });

    it("can hide all hideable columns", () => {
      const { result } = renderHook(() => useColumnConfig("resources", builtinColumns));

      act(() => {
        // Only "name" (non-hideable) in the list
        result.current.setVisible(["name"]);
      });

      expect(result.current.visibleKeys).toEqual(["name"]);
    });
  });

  // -------------------------------------------------------------------------
  // ViewKey variation
  // -------------------------------------------------------------------------
  describe("view isolation", () => {
    it("reads from the correct localStorage key for the given view", () => {
      renderHook(() => useColumnConfig("projects", builtinColumns));
      expect(localStorageStub.getItem).toHaveBeenCalledWith("colvis_projects");
    });

    it("writes to the correct localStorage key for the given view", () => {
      const { result } = renderHook(() => useColumnConfig("projects", builtinColumns));

      act(() => {
        result.current.setVisible(["name"]);
      });

      expect(localStorageStub.setItem).toHaveBeenCalledWith("colvis_projects", expect.any(String));
    });
  });
});
