import { DataTypes, IdDefaultType, LabelDefaultType } from "../src/DataTypes";

enum GenderEnum {
  Male = "M",
  Female = "F"
}

test("Tests for DI", () => {
  const item: DataTypes.DIS<"id", number> & DataTypes.DIS<"label", string> = {
    id: 1,
    label: "Etsoo"
  };
  expect(item.id).toBe(1);
});

test("Tests for IdLabelType", () => {
  const item: DataTypes.IdLabelItem = {
    id: 1,
    label: "Etsoo"
  };
  const itemCopy: DataTypes.IdLabelType<"id", "label"> = { ...item };
  expect(item).toEqual(itemCopy);
});

test("Tests for Bigint Type", () => {
  type BigintType = {
    id: bigint;
    label: string;
  };

  const item: BigintType = {
    id: 9007199254740999n,
    label: "Etsoo"
  };

  const json = JSON.stringify(item);
  expect(json).toBe('{"id":"9007199254740999n","label":"Etsoo"}');

  const itemBack = JSON.parse(
    json,
    DataTypes.jsonBigintReceiver("id")
  ) as BigintType;
  expect(itemBack.id).toBe(9007199254740999n);
});

test("Tests for convert", () => {
  expect(DataTypes.convert("5", 0)).toStrictEqual(5);
  expect(DataTypes.convert("2021/10/13", new Date())?.getDate()).toStrictEqual(
    13
  );
  expect(DataTypes.convert("a", [])?.length).toStrictEqual(1);
  expect(DataTypes.convert("1", [0])).toStrictEqual([1]);
  expect(DataTypes.convert("", 0)).toBeUndefined();
  expect(DataTypes.convert("false", true)).toBeFalsy();
  expect(DataTypes.convert("1", true)).toBeTruthy();
  expect(DataTypes.convert(true, true)).toBeTruthy();
});

test("Tests for convertByType", () => {
  expect(DataTypes.convertByType("", "number")).toBeUndefined();
  expect(DataTypes.convertByType("", "string")).toBeUndefined();
  expect(DataTypes.convertByType("", "boolean")).toBeUndefined();
  expect(DataTypes.convertByType(["5", 8], "number[]")).toStrictEqual([5, 8]);
  expect(DataTypes.convertByType(["5", 8], "string[]")).toStrictEqual([
    "5",
    "8"
  ]);
  expect(
    DataTypes.convertByType("2021/10/13", "date")?.getDate()
  ).toStrictEqual(13);
});

test("Tests for convertSimple", () => {
  expect(
    DataTypes.convertSimple(5.8, DataTypes.CombinedEnum.Int)
  ).toStrictEqual(6);
  expect(
    DataTypes.convertSimple(5.88293, DataTypes.CombinedEnum.Money)
  ).toStrictEqual(5.8829);
});

test("Tests for getBasicName", () => {
  expect(DataTypes.getBasicName(DataTypes.CombinedEnum.DateTime)).toStrictEqual(
    "date"
  );
});

test("Tests for getEnumByKey", () => {
  expect(DataTypes.getEnumByKey(DataTypes.HAlignEnum, "Center")).toStrictEqual(
    DataTypes.HAlignEnum.Center
  );

  expect(
    DataTypes.getEnumByKey(DataTypes.HAlignEnum, "Unknown")
  ).toBeUndefined();
});

test("Tests for getEnumByValue", () => {
  expect(DataTypes.getEnumByValue(DataTypes.HAlignEnum, 2)).toStrictEqual(
    DataTypes.HAlignEnum.Center
  );

  expect(DataTypes.getEnumByValue(DataTypes.HAlignEnum, 4)).toBeUndefined();
});

test("Tests for getEnumByValue of string value enum", () => {
  expect(DataTypes.getEnumByValue(GenderEnum, "M")).toStrictEqual(
    GenderEnum.Male
  );
  expect(DataTypes.getEnumByValue(GenderEnum, "F")).toStrictEqual(
    GenderEnum.Female
  );
});

test("Tests for getEnumKey", () => {
  expect(
    DataTypes.getEnumKey(DataTypes.HAlignEnum, DataTypes.HAlignEnum.Center)
  ).toBe("Center");
});

test("Tests for getEnumKey of string value enum", () => {
  expect(DataTypes.getEnumKey(GenderEnum, GenderEnum.Female)).toBe("Female");
  expect(DataTypes.getEnumKey(GenderEnum, "None")).toBeUndefined();
});

test("Tests for getEnumKeys", () => {
  enum ProductUnit {
    KILOGRAM = 32,
    GRAM = "GRAM33"
  }
  expect(DataTypes.getEnumKeys(ProductUnit)).toContainEqual("GRAM");

  const keys = DataTypes.getEnumKeys(DataTypes.CombinedEnum);
  expect(keys).toContainEqual("Unkwown");
});

test("Tests for IdLabelItem", () => {
  // Arrange
  const items: DataTypes.IdLabelItem[] = [
    { id: 1, label: "Item 1" },
    { id: 2, label: "Item 2" }
  ];

  // Assert
  expect(items[0].id).toBe(1);
  expect(items[1].label).toBe("Item 2");
});

test("Tests for getValue", () => {
  const data = { id: 1, flag: true };
  expect(DataTypes.getValue(data, "id")).toBe(1);
  expect(DataTypes.getValue(data, "flag")).toBeTruthy();
  expect(DataTypes.getValue(data, "unknown")).toBeUndefined();
});

test("Tests for getIdValue", () => {
  const data = { id: 1, flag: true, field: "string" };
  expect(DataTypes.getIdValue(data, "id")).toBe(1);
  expect(DataTypes.getIdValue(data, "field")).toBe("string");
});

test("Tests for getStringValue", () => {
  const data = { id: 1, flag: true };
  expect(DataTypes.getStringValue(data, "id")).toBe("1");
  expect(DataTypes.getStringValue(data, "flag")).toBe("true");
});

test("Tests for isSimpleType", () => {
  expect(DataTypes.isSimpleType(1)).toBeTruthy();
  expect(DataTypes.isSimpleType(new Date())).toBeTruthy();
  expect(DataTypes.isSimpleType(Symbol())).toBeFalsy();
  expect(DataTypes.isSimpleType(["a", "b", "c"])).toBeTruthy();
  expect(DataTypes.isSimpleType({})).toBeFalsy();
});

test("Tests for IdDefaultType", () => {
  const test = <T extends object, F extends keyof T = IdDefaultType<T>>(
    obj: T,
    field?: F
  ) => {
    const f = field ?? ("id" as F);
    return obj[f];
  };

  type D = { label: string; name: string; id: number };
  const data: D = {
    label: "label",
    name: "name",
    id: 1
  };
  const v = test<D>(data);
  expect(typeof v).toBe("number");

  const v1 = test<D, LabelDefaultType<D>>(data, "label");
  expect(v1).toBe("label");
});

test("Tests for jsonReplacer", () => {
  const obj = { a: 1, b: "hello", c: { c1: "C1", c2: false, c3: 128 } };

  const json1 = JSON.stringify(
    obj,
    DataTypes.jsonReplacer(function (key, value, path) {
      if (["", "a"].includes(key)) {
        return value;
      }
      return undefined;
    })
  );
  expect(json1).toBe('{"a":1}');

  const json2 = JSON.stringify(
    obj,
    DataTypes.jsonReplacer(function (key, value, path) {
      if (["", "c"].includes(key) || path === "c.c2") {
        return value;
      }
      return undefined;
    })
  );
  expect(json2).toBe('{"c":{"c2":false}}');
});

test("Tests for jsonSerialize", () => {
  const obj = { a: 1, b: null, c: { c1: undefined, c2: false, c3: 128 } };
  const json = DataTypes.jsonSerialize(obj);
  expect(json).toBe('{"a":1,"c":{"c2":false,"c3":128}}');
});

test("Tests for jsonSerialize with removing empty values", () => {
  const obj = {
    a: 1,
    b: null,
    c: { c1: undefined, c2: false, c3: 128, c4: "" },
    d: ""
  };
  const json = DataTypes.jsonSerialize(obj, {
    ignoreEmpty: true,
    ignoreNull: false
  });
  expect(json).toBe('{"a":1,"b":null,"c":{"c2":false,"c3":128}}');
});

test("Tests for EditType", () => {
  type AddEntity = {
    name: string;
    age?: number;
  };

  type EditType = DataTypes.EditType<AddEntity>;

  const data1: EditType = { id: 1, name: "hello", changedFields: ["name"] };
  const data2: EditType = { id: 1, name: "hello", age: 1 };

  expect(data1.name).toBe(data2.name);
});

test("Tests for BasicTemplate", () => {
  const template: DataTypes.BasicTemplate = {
    id: "number",
    name: "string",
    birthday: "date"
  };

  const data: { currentPage?: number } & DataTypes.BasicTemplateType<
    typeof template
  > = {
    id: 1,
    name: "Etsoo",
    birthday: new Date()
  };

  expect(data.id).toBe(1);
});

test("Tests for isDeepEqual - Primitive values", () => {
  // Same values
  expect(DataTypes.isDeepEqual(1, 1)).toBeTruthy();
  expect(DataTypes.isDeepEqual("hello", "hello")).toBeTruthy();
  expect(DataTypes.isDeepEqual(true, true)).toBeTruthy();
  expect(DataTypes.isDeepEqual(false, false)).toBeTruthy();

  // Different values
  expect(DataTypes.isDeepEqual(1, 2)).toBeFalsy();
  expect(DataTypes.isDeepEqual("hello", "world")).toBeFalsy();
  expect(DataTypes.isDeepEqual(true, false)).toBeFalsy();

  // Different types
  expect(DataTypes.isDeepEqual(1, "1")).toBeFalsy();
  expect(DataTypes.isDeepEqual(true, 1)).toBeFalsy();
  expect(DataTypes.isDeepEqual(null, undefined)).toBeTruthy(); // Default strict=1
});

test("Tests for isDeepEqual - Null and undefined", () => {
  // strict = undefined (default): null == undefined
  expect(DataTypes.isDeepEqual(null, null)).toBeTruthy();
  expect(DataTypes.isDeepEqual(undefined, undefined)).toBeTruthy();
  expect(DataTypes.isDeepEqual(null, undefined)).toBeTruthy();
  expect(DataTypes.isDeepEqual(undefined, null)).toBeTruthy();

  // strict = 2: null !== undefined
  expect(DataTypes.isDeepEqual(null, null, true)).toBeTruthy();
  expect(DataTypes.isDeepEqual(undefined, undefined, true)).toBeTruthy();
  expect(DataTypes.isDeepEqual(null, undefined, true)).toBeFalsy();
  expect(DataTypes.isDeepEqual(undefined, null, true)).toBeFalsy();

  // With non-null values
  expect(DataTypes.isDeepEqual(null, 0)).toBeFalsy();
  expect(DataTypes.isDeepEqual(undefined, "")).toBeFalsy();
});

test("Tests for isDeepEqual - Loose equality (strict=0)", () => {
  // Loose equal cases
  expect(DataTypes.isDeepEqual(1, "1", false)).toBeTruthy();
  expect(DataTypes.isDeepEqual(0, false, false)).toBeTruthy();
  expect(DataTypes.isDeepEqual(1, true, false)).toBeTruthy();
  expect(DataTypes.isDeepEqual("", false, false)).toBeTruthy();
  expect(DataTypes.isDeepEqual(null, undefined, false)).toBeTruthy();

  // Still not equal
  expect(DataTypes.isDeepEqual(1, 2, false)).toBeFalsy();
  expect(DataTypes.isDeepEqual("hello", "world", false)).toBeFalsy();
});

test("Tests for isDeepEqual - BigInt values", () => {
  expect(DataTypes.isDeepEqual(123n, 123n)).toBeTruthy();
  expect(DataTypes.isDeepEqual(123n, 456n)).toBeFalsy();
  expect(DataTypes.isDeepEqual(123n, 123)).toBeFalsy();
});

test("Tests for isDeepEqual - Date objects", () => {
  const date1 = new Date("2023-01-01");
  const date2 = new Date("2023-01-01");
  const date3 = new Date("2023-01-02");

  expect(DataTypes.isDeepEqual(date1, date1)).toBeTruthy();
  expect(DataTypes.isDeepEqual(date1, date2)).toBeTruthy();
  expect(DataTypes.isDeepEqual(date1, date3)).toBeFalsy();
});

test("Tests for isDeepEqual - Arrays", () => {
  const arr1 = [1, 2, 3];
  expect(DataTypes.isDeepEqual(arr1, arr1)).toBeTruthy();

  // Different references, same content
  expect(DataTypes.isDeepEqual([1, 2, 3], [1, 2, 3])).toBeTruthy();
  expect(DataTypes.isDeepEqual([], [])).toBeTruthy();

  // Different content
  expect(DataTypes.isDeepEqual([1, 2, 3], [1, 2, 4])).toBeFalsy();
  expect(DataTypes.isDeepEqual([1, 2], [1, 2, 3])).toBeFalsy();

  // Nested arrays
  expect(
    DataTypes.isDeepEqual(
      [
        [1, 2],
        [3, 4]
      ],
      [
        [1, 2],
        [3, 4]
      ]
    )
  ).toBeTruthy();
  expect(
    DataTypes.isDeepEqual(
      [
        [1, 2],
        [3, 4]
      ],
      [
        [1, 2],
        [3, 5]
      ]
    )
  ).toBeFalsy();
});

test("Tests for isDeepEqual - Objects", () => {
  // Same reference
  const obj1 = { a: 1, b: 2 };
  expect(DataTypes.isDeepEqual(obj1, obj1)).toBeTruthy();

  // Different references, same content
  expect(DataTypes.isDeepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBeTruthy();
  expect(DataTypes.isDeepEqual({}, {})).toBeTruthy();

  // Different content
  expect(DataTypes.isDeepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBeFalsy();
  expect(DataTypes.isDeepEqual({ a: 1 }, { a: 1, b: 2 })).toBeFalsy();

  expect(
    DataTypes.isDeepEqual({ a: 1, b: "2" }, { a: 1, b: 2 }, false)
  ).toBeTruthy();
  expect(DataTypes.isDeepEqual({ a: 1 }, { a: 1, b: null })).toBeTruthy();
  expect(
    DataTypes.isDeepEqual({ a: 1, c: true }, { a: 1, b: null })
  ).toBeFalsy();
  expect(DataTypes.isDeepEqual({ a: 1 }, { a: 1, b: null }, true)).toBeFalsy();

  // Nested objects
  expect(
    DataTypes.isDeepEqual(
      { a: { x: 1, y: 2 }, b: 3 },
      { a: { x: 1, y: 2 }, b: 3 }
    )
  ).toBeTruthy();

  expect(
    DataTypes.isDeepEqual(
      { a: { x: 1, y: 2 }, b: 3 },
      { a: { x: 1, y: 3 }, b: 3 }
    )
  ).toBeFalsy();
});

test("Tests for isDeepEqual - Mixed complex types", () => {
  // Arrays with objects
  expect(
    DataTypes.isDeepEqual(
      [
        { id: 1, name: "Alice" },
        { id: 2, name: "Bob" }
      ],
      [
        { id: 1, name: "Alice" },
        { id: 2, name: "Bob" }
      ]
    )
  ).toBeTruthy();

  // Objects with arrays
  expect(
    DataTypes.isDeepEqual(
      { users: [1, 2, 3], active: true },
      { users: [1, 2, 3], active: true }
    )
  ).toBeTruthy();

  // Objects with different property order
  expect(DataTypes.isDeepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBeTruthy();
});

test("Tests for isDeepEqual - Edge cases", () => {
  // Functions - should not be equal unless same reference
  const func1 = () => {};
  const func2 = () => {};
  expect(DataTypes.isDeepEqual(func1, func1)).toBeTruthy();
  expect(DataTypes.isDeepEqual(func1, func2)).toBeFalsy();

  // NaN values
  // NaN === NaN is false, but they should be considered equal
  expect(DataTypes.isDeepEqual(NaN, NaN)).toBeTruthy();

  // Infinity
  expect(DataTypes.isDeepEqual(Infinity, Infinity)).toBeTruthy();
  expect(DataTypes.isDeepEqual(-Infinity, -Infinity)).toBeTruthy();
  expect(DataTypes.isDeepEqual(Infinity, -Infinity)).toBeFalsy();

  // Zero variants
  expect(DataTypes.isDeepEqual(0, -0)).toBeTruthy();
  expect(DataTypes.isDeepEqual(+0, -0)).toBeTruthy();
});
