import { EnoFactory } from "./EnoFactory";
import dataConstants from "./constants";
import { IEno } from "./models/types";

describe("Basic functionality - EnoFactory", () => {
  const testFactory = new EnoFactory();

  beforeEach(() => {
    testFactory.reset();
  });

  it("should create an eno", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .setFields([
        { tip: "field tip1", value: ["field value1"] },
        { tip: "field tip2", value: ["field value2"] },
      ]);

    const microEno = testFactory.makeEno();

    expect(microEno.source.type).toBe("type");
    expect(microEno.source.security).toBe("security");
    expect(microEno.tip).toBe(microEno.sid);
    expect(microEno.clientT.branch).toBe(dataConstants.BRANCH_MASTER);
    expect(microEno.clientT.sequence).toBe(1);
    expect(microEno.source.field[0].tip).toBe("field tip1");
    expect(microEno.source.field[0].value).toEqual(["field value1"]);
    expect(microEno.source.field[1].tip).toBe("field tip2");
    expect(microEno.source.field[1].value).toEqual(["field value2"]);
  });

  it("should correctly calculate the sid", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .useEmptyNonce()
      .setFields([
        { tip: "field tip1", value: ["field value1"] },
        { tip: "field tip2", value: ["field value2"] },
      ]);

    const eno = testFactory.makeEno();

    expect(eno.tip).toBe(eno.sid);
    expect(eno.tip).toBe(
      "cc25fab5d2732cbdfc3dbbdcf3c7c45e005cba4fc7b6a6ae10ebe95b2a7c961c"
    );
  });

  it("should create a new eno when the factory is re-used", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: ["value1"] });
    testFactory.setField({ tip: "tip", value: ["value2"] });

    const microEno1 = testFactory.makeEno();
    const microEno2 = testFactory.makeEno();

    expect(microEno1.tip).not.toBe(microEno2.tip);
    expect(microEno1.source.nonce).not.toBe(microEno2.source.nonce);
    expect(microEno2.clientT.sequence).toBe(1);
  });

  it("should patch the previously made eno when the factory is re-used if cleanEno is not called", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: ["value1"] });
    testFactory.setField({ tip: "tip", value: ["value2"] });

    const microEno1 = testFactory.makeEno();
    const microEno1Patched = testFactory.setProtoToPatch(microEno1).makeEno();

    expect(microEno1Patched.tip).toBe(microEno1.tip);
    expect(microEno1Patched.sid).not.toBe(microEno1.sid);
    expect(microEno1Patched.source.parent[0]).toBe(microEno1.sid);
    expect(microEno1Patched.source.nonce).not.toBe(microEno1.source.nonce);
    expect(microEno1Patched.clientT.sequence).toBe(
      microEno1.clientT.sequence + 1
    );
  });

  it("can't set proto to patch from an eno that has source", () => {
    expect(() => {
      testFactory.setProtoToPatch({ tip: "tip", sid: "sid" });
    }).toThrow();
  });

  it("should patch the previously made eno without clientT", () => {
    const patchedEno = testFactory
      .setProtoToPatch({
        tip: "tip",
        sid: "sid",
        source: {
          deleted: true,
          type: "type",
          security: "security",
          nonce: "",
          field: [],
        },
      })
      .makeEno();

    expect(patchedEno.clientT.sequence).toBe(1);
  });

  it("should set a field on the existing field value", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: ["value1"] });
    testFactory.setField({ tip: "tip", value: ["value2"] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].value).toEqual(["value2"]);
  });

  it("should set a field formula on a new field while there are other fields", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField("tip1", ["val1"])
      .setFieldFormula("tip2", "ARRAY()");

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[1].tip).toBe("tip2");
    expect(microEno.source.field[1].value).toBeUndefined();
    expect(microEno.source.field[1].formula).toEqual(["ARRAY()"]);
  });

  it("should set a field formula on the existing field", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: ["value1"] });
    testFactory.setFieldFormula("tip", "ARRAY()");

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].value).not.toEqual(["value2"]);
    expect(microEno.source.field[0].formula).toEqual(["ARRAY()"]);
  });

  it("should set a field by tip and value", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField("tip", ["value1"]);

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].value[0]).toBe("value1");
  });

  it("should set a field by tip and formula", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setFieldFormula("tip", "ARRAY()");

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].formula).toEqual(["ARRAY()"]);
  });

  it("should filter out undefined or null when setting a field", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: ["0", null, undefined] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].value).toEqual(["0"]);
  });

  it("should filter out empty value fields", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: [] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field).toEqual([]);
  });

  it("should normalize the given value", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip1", value: null })
      .setField({ tip: "tip2", value: undefined });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field).toEqual([]);
  });

  it("should normalize the given i18n value", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip1", i18n: [{ lang: "en-us", value: null }] })
      .setField({ tip: "tip2", i18n: [{ lang: "en-us", value: undefined }] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field).toEqual([]);
  });

  it("should filter out empty value fields (i18n)", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({
        tip: "tip",
        i18n: [
          { lang: "en-us", value: [] },
          { lang: "en-gb", value: [] },
        ],
      });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field).toEqual([]);
  });

  it("should set a i18n field on a non-existing field tip", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip1", value: ["val1-1"] });
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setI18nValue("tip2", "en-us", ["val2-1"]);

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0]).toEqual({
      tip: "tip1",
      value: ["val1-1"],
    });
    expect(microEno.source.field[1]).toEqual({
      tip: "tip2",
      i18n: [{ lang: "en-us", value: ["val2-1"] }],
    });
  });

  it("should set a i18n field on an existing field tip and existing lang", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setI18nValue("tip", "en-us", ["val1"]);
    testFactory.setI18nValue("tip", "en-us", ["val2"]);

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0]).toEqual({
      tip: "tip",
      i18n: [{ lang: "en-us", value: ["val2"] }],
    });
  });

  it("should set a i18n field on an existing field tip and non-existing lang", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setI18nValue("tip", "en-us", ["val1"]);
    testFactory.setI18nValue("tip", "en-gb", ["val2"]);

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0]).toEqual({
      tip: "tip",
      i18n: [
        { lang: "en-us", value: ["val1"] },
        { lang: "en-gb", value: ["val2"] },
      ],
    });
  });

  it(
    '#setI18nValue should create "i18n" values and remove legacy "value" values when an i18n value is set and a legacy "value" ' +
      "value exists",
    () => {
      const eno = testFactory
        .setType("type")
        .setSecurity(dataConstants.SECURITY.OP)
        .setField({ tip: "field-1", value: ["value-1"] })
        .setI18nValue("field-1", dataConstants.LANG_DEFAULT, ["value-2"])
        .makeEno();

      expect(eno.source.field[0]).toEqual({
        tip: "field-1",
        i18n: [{ lang: dataConstants.LANG_DEFAULT, value: ["value-2"] }],
      });
    }
  );

  it("#setI18nValue remove the lang entry if [] is passed as value", () => {
    const eno = testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setI18nValue("field-1", "zh", ["value-1"])
      .setI18nValue("field-1", "fr", ["value-2"])
      .setI18nValue("field-1", "fr", [])
      .makeEno();

    expect(eno.source.field[0]).toEqual({
      tip: "field-1",
      i18n: [{ lang: "zh", value: ["value-1"] }],
    });
  });

  it("should filter out undefined or null when setting a i18n field", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({
        tip: "tip",
        i18n: [{ lang: "en-us", value: ["0", null, undefined] }],
      });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].i18n[0].value).toEqual(["0"]);
  });

  it("should set a field on the existing field value", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.OP)
      .setField({ tip: "tip", value: ["value1"] });
    testFactory.setField({ tip: "tip", value: ["value2"] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].value).toEqual(["value2"]);
  });

  it("should create an eno with empty nonce on a certain type", () => {
    testFactory
      .setType("op/query")
      .setSecurity(dataConstants.SECURITY.EVERYONE)
      .setField({ tip: "tip", value: ["value"] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.nonce).toBe("");
  });

  it("should create an eno with empty nonce when told to", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.EVERYONE)
      .setField({ tip: "tip", value: ["value"] });
    testFactory.useEmptyNonce();

    const microEno = testFactory.makeEno();

    expect(microEno.source.nonce).toBe("");
  });

  it("should create an eno with tip nonce when told to", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.EVERYONE)
      .setField({ tip: "tip", value: ["value"] });
    testFactory.setWellKnownTip("test tip").useTipNonce();

    const microEno = testFactory.makeEno();

    expect(microEno.source.nonce).toBe("test tip");
  });

  it("should create an eno with random nonce when told to", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.EVERYONE)
      .setField({ tip: "tip", value: ["value"] });
    testFactory.useEmptyNonce().useRandomNonce();

    const microEno = testFactory.makeEno();

    expect(microEno.source.nonce).not.toBe("");
  });

  it("should normally create an eno with a random nonce", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.EVERYONE)
      .setField({ tip: "tip", value: ["value"] });

    const microEno = testFactory.makeEno();

    expect(microEno.source.nonce).not.toBe("");
  });

  it("should create an eno with known nonce when told to", () => {
    testFactory
      .setType("type")
      .setSecurity(dataConstants.SECURITY.EVERYONE)
      .setField({ tip: "tip", value: ["value"] });
    testFactory.setWellKnownTip("test tip").useKnownNonce('my-nonce');

    const microEno = testFactory.makeEno();

    expect(microEno.source.nonce).toBe("my-nonce");
  });

  it("should create an eno by cloning an existing eno", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .setField({ tip: "tip", value: ["value"] });

    const microEno = testFactory.makeEno();

    testFactory.setProto(microEno);
    const clonedEno = testFactory.makeEno();

    expect(clonedEno.source.field).toEqual(microEno.source.field);
    expect(clonedEno.source.parent).toEqual(microEno.source.parent);
    expect(clonedEno.source.type).toBe(microEno.source.type);
    expect(clonedEno.source.security).toBe(microEno.source.security);
    expect(clonedEno.source.deleted).toBe(microEno.source.deleted);
    expect(clonedEno.clientT).not.toBeNull();
  });

  it("should reset all the fields", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .setField({ tip: "tip", value: ["value"] });
    testFactory.resetFields();

    const microEno = testFactory.makeEno();

    expect(microEno.source.field).toEqual([]);
  });

  it("should empty field when setting a different type", () => {
    testFactory
      .setType("type1")
      .setSecurity("security")
      .setField({ tip: "tip", value: ["value"] })
      .setType("type2");

    const microEno = testFactory.makeEno();

    expect(microEno.source.field).toEqual([]);
  });

  it("should not empty field when setting a same type", () => {
    testFactory
      .setType("type1")
      .setSecurity("security")
      .setField({ tip: "tip", value: ["value"] })
      .setType("type1");

    const microEno = testFactory.makeEno();

    expect(microEno.source.field[0].tip).toBe("tip");
    expect(microEno.source.field[0].value).toEqual(["value"]);
  });

  it("should delete the given eno", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .setField({ tip: "field", value: ["val"] })
      .setDeleted(true);

    const microEno = testFactory.makeEno();

    expect(microEno.source.deleted).toBeTruthy();
    expect(microEno.source.field).toEqual([]);
  });

  it("should set the tip when asked", () => {
    testFactory.setType("type").setSecurity("security").setWellKnownTip("tip");

    const microEno = testFactory.makeEno();

    expect(microEno.tip).toBe("tip");
  });

  it("should set the tip to the sid", () => {
    testFactory.setType("type").setSecurity("security");

    const microEno = testFactory.makeEno();

    expect(microEno.tip).toBe(microEno.sid);
  });

  it("should set a branch", () => {
    testFactory.setType("type").setSecurity("security").setBranch("branch");

    const microEno = testFactory.makeEno();

    expect(microEno.clientT.branch).toBe("branch");
  });

  it("should set a default branch", () => {
    testFactory.setType("type").setSecurity("security").setBranch();

    const microEno = testFactory.makeEno();

    expect(microEno.clientT.branch).toBe(dataConstants.BRANCH_MASTER);
  });

  it("should set a branch on the existing ClientT", () => {
    testFactory.setType("type").setSecurity("security").setBranch("branch1");
    testFactory.setBranch("branch2");

    const microEno = testFactory.makeEno();

    expect(microEno.clientT.branch).toBe("branch2");
  });

  it("should set a sequence", () => {
    testFactory.setType("type").setSecurity("security").setSequence(3);

    const microEno = testFactory.makeEno();

    expect(microEno.clientT.sequence).toBe(3);
  });

  it("should set a sequence when the clientT already exists", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .setSequence(3)
      .setSequence(2);

    const microEno = testFactory.makeEno();

    expect(microEno.clientT.sequence).toBe(2);
  });

  it("should not affect on the previous eno when the factory is reused.", () => {
    testFactory
      .setType("type")
      .setSecurity("security")
      .setField({ tip: "tip", value: ["true"] });

    const microEno1 = testFactory.makeEno();
    const microEno2 = testFactory
      .setProto(microEno1)
      .setField({ tip: "tip", value: ["false"] })
      .makeEno();

    expect(microEno1.getFieldBooleanValue("tip")).toBeTruthy();
    expect(microEno2.getFieldBooleanValue("tip")).toBeFalsy();
  });

  it("should reset useTipNonce state when factory is reset", () => {
    testFactory.setType("type").setSecurity("security").setWellKnownTip("test-tip").useTipNonce();

    const eno1 = testFactory.makeEno();

    testFactory.reset("type", "security");
    const eno2 = testFactory.setProtoToPatch(eno1).makeEno();

    // First eno should use tip nonce, second should use random nonce (default nonce)
    expect(eno1.source.nonce).toBe("test-tip");
    expect(eno2.source.nonce).not.toBe("test-tip");
    expect(eno2.source.nonce).not.toBe("");
  });

  it("should reset useKnownNonce state when factory is reset", () => {
    testFactory.setType("type").setSecurity("security").useKnownNonce("known-nonce");

    const eno1 = testFactory.makeEno();

    testFactory.reset("type", "security");
    const eno2 = testFactory.makeEno();

    // First eno should use known nonce, second should use random nonce (default nonce)
    expect(eno1.source.nonce).toBe("known-nonce");
    expect(eno2.source.nonce).not.toBe("known-nonce");
    expect(eno2.source.nonce).not.toBe("");
  });

  it("should reset useKnownNonce state when useRandomNonce is called", () => {
    testFactory.setType("type").setSecurity("security").useKnownNonce("known-nonce");

    const eno1 = testFactory.makeEno();

    testFactory.useRandomNonce();
    const eno2 = testFactory.makeEno();

    // First eno should use known nonce, second should use random nonce
    expect(eno1.source.nonce).toBe("known-nonce");
    expect(eno2.source.nonce).not.toBe("known-nonce");
    expect(eno2.source.nonce).not.toBe("");
  });

  it("should throw an error when proto information is not sufficient", () => {
    expect(function () {
      testFactory.makeEno();
    }).toThrow();
  });
});

describe("With type and security on constructor - EnoFactory", () => {
  const testFactory = new EnoFactory("type", "security");

  it("should create an eno", () => {
    const microEno = testFactory.makeEno();

    expect(microEno.source.type).toBe("type");
    expect(microEno.source.security).toBe("security");
    expect(microEno.tip).toBe(microEno.sid);
    expect(microEno.clientT.branch).toBe(dataConstants.BRANCH_MASTER);
    expect(microEno.clientT.sequence).toBe(1);
  });
});

describe("With initial eno given - EnoFactory", () => {
  let tempFactory = null;

  beforeEach(() => {
    tempFactory = new EnoFactory("type", "security");
  });

  it("should create an eno", () => {
    const eno = tempFactory
      .setField({ tip: "field", value: ["val"] })
      .makeEno();
    const testFactory = new EnoFactory(eno);
    const microEno = testFactory.makeEno();

    expect(microEno.source.type).toBe("type");
    expect(microEno.source.security).toBe("security");
    expect(microEno.tip).toBe(microEno.sid);
    expect(microEno.clientT.branch).toBe(dataConstants.BRANCH_MASTER);
    expect(microEno.clientT.sequence).toBe(1);
    expect(microEno.source.field[0].tip).toBe("field");
    expect(microEno.source.field[0].value).toEqual(["val"]);
  });

  it("should create an eno (empty source and empty clientT)", () => {
    const eno: IEno = tempFactory.makeEno().toJson();
    eno.clientT = null;
    eno.source = null;
    const testFactory = new EnoFactory(eno);
    const microEno = testFactory
      .setType("type")
      .setSecurity("security")
      .makeEno();

    expect(microEno.clientT).not.toBeNull();
    expect(microEno.source.field).toEqual([]);
  });
});
