'use strict';

const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createContentAPIRequest } = require('../../../../../test/helpers/request');

let strapi;
let rq;

const defaultBody = {
  field: [
    {
      __component: 'default.simple-compo',
      name: 'someString',
    },
    {
      __component: 'default.compo-with-other-compo',
      compo: {
        name: 'someString',
      },
    },
  ],
};

const models = {
  ct: {
    displayName: 'withdynamiczone',
    singularName: 'withdynamiczone',
    pluralName: 'withdynamiczones',
    attributes: {
      field: {
        type: 'dynamiczone',
        components: ['default.compo-with-other-compo', 'default.simple-compo'],
        required: false,
        min: 2,
        max: 5,
      },
    },
  },
  simpleCompo: {
    displayName: 'simple-compo',
    attributes: {
      name: {
        type: 'string',
      },
    },
  },
  otherCompo: {
    displayName: 'compo-with-other-compo',
    attributes: {
      compo: {
        type: 'component',
        component: 'default.simple-compo',
      },
    },
  },
};

const createEntry = () => {
  return rq({
    method: 'POST',
    url: '/',
    body: {
      data: defaultBody,
    },
  });
};

const createEmpty = () => {
  return rq({
    method: 'POST',
    url: '/',
    body: {
      data: {
        field: [],
      },
    },
  });
};

describe('Not required dynamiczone', () => {
  const builder = createTestBuilder();

  beforeAll(async () => {
    await builder
      .addComponent(models.simpleCompo)
      .addComponent(models.otherCompo)
      .addContentType(models.ct)
      .build();

    strapi = await createStrapiInstance();
    rq = await createContentAPIRequest({ strapi });
    rq.setURLPrefix('/api/withdynamiczones');
  });

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

  describe('Creation', () => {
    test('Can create an entry with a dynamic zone and a nested compo', async () => {
      const res = await rq({
        method: 'POST',
        url: '/',
        body: {
          data: {
            field: [
              {
                __component: 'default.simple-compo',
                name: 'someString',
              },
              {
                __component: 'default.compo-with-other-compo',
                compo: {
                  name: 'someString',
                },
              },
            ],
          },
        },
        qs: {
          populate: ['field.compo'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data).toMatchObject({
        attributes: {
          field: [
            {
              id: expect.anything(),
              __component: 'default.simple-compo',
              name: 'someString',
            },
            {
              id: expect.anything(),
              __component: 'default.compo-with-other-compo',
              compo: {
                id: expect.anything(),
                name: 'someString',
              },
            },
          ],
        },
      });
    });

    test('Can create entry with empty dynamiczone if it is not required', async () => {
      const res = await rq({
        method: 'POST',
        url: '/',
        body: {
          data: {
            field: [],
          },
        },
        qs: {
          populate: ['field'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data.attributes.field.length).toBe(0);
    });

    test('Throw if min items is not respected', async () => {
      const res = await rq({
        method: 'POST',
        url: '/',
        body: {
          data: {
            field: [
              {
                __component: 'default.simple-compo',
                name: 'someString',
              },
            ],
          },
        },
      });

      expect(res.statusCode).toBe(400);
    });

    test('Throws if max items is not respected', async () => {
      const res = await rq({
        method: 'POST',
        url: '/',
        body: {
          data: {
            field: Array(10).fill({
              __component: 'default.simple-compo',
              name: 'someString',
            }),
          },
        },
      });

      expect(res.statusCode).toBe(400);
    });
  });

  describe('Getting one entry', () => {
    test('The entry has its dynamic zone populated', async () => {
      const createRes = await createEntry();
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'GET',
        url: `/${entryId}`,
        qs: {
          populate: ['field.compo'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data).toMatchObject({
        attributes: {
          field: [
            {
              id: expect.anything(),
              __component: 'default.simple-compo',
              name: 'someString',
            },
            {
              id: expect.anything(),
              __component: 'default.compo-with-other-compo',
              compo: {
                id: expect.anything(),
                name: 'someString',
              },
            },
          ],
        },
      });
    });
  });

  describe('Listing entries', () => {
    test('The entries have their dynamic zones populated', async () => {
      const res = await rq({
        method: 'GET',
        url: '/',
        qs: {
          populate: ['field'],
        },
      });

      expect(res.statusCode).toBe(200);

      expect(Array.isArray(res.body.data)).toBe(true);
      expect(res.body.data).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            attributes: expect.objectContaining({
              field: expect.arrayContaining([
                expect.objectContaining({
                  id: expect.anything(),
                  __component: expect.any(String),
                }),
              ]),
            }),
          }),
        ])
      );
    });
  });

  describe('Edition', () => {
    test('Can empty non required dynamic zone', async () => {
      const createRes = await createEntry();

      expect(createRes.statusCode).toBe(200);
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'PUT',
        url: `/${entryId}`,
        body: {
          data: {
            field: [],
          },
        },
        qs: {
          populate: ['field'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data.attributes.field).toEqual([]);
    });

    test('Can add items to empty dynamic zone', async () => {
      const createRes = await createEmpty();

      expect(createRes.statusCode).toBe(200);
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'PUT',
        url: `/${entryId}`,
        body: {
          data: defaultBody,
        },
        qs: {
          populate: ['field.compo'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data).toMatchObject({
        attributes: {
          field: [
            {
              id: expect.anything(),
              __component: 'default.simple-compo',
              name: 'someString',
            },
            {
              id: expect.anything(),
              __component: 'default.compo-with-other-compo',
              compo: {
                id: expect.anything(),
                name: 'someString',
              },
            },
          ],
        },
      });
    });

    test('Can remove items from dynamic zone', async () => {
      const createRes = await createEntry();

      expect(createRes.statusCode).toBe(200);
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'PUT',
        url: `/${entryId}`,
        body: {
          data: {
            field: [
              {
                __component: 'default.simple-compo',
                name: 'otherString',
              },
              {
                __component: 'default.simple-compo',
                name: 'secondString',
              },
            ],
          },
        },
        qs: {
          populate: ['field'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data).toMatchObject({
        attributes: {
          field: [
            {
              id: expect.anything(),
              __component: 'default.simple-compo',
              name: 'otherString',
            },
            {
              id: expect.anything(),
              __component: 'default.simple-compo',
              name: 'secondString',
            },
          ],
        },
      });
    });

    test('Respects min items', async () => {
      const createRes = await createEntry();

      expect(createRes.statusCode).toBe(200);
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'PUT',
        url: `/${entryId}`,
        body: {
          data: {
            field: [
              {
                __component: 'default.simple-compo',
                name: 'otherString',
              },
            ],
          },
        },
      });

      expect(res.statusCode).toBe(400);
    });

    test('Respects max items', async () => {
      const createRes = await createEntry();

      expect(createRes.statusCode).toBe(200);
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'PUT',
        url: `/${entryId}`,
        body: {
          data: {
            field: Array(10).fill({
              __component: 'default.simple-compo',
              name: 'otherString',
            }),
          },
        },
      });

      expect(res.statusCode).toBe(400);
    });
  });

  describe('Deletion', () => {
    test('Returns the entry with its paths populated', async () => {
      const createRes = await createEntry();

      expect(createRes.statusCode).toBe(200);
      const entryId = createRes.body.data.id;

      const res = await rq({
        method: 'DELETE',
        url: `/${entryId}`,
        qs: {
          populate: ['field'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body.data.attributes.field)).toBe(true);
      expect(res.body.data).toEqual(
        expect.objectContaining({
          attributes: expect.objectContaining({
            field: expect.arrayContaining([
              expect.objectContaining({
                id: expect.anything(),
                __component: expect.any(String),
              }),
            ]),
          }),
        })
      );
    });
  });
});
