import SortUtil from "./SortUtil";

interface TestObject {
  active?: boolean;
  createdAt?: Date;
  id?: number;
  name?: string;
  value?: number;
}

interface TestObjectWithString {
  createdAt: string;
}

interface TestObjectWithDate {
  createdAt: Date;
}

describe("SortUtil", () => {
  describe("compareByBoolean", () => {
    test("returns 0 when both values are true", () => {
      const obj1 = { active: true };
      const obj2 = { active: true };
      expect(SortUtil.compareByBoolean(obj1, obj2, "active")).toBe(0);
    });

    test("returns 0 when both values are false", () => {
      const obj1 = { active: false };
      const obj2 = { active: false };
      expect(SortUtil.compareByBoolean(obj1, obj2, "active")).toBe(0);
    });

    test("returns -1 when first value is true and second is false", () => {
      const obj1 = { active: true };
      const obj2 = { active: false };
      expect(SortUtil.compareByBoolean(obj1, obj2, "active")).toBe(-1);
    });

    test("returns 1 when first value is false and second is true", () => {
      const obj1 = { active: false };
      const obj2 = { active: true };
      expect(SortUtil.compareByBoolean(obj1, obj2, "active")).toBe(1);
    });
  });

  describe("compareByNumber", () => {
    test("returns 0 when both objects are undefined", () => {
      expect(
        SortUtil.compareByNumber<TestObject>(undefined, undefined, "value"),
      ).toBe(0);
    });

    test("returns 0 when first object is undefined", () => {
      const obj2: TestObject = { value: 5 };
      expect(
        SortUtil.compareByNumber<TestObject>(undefined, obj2, "value"),
      ).toBe(0);
    });

    test("returns 0 when second object is undefined", () => {
      const obj1: TestObject = { value: 5 };
      expect(
        SortUtil.compareByNumber<TestObject>(obj1, undefined, "value"),
      ).toBe(0);
    });

    test("returns 0 when values are not numbers", () => {
      const obj1 = { value: "not a number" };
      const obj2 = { value: "also not a number" };
      expect(SortUtil.compareByNumber(obj1, obj2, "value")).toBe(0);
    });

    test("returns 1 when first value is greater than second", () => {
      const obj1: TestObject = { value: 10 };
      const obj2: TestObject = { value: 5 };
      expect(SortUtil.compareByNumber(obj1, obj2, "value")).toBe(1);
    });

    test("returns -1 when first value is less than second", () => {
      const obj1: TestObject = { value: 5 };
      const obj2: TestObject = { value: 10 };
      expect(SortUtil.compareByNumber(obj1, obj2, "value")).toBe(-1);
    });

    test("returns -1 when values are equal", () => {
      const obj1: TestObject = { value: 5 };
      const obj2: TestObject = { value: 5 };
      expect(SortUtil.compareByNumber(obj1, obj2, "value")).toBe(-1);
    });
  });

  describe("compareByText", () => {
    test("returns 0 when strings are equal", () => {
      const obj1 = { name: "test" };
      const obj2 = { name: "test" };
      expect(SortUtil.compareByText(obj1, obj2, "name")).toBe(0);
    });

    test("returns negative when first string comes before second alphabetically", () => {
      const obj1 = { name: "apple" };
      const obj2 = { name: "banana" };
      expect(SortUtil.compareByText(obj1, obj2, "name")).toBeLessThan(0);
    });

    test("returns positive when first string comes after second alphabetically", () => {
      const obj1 = { name: "zebra" };
      const obj2 = { name: "apple" };
      expect(SortUtil.compareByText(obj1, obj2, "name")).toBeGreaterThan(0);
    });

    test("handles case insensitive comparison", () => {
      const obj1 = { name: "Apple" };
      const obj2 = { name: "apple" };
      expect(SortUtil.compareByText(obj1, obj2, "name")).toBe(0);
    });

    test("handles numbers as strings", () => {
      const obj1 = { id: 1 };
      const obj2 = { id: 2 };
      expect(SortUtil.compareByText(obj1, obj2, "id")).toBeLessThan(0);
    });
  });

  describe("sortNumeric", () => {
    test("sorts numbers in ascending order", () => {
      const numbers = [3, 1, 4, 1, 5];
      const result = SortUtil.sortNumeric(numbers);
      expect(result).toEqual([1, 1, 3, 4, 5]);
    });

    test("returns empty array when input is empty", () => {
      const result = SortUtil.sortNumeric([]);
      expect(result).toEqual([]);
    });

    test("handles single number", () => {
      const result = SortUtil.sortNumeric([42]);
      expect(result).toEqual([42]);
    });

    test("handles negative numbers", () => {
      const numbers = [-3, -1, -4, -1, -5];
      const result = SortUtil.sortNumeric(numbers);
      expect(result).toEqual([-5, -4, -3, -1, -1]);
    });

    test("handles mixed positive and negative numbers", () => {
      const numbers = [3, -1, 4, -2, 0];
      const result = SortUtil.sortNumeric(numbers);
      expect(result).toEqual([-2, -1, 0, 3, 4]);
    });

    test("returns 0 for null or undefined values", () => {
      // This tests the fallback behavior in the sort function
      const numbers = [null as any, undefined as any, 1, 2];
      const result = SortUtil.sortNumeric(numbers);
      expect(result).toEqual([null, undefined, 1, 2]);
    });

    test("does not mutate original array", () => {
      const numbers = [3, 1, 4];
      const original = [...numbers];
      SortUtil.sortNumeric(numbers);
      expect(numbers).toEqual(original);
    });
  });

  describe("compareByDate", () => {
    test("returns 0 when dates are equal", () => {
      const date = new Date("2023-01-01");
      const obj1: TestObjectWithDate = { createdAt: date };
      const obj2: TestObjectWithDate = { createdAt: date };
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBe(0);
    });

    test("returns negative when first date is before second", () => {
      const obj1: TestObjectWithDate = { createdAt: new Date("2023-01-01") };
      const obj2: TestObjectWithDate = { createdAt: new Date("2023-01-02") };
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBeLessThan(0);
    });

    test("returns positive when first date is after second", () => {
      const obj1: TestObjectWithDate = { createdAt: new Date("2023-01-02") };
      const obj2: TestObjectWithDate = { createdAt: new Date("2023-01-01") };
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBeGreaterThan(
        0,
      );
    });

    test("handles date strings", () => {
      const obj1: TestObjectWithString = { createdAt: "2023-01-01" };
      const obj2: TestObjectWithString = { createdAt: "2023-01-02" };
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBeLessThan(0);
    });

    test("returns -1 when first date is invalid", () => {
      const obj1: TestObjectWithString = { createdAt: "invalid date" };
      const obj2: TestObjectWithString = { createdAt: "2023-01-01" };
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBe(-1);
    });

    test("returns 1 when second date is invalid", () => {
      const obj1: TestObjectWithString = { createdAt: "2023-01-01" };
      const obj2: TestObjectWithString = { createdAt: "invalid date" };
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBe(1);
    });

    test("handles both dates being invalid", () => {
      const obj1: TestObjectWithString = { createdAt: "invalid date 1" };
      const obj2: TestObjectWithString = { createdAt: "invalid date 2" };
      // When both are invalid, first gets -1, so result should be negative
      expect(SortUtil.compareByDate(obj1, obj2, "createdAt")).toBe(-1);
    });
  });
});
