import Factory from "/tests/util/factory.js";
import { importPluginsJSONFile, ReactionTestAPICore } from "@reactioncommerce/api-core";
import insertPrimaryShop from "@reactioncommerce/api-utils/tests/insertPrimaryShop.js";

jest.setTimeout(300000);

const internalShopId = "123";
const opaqueShopId = "cmVhY3Rpb24vc2hvcDoxMjM="; // reaction/shop:123
const shopName = "Test Shop";
const mockTags = Factory.Tag.makeMany(25, {
  _id: (index) => (index + 100).toString(),
  position: (index) => index + 100,
  shopId: internalShopId,
  slug: (index) => `slug${index + 100}`
});

const tagsQuery = `query ($shopId: ID!, $after: ConnectionCursor, $before: ConnectionCursor, $first: ConnectionLimitInt, $last: ConnectionLimitInt) {
  tags(shopId: $shopId, after: $after, before: $before, first: $first, last: $last) {
    totalCount
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
    nodes {
      _id
      position
    }
  }
}`;

let testApp;
let query;
beforeAll(async () => {
  testApp = new ReactionTestAPICore();
  const plugins = await importPluginsJSONFile("../../../../../plugins.json", (pluginList) => {
    // Remove the `files` plugin when testing. Avoids lots of errors.
    delete pluginList.files;

    return pluginList;
  });
  await testApp.reactionNodeApp.registerPlugins(plugins);
  await testApp.start();
  query = testApp.query(tagsQuery);

  const customerGroup = Factory.Group.makeOne({
    _id: "customerGroup",
    createdBy: null,
    name: "customer",
    permissions: ["customer"],
    slug: "customer",
    shopId: internalShopId
  });
  await testApp.collections.Groups.insertOne(customerGroup);

  await testApp.setLoggedInUser({
    _id: "123",
    groups: [customerGroup._id]
  });

  await insertPrimaryShop(testApp.context, { _id: internalShopId, name: shopName });
  await Promise.all(mockTags.map((tag) => testApp.collections.Tags.insertOne(tag)));
});

// There is no need to delete any test data from collections because
// testApp.stop() will drop the entire test database. Each integration
// test file gets its own test database.
afterAll(() => testApp.stop());

test("get the first 20 tags when neither first or last is in query", async () => {
  let result;
  try {
    result = await query({ shopId: opaqueShopId });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(20);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: "MTE5", hasNextPage: true, hasPreviousPage: false, startCursor: "MTAw" });

  try {
    result = await query({ shopId: opaqueShopId, after: result.tags.pageInfo.endCursor });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(5);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: "MTI0", hasNextPage: false, hasPreviousPage: true, startCursor: "MTIw" });

  // Ensure it's also correct when we pass `first: 5` explicitly
  try {
    result = await query({ shopId: opaqueShopId, after: "MTE5", first: 5 });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(5);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: "MTI0", hasNextPage: false, hasPreviousPage: true, startCursor: "MTIw" });
});

test("get the last 10 tags when last is in query and before last item in list", async () => {
  let result;
  try {
    result = await query({ shopId: opaqueShopId, last: 10, before: "MTI0" });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(10);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: "MTIz", hasNextPage: true, hasPreviousPage: true, startCursor: "MTE0" });

  try {
    result = await query({ shopId: opaqueShopId, last: 10, before: result.tags.pageInfo.startCursor });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(10);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: "MTEz", hasNextPage: true, hasPreviousPage: true, startCursor: "MTA0" });

  try {
    result = await query({ shopId: opaqueShopId, last: 4, before: result.tags.pageInfo.startCursor });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(4);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: "MTAz", hasNextPage: true, hasPreviousPage: false, startCursor: "MTAw" });
});

test("works correctly when last goes before start", async () => {
  let result;
  try {
    result = await query({ shopId: opaqueShopId, last: 5, before: "MTAw" });
  } catch (error) {
    expect(error).toBeUndefined();
    return;
  }

  expect(result.tags.nodes.length).toBe(0);
  expect(result.tags.totalCount).toBe(25);
  expect(result.tags.pageInfo).toEqual({ endCursor: null, hasNextPage: true, hasPreviousPage: false, startCursor: null });
});
