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

// ---------------------------------------------------------------------------
// Mock next/navigation — must be registered before the module under test is
// imported so that the mock factory replaces the real module at resolve time.
// ---------------------------------------------------------------------------
const mockReplace = vi.fn();
let mockSearchParamsEntries: [string, string][] = [];
let mockPathname = "/resources";

vi.mock("next/navigation", () => {
  return {
    useRouter: () => ({ replace: mockReplace }),
    usePathname: () => mockPathname,
    useSearchParams: () => {
      const params = new URLSearchParams(
        mockSearchParamsEntries.map(([k, v]) => `${k}=${v}`).join("&"),
      );
      return params;
    },
  };
});

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

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function setSearchParams(entries: [string, string][]) {
  mockSearchParamsEntries = entries;
}

function lastReplaceUrl(): string {
  const calls = mockReplace.mock.calls;
  return calls[calls.length - 1]![0] as string;
}

function lastReplaceOpts(): unknown {
  const calls = mockReplace.mock.calls;
  return calls[calls.length - 1]![1];
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("useFilters", () => {
  beforeEach(() => {
    mockReplace.mockReset();
    mockSearchParamsEntries = [];
    mockPathname = "/resources";
  });

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

  // -------------------------------------------------------------------------
  // Initial state derivation
  // -------------------------------------------------------------------------
  describe("initial state — no params", () => {
    it("returns empty string for search when not set", () => {
      const { result } = renderHook(() => useFilters());
      expect(result.current.search).toBe("");
    });

    it("returns empty string for chapter when not set", () => {
      const { result } = renderHook(() => useFilters());
      expect(result.current.chapter).toBe("");
    });

    it("returns empty string for status when not set", () => {
      const { result } = renderHook(() => useFilters());
      expect(result.current.status).toBe("");
    });

    it("returns empty array for customFieldFilters when not set", () => {
      const { result } = renderHook(() => useFilters());
      expect(result.current.customFieldFilters).toEqual([]);
    });

    it("hasActiveFilters is false when nothing is set", () => {
      const { result } = renderHook(() => useFilters());
      expect(result.current.hasActiveFilters).toBe(false);
    });
  });

  describe("initial state — params already in URL", () => {
    it("reads search from the search param", () => {
      setSearchParams([["search", "alice"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.search).toBe("alice");
    });

    it("reads chapter from the chapter param", () => {
      setSearchParams([["chapter", "vfx"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.chapter).toBe("vfx");
    });

    it("reads status from the status param", () => {
      setSearchParams([["status", "ACTIVE"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.status).toBe("ACTIVE");
    });

    it("hasActiveFilters is true when search is non-empty", () => {
      setSearchParams([["search", "bob"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.hasActiveFilters).toBe(true);
    });

    it("hasActiveFilters is true when chapter is non-empty", () => {
      setSearchParams([["chapter", "rigging"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.hasActiveFilters).toBe(true);
    });

    it("hasActiveFilters is true when status is non-empty", () => {
      setSearchParams([["status", "DRAFT"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.hasActiveFilters).toBe(true);
    });
  });

  // -------------------------------------------------------------------------
  // customFieldFilters parsing
  // -------------------------------------------------------------------------
  describe("customFieldFilters parsing", () => {
    it("parses a single custom field filter from cf_/cft_ params", () => {
      setSearchParams([
        ["cf_rating", "5"],
        ["cft_rating", FieldType.NUMBER],
      ]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.customFieldFilters).toEqual([
        { key: "rating", value: "5", type: FieldType.NUMBER },
      ]);
    });

    it("defaults type to TEXT when cft_ param is absent", () => {
      setSearchParams([["cf_tag", "hero"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.customFieldFilters).toEqual([
        { key: "tag", value: "hero", type: FieldType.TEXT },
      ]);
    });

    it("parses multiple custom field filters", () => {
      setSearchParams([
        ["cf_dept", "vfx"],
        ["cft_dept", "SELECT"],
        ["cf_level", "senior"],
        ["cft_level", FieldType.TEXT],
      ]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.customFieldFilters).toHaveLength(2);
      const keys = result.current.customFieldFilters.map((f) => f.key);
      expect(keys).toContain("dept");
      expect(keys).toContain("level");
    });

    it("excludes cft_ entries from customFieldFilters (only cf_ entries are rows)", () => {
      setSearchParams([
        ["cf_x", "1"],
        ["cft_x", FieldType.NUMBER],
      ]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.customFieldFilters).toHaveLength(1);
    });

    it("skips custom field filter when the value is empty string", () => {
      setSearchParams([
        ["cf_empty", ""],
        ["cft_empty", FieldType.TEXT],
      ]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.customFieldFilters).toHaveLength(0);
    });

    it("hasActiveFilters is true when customFieldFilters is non-empty", () => {
      setSearchParams([["cf_color", "red"]]);
      const { result } = renderHook(() => useFilters());
      expect(result.current.hasActiveFilters).toBe(true);
    });
  });

  // -------------------------------------------------------------------------
  // setFilter
  // -------------------------------------------------------------------------
  describe("setFilter", () => {
    it("calls router.replace with the updated param", () => {
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setFilter("search", "robot");
      });
      expect(mockReplace).toHaveBeenCalledOnce();
      const url = lastReplaceUrl();
      expect(url).toContain("search=robot");
    });

    it("removes the param when value is empty string", () => {
      setSearchParams([["search", "robot"]]);
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setFilter("search", "");
      });
      const url = lastReplaceUrl();
      expect(url).not.toContain("search=");
    });

    it("preserves existing unrelated params when setting a filter", () => {
      setSearchParams([["chapter", "vfx"]]);
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setFilter("search", "dragon");
      });
      const url = lastReplaceUrl();
      expect(url).toContain("chapter=vfx");
      expect(url).toContain("search=dragon");
    });

    it("calls router.replace with scroll: false", () => {
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setFilter("status", "ACTIVE");
      });
      expect(lastReplaceOpts()).toEqual({ scroll: false });
    });

    it("uses the current pathname in the replace URL", () => {
      mockPathname = "/projects";
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setFilter("search", "x");
      });
      expect(lastReplaceUrl()).toMatch(/^\/projects\?/);
    });
  });

  // -------------------------------------------------------------------------
  // setCustomFieldFilter
  // -------------------------------------------------------------------------
  describe("setCustomFieldFilter", () => {
    it("sets cf_ and cft_ params for a new custom field filter", () => {
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setCustomFieldFilter("score", "10", FieldType.NUMBER);
      });
      const url = lastReplaceUrl();
      expect(url).toContain("cf_score=10");
      expect(url).toContain("cft_score=NUMBER");
    });

    it("removes cf_ and cft_ params when value is empty", () => {
      setSearchParams([
        ["cf_score", "10"],
        ["cft_score", FieldType.NUMBER],
      ]);
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setCustomFieldFilter("score", "", FieldType.NUMBER);
      });
      const url = lastReplaceUrl();
      expect(url).not.toContain("cf_score");
      expect(url).not.toContain("cft_score");
    });

    it("preserves other params when clearing a custom field filter", () => {
      setSearchParams([
        ["search", "alice"],
        ["cf_score", "10"],
        ["cft_score", FieldType.NUMBER],
      ]);
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setCustomFieldFilter("score", "", FieldType.NUMBER);
      });
      const url = lastReplaceUrl();
      expect(url).toContain("search=alice");
    });

    it("calls router.replace with scroll: false", () => {
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.setCustomFieldFilter("tag", "hero", FieldType.TEXT);
      });
      expect(lastReplaceOpts()).toEqual({ scroll: false });
    });
  });

  // -------------------------------------------------------------------------
  // clearFilters
  // -------------------------------------------------------------------------
  describe("clearFilters", () => {
    it("calls router.replace with just the pathname (no query string)", () => {
      setSearchParams([
        ["search", "alice"],
        ["chapter", "vfx"],
      ]);
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.clearFilters();
      });
      expect(lastReplaceUrl()).toBe(mockPathname);
    });

    it("calls router.replace with scroll: false when clearing", () => {
      const { result } = renderHook(() => useFilters());
      act(() => {
        result.current.clearFilters();
      });
      expect(lastReplaceOpts()).toEqual({ scroll: false });
    });
  });
});
