import * as sinon from "sinon";
import * as Logger from "../../../Common/Logger";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { GremlinClient, GremlinClientParameters } from "./GremlinClient";

describe("Gremlin Client", () => {
  const emptyParams: GremlinClientParameters = {
    endpoint: null,
    collectionId: null,
    databaseId: null,
    maxResultSize: 10000,
    password: null,
  };

  it("should use databaseId, collectionId and password to authenticate", () => {
    const collectionId = "collectionId";
    const databaseId = "databaseId";
    const testPassword = "password";
    const gremlinClient = new GremlinClient();

    gremlinClient.initialize({
      endpoint: null,
      collectionId,
      databaseId,
      maxResultSize: 0,
      password: testPassword,
    });

    // User must includes these values
    expect(gremlinClient.client.params.user.indexOf(collectionId)).not.toBe(-1);
    expect(gremlinClient.client.params.user.indexOf(databaseId)).not.toBe(-1);
    expect(gremlinClient.client.params.password).toEqual(testPassword);
  });

  it("should aggregate RU charges across multiple responses", (done) => {
    const gremlinClient = new GremlinClient();
    const ru1 = 1;
    const ru2 = 2;
    const ru3 = 3;
    const requestId = "id";
    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId);
    gremlinClient
      .execute("fake query")
      .then((result) => expect(result.totalRequestCharge).toBe(ru1 + ru2 + ru3))
      .finally(done);

    gremlinClient.client.params.progressCallback({
      data: ["data1"],
      requestCharge: ru1,
      requestId: requestId,
    });
    gremlinClient.client.params.progressCallback({
      data: ["data2"],
      requestCharge: ru2,
      requestId: requestId,
    });
    gremlinClient.client.params.successCallback({
      data: ["data3"],
      requestCharge: ru3,
      requestId: requestId,
    });
  });

  it("should keep track of pending requests", () => {
    const gremlinClient = new GremlinClient();
    const fakeRequestIds = ["id1", "id2", "id3"];
    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => fakeRequestIds.pop());
    gremlinClient.execute("fake query");
    gremlinClient.execute("fake query");
    gremlinClient.execute("fake query");
    expect(gremlinClient.pendingResults.size).toBe(3);
  });

  it("should clean up pending request ids after success", async () => {
    const gremlinClient = new GremlinClient();
    const ru1 = 1;
    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => {
      const requestId = "id";
      setTimeout(() => {
        gremlinClient.client.params.successCallback({
          data: ["data1"],
          requestCharge: ru1,
          requestId: requestId,
        });
      }, 0);
      return requestId;
    });
    await gremlinClient.execute("fake query");
    expect(gremlinClient.pendingResults.size).toBe(0);
  });

  it("should log and display error out on unknown requestId", () => {
    const gremlinClient = new GremlinClient();
    const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
    const logErrorSpy = sinon.spy(Logger, "logError");

    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => "requestId");
    gremlinClient.execute("fake query");
    gremlinClient.client.params.successCallback({
      data: ["data1"],
      requestCharge: 1,
      requestId: "unknownId",
    });

    expect(logConsoleSpy.called).toBe(true);
    expect(logErrorSpy.called).toBe(true);

    logConsoleSpy.restore();
    logErrorSpy.restore();
  });

  it("should not display RU if null or undefined", () => {
    const emptyResult = "";
    expect(GremlinClient.getRequestChargeString(null)).toEqual(emptyResult);
    expect(GremlinClient.getRequestChargeString(undefined)).toEqual(emptyResult);
    expect(GremlinClient.getRequestChargeString(123)).not.toEqual(emptyResult);
    expect(GremlinClient.getRequestChargeString("123")).not.toEqual(emptyResult);
  });

  it("should not aggregate RU if not a number and reset totalRequestCharge to undefined", (done) => {
    const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
    const logErrorSpy = sinon.spy(Logger, "logError");

    const gremlinClient = new GremlinClient();
    const ru1 = 123;
    const ru2 = "should be a number";
    const requestId = "id";

    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId);
    gremlinClient
      .execute("fake query")
      .then(
        (result) => {
          try {
            expect(result.totalRequestCharge).toBe(undefined);
            expect(logConsoleSpy.called).toBe(true);
            expect(logErrorSpy.called).toBe(true);
            done();
          } catch (e) {
            done(e);
          }
        },
        (error) => done.fail(error),
      )
      .finally(() => {
        logConsoleSpy.restore();
        logErrorSpy.restore();
      });

    gremlinClient.client.params.progressCallback({
      data: ["data1"],
      requestCharge: ru1,
      requestId: requestId,
    });
    gremlinClient.client.params.successCallback({
      data: ["data2"],
      requestCharge: ru2 as any,
      requestId: requestId,
    });
  });

  it("should not aggregate RU if undefined and reset totalRequestCharge to undefined", (done) => {
    const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
    const logErrorSpy = sinon.spy(Logger, "logError");

    const gremlinClient = new GremlinClient();
    const ru1 = 123;
    const ru2: number = undefined;
    const requestId = "id";

    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId);
    gremlinClient
      .execute("fake query")
      .then(
        (result) => {
          try {
            expect(result.totalRequestCharge).toBe(undefined);
            expect(logConsoleSpy.called).toBe(true);
            expect(logErrorSpy.called).toBe(true);
            done();
          } catch (e) {
            done(e);
          }
        },
        (error) => done.fail(error),
      )
      .finally(() => {
        logConsoleSpy.restore();
        logErrorSpy.restore();
      });

    gremlinClient.client.params.progressCallback({
      data: ["data1"],
      requestCharge: ru1,
      requestId: requestId,
    });
    gremlinClient.client.params.successCallback({
      data: ["data2"],
      requestCharge: ru2,
      requestId: requestId,
    });
  });

  it("should track RUs even on failure", (done) => {
    const gremlinClient = new GremlinClient();
    const requestId = "id";
    const RU = 1234;
    const error = "Some error";

    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId);
    const abortPendingRequestSpy = sinon.spy(gremlinClient, "abortPendingRequest");
    gremlinClient.execute("fake query").then(
      (result) => done.fail(`Unexpectedly succeeded with ${result}`),
      (error) => {
        try {
          expect(abortPendingRequestSpy.calledWith(requestId, error, RU)).toBe(true);
          done();
        } catch (e) {
          done(e);
        }
      },
    );

    gremlinClient.client.params.failureCallback(
      {
        data: null,
        requestCharge: RU,
        requestId: requestId,
      },
      error,
    );
  });

  it("should abort all pending requests if requestId from failure response", (done) => {
    const gremlinClient = new GremlinClient();
    const requestId = "id";
    const error = "Some error";

    gremlinClient.initialize(emptyParams);
    sinon.stub(gremlinClient.client, "executeGremlinQuery").callsFake((query: string): string => requestId);
    gremlinClient.execute("fake query").finally(() => {
      try {
        expect(gremlinClient.pendingResults.size).toBe(0);
        done();
      } catch (e) {
        done(e);
      }
    });

    gremlinClient.client.params.failureCallback(
      {
        data: null,
        requestCharge: undefined,
        requestId: undefined,
      },
      error,
    );
  });
});
