'use strict';

const { prop, difference, map, uniq } = require('lodash/fp');
const { createAuthRequest } = require('../../../../../../test/helpers/request');
const { createStrapiInstance } = require('../../../../../../test/helpers/strapi');
const { createTestBuilder } = require('../../../../../../test/helpers/builder');

const toIds = arr => uniq(map(prop('id'))(arr));

let strapi;
let rq;
const builder = createTestBuilder();

const data = {
  product: [],
  category: [],
  shop: [],
};

const productModel = {
  attributes: {
    name: {
      type: 'string',
      unique: true,
    },
    categories: {
      type: 'relation',
      relation: 'oneToMany',
      target: 'api::category.category',
      targetAttribute: 'product',
    },
    shops: {
      type: 'relation',
      relation: 'oneToMany',
      target: 'api::shop.shop',
    },
  },
  displayName: 'Product',
  singularName: 'product',
  pluralName: 'products',
};

const categoryModel = {
  attributes: {
    name: {
      type: 'string',
      unique: true,
    },
  },
  displayName: 'Category',
  singularName: 'category',
  pluralName: 'categories',
};

const shopModel = {
  attributes: {
    name: {
      type: 'string',
      unique: true,
    },
    metadata: {
      type: 'string',
    },
  },
  displayName: 'Shop',
  singularName: 'shop',
  pluralName: 'shops',
};

const PRODUCT_SHOP_COUNT = 12;
const PRODUCT_CATEGORY_COUNT = 5;
const fixtures = {
  shop: [
    { name: 'SH.A', metadata: 'foobar' },
    { name: 'SH.B', metadata: 'foobar' },
    { name: 'SH.C', metadata: 'foobar' },
    { name: 'SH.D', metadata: 'foobar' },
    { name: 'SH.E', metadata: 'foobar' },
    { name: 'SH.F', metadata: 'foobar' },
    { name: 'SH.G', metadata: 'foobar' },
    { name: 'SH.H', metadata: 'foobar' },
    { name: 'SH.I', metadata: 'foobar' },
    { name: 'SH.J', metadata: 'foobar' },
    { name: 'SH.K', metadata: 'foobar' },
    { name: 'SH.L', metadata: 'foobar' },
  ],
  category: [
    { name: 'CT.A' },
    { name: 'CT.B' },
    { name: 'CT.C' },
    { name: 'CT.D' },
    { name: 'CT.E' },
    { name: 'CT.F' },
    { name: 'CT.G' },
    { name: 'CT.H' },
    { name: 'CT.I' },
    { name: 'CT.J' },
    { name: 'CT.K' },
    { name: 'CT.L' },
  ],
  product: ({ shop, category }) => [
    {
      name: 'PD.A',
      categories: category.slice(0, PRODUCT_CATEGORY_COUNT).map(prop('id')),
      shops: shop.slice(0, PRODUCT_SHOP_COUNT).map(prop('id')),
    },
  ],
};

const getUID = modelName => `api::${modelName}.${modelName}`;
const getCMPrefixUrl = modelName => `/content-manager/collection-types/${getUID(modelName)}`;

describe('x-to-many RF Preview', () => {
  const cmProductUrl = getCMPrefixUrl(productModel.singularName);

  beforeAll(async () => {
    await builder
      .addContentTypes([shopModel, categoryModel, productModel])
      .addFixtures(shopModel.singularName, fixtures.shop)
      .addFixtures(categoryModel.singularName, fixtures.category)
      .addFixtures(productModel.singularName, fixtures.product)
      .build();

    strapi = await createStrapiInstance();
    rq = await createAuthRequest({ strapi });

    Object.assign(data, await builder.sanitizedFixtures(strapi));
  });

  afterAll(async () => {
    await strapi.destroy();
    await builder.cleanup();
  });

  describe('Entity Misc', () => {
    test.each(['foobar', 'name'])(`Throws if the targeted field is invalid (%s)`, async field => {
      const product = data.product[0];
      const { body, statusCode } = await rq.get(`${cmProductUrl}/${product.id}/${field}`);

      expect(statusCode).toBe(400);
      expect(body).toMatchObject({
        data: null,
        error: {
          status: 400,
          name: 'BadRequestError',
          message: 'Invalid target field',
          details: {},
        },
      });
    });

    test('Throws if the entity does not exist', async () => {
      const { body, statusCode } = await rq.get(`${cmProductUrl}/${data.shop[11].id}/categories`);

      expect(statusCode).toBe(404);
      expect(body).toMatchObject({
        data: null,
        error: {
          status: 404,
          name: 'NotFoundError',
          message: 'Not Found',
          details: {},
        },
      });
    });
  });

  describe('Relation Nature', () => {
    test(`Throws if the relation's nature is not a x-to-many`, async () => {
      const url = getCMPrefixUrl(categoryModel.singularName);
      const id = data.category[0].id;

      const { body, statusCode } = await rq.get(`${url}/${id}/product`);

      expect(statusCode).toBe(400);
      expect(body).toMatchObject({
        data: null,
        error: {
          status: 400,
          name: 'BadRequestError',
          message: 'Invalid target field',
          details: {},
        },
      });
    });
  });

  describe('Default Behavior', () => {
    test('Should return a preview for the shops field', async () => {
      const product = data.product[0];

      const { body, statusCode } = await rq.get(`${cmProductUrl}/${product.id}/shops`);

      expect(statusCode).toBe(200);
      expect(body.results).toHaveLength(10);
      expect(difference(toIds(body.results), toIds(data.shop))).toHaveLength(0);
    });

    test('Should return a preview for the categories field', async () => {
      const product = data.product[0];

      const { body, statusCode } = await rq.get(`${cmProductUrl}/${product.id}/categories`);

      expect(statusCode).toBe(200);
      expect(body.results).toHaveLength(5);
      expect(difference(toIds(body.results), toIds(data.category))).toHaveLength(0);
    });
  });

  describe('Pagination', () => {
    test.each([[1, 10], [2, 10], [5, 1], [4, 2], [1, 100]])(
      'Custom pagination (%s, %s)',
      async (page, pageSize) => {
        const product = data.product[0];

        const { body, statusCode } = await rq.get(
          `${cmProductUrl}/${product.id}/shops?page=${page}&pageSize=${pageSize}`
        );

        expect(statusCode).toBe(200);

        const { pagination, results } = body;

        expect(pagination.page).toBe(page);
        expect(pagination.pageSize).toBe(pageSize);
        expect(results).toHaveLength(
          Math.min(pageSize, PRODUCT_SHOP_COUNT - pageSize * (page - 1))
        );
      }
    );
  });
});
