import { EnSrv } from "./ensrv";
import { IEnSrvOptions } from "./IEnSrvOptions";
import { EnoFactory } from "./EnoFactory";
import { Eno } from "./models/Eno";
import { IQueryOption } from "./query";
import { firstValueFrom, of } from "rxjs";

describe("EnSrv", () => {
  let enSrvOptions: IEnSrvOptions;
  let enSrv: EnSrv;

  beforeEach(() => {
    enSrvOptions = {
      enSrvUrl: "https://services.dev.elasticnoggin.com/ensrv/",
      namespace: "my-namespace",
    };
    enSrv = new EnSrv(enSrvOptions);
  });

  it("Should read an eno", async () => {
    const enoFactory = new EnoFactory("my-type", "my-security");
    const eno = enoFactory.makeEno();
    const readSpy = spyOn(enSrv.fn, "read").and.returnValue(of(eno));
    const result = await firstValueFrom(
      enSrv.read("my-tip", { branch: "my-branch" }, { bulk: true })
    );
    expect(readSpy).toHaveBeenCalledWith(
      "my-tip",
      {
        enSrvUrl: "https://services.dev.elasticnoggin.com/ensrv/",
        namespace: "my-namespace",
        bulk: true,
      },
      { branch: "my-branch" }
    );
    expect(result).toEqual(eno);
  });

  it("Should send a batch", async () => {
    const enoFactory = new EnoFactory("my-type", "my-security");
    const eno = enoFactory.makeEno();
    const sendSpy = spyOn(enSrv.fn, "send").and.returnValue(of([eno]));
    const result = await firstValueFrom(enSrv.send([eno], { bulk: true }));
    expect(sendSpy).toHaveBeenCalledWith([eno], {
      enSrvUrl: "https://services.dev.elasticnoggin.com/ensrv/",
      namespace: "my-namespace",
      bulk: true,
    });
    expect(result).toEqual([eno]);
  });

  it("Should start a process", async () => {
    const processResponse = {
      operationTip: "my-response-op-tip",
      responseTip: "my-response-tip",
      isFinished: true,
    };
    const startProcessSpy = spyOn(enSrv.fn, "startProcess").and.returnValue(
      of(processResponse)
    );
    const result = await firstValueFrom(
      enSrv.startProcess(
        "my-process-tip",
        { waitForFinish: true },
        { bulk: true }
      )
    );
    expect(startProcessSpy).toHaveBeenCalledWith(
      "my-process-tip",
      {
        enSrvUrl: "https://services.dev.elasticnoggin.com/ensrv/",
        namespace: "my-namespace",
        bulk: true,
      },
      { waitForFinish: true }
    );
    expect(result).toEqual(processResponse);
  });

  it("Should pull a batch", async () => {
    const enoFactory = new EnoFactory("my-type", "my-security");
    const eno = enoFactory.makeEno();
    const pullSpy = spyOn(enSrv.fn, "pull").and.returnValue(of([eno]));
    const result = await firstValueFrom(
      enSrv.pull(["my-tip"], { branch: "my-branch" }, { bulk: true })
    );
    expect(pullSpy).toHaveBeenCalledWith(
      ["my-tip"],
      {
        enSrvUrl: "https://services.dev.elasticnoggin.com/ensrv/",
        namespace: "my-namespace",
        bulk: true,
      },
      { branch: "my-branch" }
    );
    expect(result).toEqual([eno]);
  });

  it("Should mutate the sessionToken in options", async () => {
    const processResponse = {
      operationTip: "my-response-op-tip",
      responseTip: "my-response-tip",
      isFinished: true,
    };
    spyOn(enSrv.fn, "startProcess").and.callFake(
      (processTip, myEnSrvOptions, processOptions) => {
        myEnSrvOptions.sessionToken = "my-new-session-token";
        return of(processResponse);
      }
    );
    await firstValueFrom(
      enSrv.startProcess(
        "my-process-tip",
        { waitForFinish: true },
        { bulk: true }
      )
    );
    expect(enSrvOptions.sessionToken).toBe("my-new-session-token");
  });
});

xdescribe("End-to-end EnSrv tests", () => {
  let enSrvOptions: IEnSrvOptions;

  beforeEach(() => {
    enSrvOptions = {
      enSrvUrl: "https://services.dev.elasticnoggin.com/ensrv/",
      namespace:
      "29686d9379c22c17d177cafc364a8b5a5774407b3bfd3b142a59b57a88e16e0",
      sessionToken: "<enter token here>",
    };
  });

  it("Should read an eno", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    const eno = await firstValueFrom(enSrv.read("root"));
    expect(eno.tip).toBe("root");
  });

  it("Should error when read not found", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    try {
      await firstValueFrom(enSrv.read("does-not-exist"));
      fail();
    } catch (err) {
      const errEno = new Eno(JSON.parse(err.message));
      expect(errEno.getFieldStringValue("error/message/tip")).toBe(
        "error/message/eno/not-found"
      );
    }
  });

  it("Should error when read not permitted", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    try {
      await firstValueFrom(enSrv.read("test/user/1"));
      fail();
    } catch (err) {
      const errEno = new Eno(JSON.parse(err.message));
      expect(errEno.getFieldStringValue("error/message/tip")).toBe(
        "error/message/security/access-denied"
      );
    }
  });

  it("Should error when write not permitted", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    const oldEno = await firstValueFrom(enSrv.read("root"));
    const enoFactory = new EnoFactory();
    enoFactory.setProtoToPatch(oldEno);
    enoFactory.setSecurity("security/policy/op");
    const newEno = enoFactory.makeEno();

    try {
      await firstValueFrom(enSrv.write(newEno));
      fail();
    } catch (err) {
      const errEno = new Eno(JSON.parse(err.message));
      expect(errEno.getFieldStringValue("error/message/tip")).toBe(
        "error/message/security/access-denied"
      );
    }
  });

  it("Should error when process does not exist", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    try {
      await firstValueFrom(enSrv.startProcess("does-not-exist"));
      fail();
    } catch (err) {
      const errEno = new Eno(JSON.parse(err.message));
      expect(errEno.getFieldStringValue("error/message/tip")).toBe(
        "error/message/reference/not-found"
      );
    }
  });

  it("Should evaluate a formula", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    const formulaOptions = {
      context: "root",
      vars: { myVar: "alpha" },
    };
    const results = await firstValueFrom(
      enSrv.evalFormula('ARRAY(TIP(),VAR("myVar"))', formulaOptions)
    );
    expect(results).toEqual(["root", "alpha"]);
  });

  it("Should error when formula invalid", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    const results = await firstValueFrom(enSrv.evalFormula("bad-syntax"));
    expect(results).toEqual(["bad-syntax"]);
  });

  it("Should get locale", async () => {
    const enSrv = new EnSrv(enSrvOptions);
    const results = await firstValueFrom(enSrv.locale());
    expect(results).toEqual({
      lang: "en-us",
      timezone: "Australia/Sydney",
      dateFormat: "DD MMM YYYY",
      datetimeFormat: "DD MMM YYYY HH:mm",
    });
  });

  it("Should execute a query", async () => {
    const enSrv = new EnSrv(enSrvOptions);

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [
        { label: "runtimeFilter1", formula: "EQUALS(TIP(), TIP())" },
      ],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
    };

    const results = await firstValueFrom(
      enSrv.executeQuery("eim/query/get-all-modules", queryOptions)
    );
    expect(results).toEqual({
      attributes: [
        {
          tip: "eim/query/attr/tip",
          label: "$tip",
          formula: "TIP()",
        },
        {
          label: "$tip",
          formula: "TIP()",
        },
        {
          label: "name",
          formula: 'FIELD("app/module:name")',
        },
        {
          label: "description",
          formula: 'FIELD("app/module:description")',
        },
        {
          label: "solutionNames",
          formula:
            'FIELD_VALUES("app/solution:name", FIELD("app/module:solution"))',
        },
        {
          label: "solutionColors",
          formula:
            'FIELD_VALUES("app/solution:color", FIELD("app/module:solution"))',
        },
        {
          label: "runtimeAttr1",
          formula: "TIP()",
        },
      ],
      dimensions: [
        {
          label: "runtimeDim1",
          values: [jasmine.stringMatching(/.+/)],
        },
      ],
      results: jasmine.any(Array),
      execTime: jasmine.any(Number),
    });
  });

  it("Should execute a 1d query", async () => {
    const enSrv = new EnSrv(enSrvOptions);

    const queryOptions: IQueryOption = {
      extraAttributes: [{ label: "runtimeAttr1", formula: "TIP()" }],
      extraFilters: [
        { label: "runtimeFilter1", formula: "EQUALS(TIP(), TIP())" },
      ],
      vars: {
        varKey1: ["varVal1a", "varVal1b"],
        varKey2: ["varVal2a", "varVal2b"],
      },
      dimensionOptions: [
        {
          label: "runtimeDim1",
          formula: "TIP()",
          sortby: ["TITLE()"],
          sortdir: ["asc"],
          limit: 1,
        },
      ],
    };

    const results = await firstValueFrom(
      enSrv.executeQuery1d("eim/query/get-all-modules", queryOptions)
    );

    expect(results).toEqual(jasmine.any(Array));
  });
});
