import { faker } from '@faker-js/faker/locale/en_US';
import { IllegalStateError, PromisesPort, allPromises } from '@humand-packages/common';
import { MockProxy, mock } from 'jest-mock-extended';
import { CoreForm } from '../../../../src/api/kernels/forms/business/models/coreForm';
import { CoreFormsService } from '../../../../src/api/kernels/forms/business/services/coreFormsService';
import { FormQuestionWithAnswerWithoutId } from '../../../../src/api/kernels/forms/business/types/coreFormContent';
import { getFormDraftContentReferenceIds } from '../../../../src/api/kernels/forms/business/types/formDraftContent';
import { FormTemplate } from '../../../../src/api/modules/formTemplates/business/models/formTemplate';
import { FormTemplatesService } from '../../../../src/api/modules/formTemplates/business/services/formTemplatesService';
import { PerformanceReviewFormTemplate } from '../../../../src/api/modules/performanceReviews/business/models/performanceReviewFormTemplate';
import { PerformanceReviewUtilsPort } from '../../../../src/api/modules/performanceReviews/business/ports/performanceReviewUtilsPort';
import { PerformanceReviewFormTemplatesAdapter } from '../../../../src/api/modules/performanceReviews/infrastructure/adapters/formTemplatesAdapter';
import { CreateQuestionTemplateInterface } from '../../../../src/api/modules/questionTemplates/business/interfaces/createQuestionTemplateInterface';
import { QuestionTemplate } from '../../../../src/api/modules/questionTemplates/business/models/questionTemplate';
import { QuestionTemplatesService } from '../../../../src/api/modules/questionTemplates/business/services/questionTemplatesService';
import { QUESTION_TEMPLATE_ORIGINS } from '../../../../src/api/modules/questionTemplates/business/types/origins';
import { QUESTION_TEMPLATE_STATUS } from '../../../../src/api/modules/questionTemplates/business/types/status';
import { QUESTION_TEMPLATES_QUESTION_TYPES } from '../../../../src/api/modules/questionTemplates/business/types/types';
import { fakePositiveInteger } from '../../../common/common';
import { checkMockCallsCount, mockImplementationTimes, mockResolvedValueTimes } from '../../../common/mock/mockHelpers';
import { generateFormDraftWithQuestions, generateRandomCoreFormQuestion, generateRandomFormDraft } from '../../../kernels/forms/common';
import { generateRandomFormTemplate } from '../../formTemplates/common';
import {
    generateCreateQuestionTemplateDataRandomly,
    generateQuestionTemplateWithValues,
    generateRandomQuestionTemplate,
} from '../../questionTemplates/common';
import {
    generateFormTemplateWithValues,
    generatePerformanceReviewFormTemplateWithValues,
    generateRandomPerformanceReviewFormTemplateWithTemplate,
} from '../common';

let formTemplatesService: MockProxy<FormTemplatesService>;
let questionTemplatesService: MockProxy<QuestionTemplatesService>;
let coreFormsService: MockProxy<CoreFormsService>;
let promisesPort: MockProxy<PromisesPort>;
let utilsPort: MockProxy<PerformanceReviewUtilsPort>;
let performanceReviewsFormTemplatesAdapter: PerformanceReviewFormTemplatesAdapter;

describe('PerformanceReviewFormTemplatesAdapter', () => {
    beforeEach(() => {
        formTemplatesService = mock<FormTemplatesService>();
        questionTemplatesService = mock<QuestionTemplatesService>();
        coreFormsService = mock<CoreFormsService>();
        promisesPort = mock<PromisesPort>();
        utilsPort = mock<PerformanceReviewUtilsPort>();
        performanceReviewsFormTemplatesAdapter = new PerformanceReviewFormTemplatesAdapter(
            formTemplatesService,
            questionTemplatesService,
            coreFormsService,
            promisesPort,
            utilsPort,
            10,
        );

        // General implementation mocks
        promisesPort.allPromises.mockImplementation((promises) => {
            return allPromises(promises);
        });
    });

    describe('formTemplatesExistWithIds', () => {
        let instanceId: number;
        let ids: number[];

        beforeEach(() => {
            const possibleIds = Array.from(new Set(faker.helpers.multiple(faker.number.int, { count: 10 })));
            ids = faker.helpers.arrayElements(possibleIds);
            instanceId = fakePositiveInteger();
        });

        it('should return true if all form templates exist', async () => {
            formTemplatesService.countFormTemplatesWithIds.mockResolvedValueOnce(ids.length);

            const result = await performanceReviewsFormTemplatesAdapter.formTemplatesExistWithIds(ids, instanceId);

            expect(result).toBeTrue();
        });

        it('should return true if all form templates exist and values are repeated', async () => {
            formTemplatesService.countFormTemplatesWithIds.mockResolvedValueOnce(ids.length);

            const result = await performanceReviewsFormTemplatesAdapter.formTemplatesExistWithIds([...ids, ids[0]], instanceId);

            expect(result).toBeTrue();
        });

        it('should return false if one of the form templates does not exist', async () => {
            formTemplatesService.countFormTemplatesWithIds.mockResolvedValueOnce(ids.length - 1);

            const result = await performanceReviewsFormTemplatesAdapter.formTemplatesExistWithIds(ids, instanceId);

            expect(result).toBeFalse();
        });

        it('should return false if none of the form templates exist', async () => {
            formTemplatesService.countFormTemplatesWithIds.mockResolvedValueOnce(0);

            const result = await performanceReviewsFormTemplatesAdapter.formTemplatesExistWithIds(ids, instanceId);

            expect(result).toBeFalse();
        });

        it('should fail if the count operation fails', async () => {
            formTemplatesService.countFormTemplatesWithIds.mockRejectedValueOnce(new Error());

            await expect(performanceReviewsFormTemplatesAdapter.formTemplatesExistWithIds(ids, instanceId)).rejects.toThrow(Error);
        });
    });

    describe('findFormTemplate', () => {
        let formTemplate: FormTemplate;

        beforeEach(() => {
            const id = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            formTemplate = generateFormTemplateWithValues(id, instanceId);
        });

        it('should return correctly with form drafts', async () => {
            formTemplate.draft = generateRandomFormDraft(formTemplate.draftId);
            formTemplatesService.listFormTemplates.mockResolvedValueOnce({
                formTemplates: [formTemplate],
                totalCount: 1,
            });

            const result = await performanceReviewsFormTemplatesAdapter.findFormTemplate(formTemplate.id!, formTemplate.instanceId);

            expect(result).not.toBeNull();
            expect(result).toBeInstanceOf(FormTemplate);
            expect(result!.id).toBe(formTemplate.id);
            expect(result!.draft).not.toBeNull();
            expect(result!.draft?.tag).toBe(formTemplate.draftId);
        });

        it('should return correctly if form template does not exist', async () => {
            formTemplatesService.listFormTemplates.mockResolvedValueOnce({
                formTemplates: [],
                totalCount: 0,
            });

            const result = await performanceReviewsFormTemplatesAdapter.findFormTemplate(formTemplate.id!, formTemplate.instanceId);

            expect(result).toBeNull();
        });

        it('should fail if the list operation fails', async () => {
            formTemplatesService.listFormTemplates.mockRejectedValueOnce(new Error());

            await expect(performanceReviewsFormTemplatesAdapter.findFormTemplate(formTemplate.id!, formTemplate.instanceId)).rejects.toThrow(Error);
        });
    });

    describe('findFormTemplatesById', () => {
        let instanceId: number;
        let ids: number[];

        beforeEach(() => {
            const possibleIds = Array.from(new Set(faker.helpers.multiple(faker.number.int, { count: 10 })));
            ids = faker.helpers.arrayElements(possibleIds);
            instanceId = fakePositiveInteger();
        });

        it('should return correctly with form drafts', async () => {
            formTemplatesService.listFormTemplates.mockResolvedValueOnce({
                formTemplates: ids.map((id) => {
                    const formTemplate = generateFormTemplateWithValues(id, instanceId);
                    formTemplate.draft = generateRandomFormDraft(formTemplate.draftId);
                    return formTemplate;
                }),
                totalCount: ids.length,
            });

            const result = await performanceReviewsFormTemplatesAdapter.findFormTemplatesById(ids, instanceId);

            expect(Object.keys(result)).toHaveLength(ids.length);

            Object.keys(result).forEach((key) => {
                const formTemplateId = parseInt(key);
                const formTemplate = result[formTemplateId];

                expect(formTemplate).toBeInstanceOf(FormTemplate);
                expect(formTemplate.id).toBe(formTemplateId);
                expect(formTemplate.draft).not.toBeNull();
                expect(formTemplate.draft?.tag).toBe(formTemplate.draftId);
            });
        });

        it('should return correctly if list of ids is empty', async () => {
            formTemplatesService.listFormTemplates.mockResolvedValueOnce({
                formTemplates: [],
                totalCount: 0,
            });

            const result = await performanceReviewsFormTemplatesAdapter.findFormTemplatesById([], instanceId);

            expect(result).not.toBeNull();
            expect(Object.keys(result)).toHaveLength(0);
        });

        it('should fail if the list operation fails', async () => {
            formTemplatesService.listFormTemplates.mockRejectedValueOnce(new Error());

            await expect(performanceReviewsFormTemplatesAdapter.findFormTemplatesById(ids, instanceId)).rejects.toThrow(Error);
        });
    });

    describe('getRatingQuestionsOfFormTemplate', () => {
        let formTemplate: FormTemplate;

        beforeEach(() => {
            const id = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            formTemplate = generateFormTemplateWithValues(id, instanceId);
            formTemplate.draft = generateFormDraftWithQuestions(formTemplate.draftId!);

            utilsPort.sortObjectArrayByPrimitiveArray.mockImplementationOnce((array) => array);
        });

        it('should return empty if form template has no rating questions', async () => {
            questionTemplatesService.listQuestionTemplates.mockResolvedValueOnce({
                questionTemplates: [],
                totalCount: 0,
            });

            const result = await performanceReviewsFormTemplatesAdapter.getRatingQuestionsOfFormTemplate(formTemplate);

            expect(result).toHaveLength(0);
        });

        it('should return empty if form template has no questions', async () => {
            formTemplate.draft = generateRandomFormDraft(formTemplate.draftId!);
            questionTemplatesService.listQuestionTemplates.mockResolvedValueOnce({
                questionTemplates: [],
                totalCount: 0,
            });

            const result = await performanceReviewsFormTemplatesAdapter.getRatingQuestionsOfFormTemplate(formTemplate);

            expect(result).toHaveLength(0);
        });

        it('should return list of rating questions of form template', async () => {
            const referenceIds = getFormDraftContentReferenceIds(formTemplate.draft!.content);
            questionTemplatesService.listQuestionTemplates.mockResolvedValueOnce({
                questionTemplates: referenceIds.map((id) =>
                    generateQuestionTemplateWithValues(id, formTemplate.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
                ),
                totalCount: referenceIds.length,
            });

            const result = await performanceReviewsFormTemplatesAdapter.getRatingQuestionsOfFormTemplate(formTemplate);

            expect(result).toHaveLength(referenceIds.length);

            result.forEach((question, index) => {
                expect(question.id).toBe(referenceIds[index]);
                expect(question.type).toBe(QUESTION_TEMPLATES_QUESTION_TYPES.RATING);
            });
        });

        it('should throw illegal state error if form template has no draft loaded', async () => {
            formTemplate.draft = null;

            await expect(performanceReviewsFormTemplatesAdapter.getRatingQuestionsOfFormTemplate(formTemplate)).rejects.toThrow(IllegalStateError);
        });

        it('should fail if the list operation fails', async () => {
            questionTemplatesService.listQuestionTemplates.mockRejectedValueOnce(new Error());

            await expect(performanceReviewsFormTemplatesAdapter.getRatingQuestionsOfFormTemplate(formTemplate)).rejects.toThrow(Error);
        });
    });

    describe('publishFormTemplates', () => {
        let reviewFormTemplates: PerformanceReviewFormTemplate[];

        beforeEach(() => {
            const reviewCycleId = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            const formTemplatesCount = faker.number.int({ max: 3, min: 1 });
            reviewFormTemplates = Array(formTemplatesCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycleId, instanceId));
        });

        it('should succeed if an empty array is provided', async () => {
            formTemplatesService.startUsingFormTemplates.mockResolvedValueOnce();
            questionTemplatesService.startUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.publishFormTemplates([])).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesService.publishFormTemplate, 0);
            checkMockCallsCount(formTemplatesService.startUsingFormTemplates, 1);
            checkMockCallsCount(questionTemplatesService.startUsingQuestionTemplates, 1);
        });

        it('should succeed if form templates have no questions', async () => {
            mockImplementationTimes(
                formTemplatesService.publishFormTemplate,
                (formTemplate: FormTemplate) => {
                    const coreForm = new CoreForm(
                        fakePositiveInteger(),
                        formTemplate.draftId as string,
                        fakePositiveInteger(),
                        { description: null, steps: [], title: formTemplate.title },
                        [],
                        faker.date.recent(),
                        faker.date.recent(),
                        null,
                        null,
                    );
                    return Promise.resolve(coreForm);
                },
                reviewFormTemplates.length,
            );
            formTemplatesService.startUsingFormTemplates.mockResolvedValueOnce();
            questionTemplatesService.startUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.publishFormTemplates(reviewFormTemplates)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesService.publishFormTemplate, reviewFormTemplates.length);
            checkMockCallsCount(formTemplatesService.startUsingFormTemplates, 1);
            checkMockCallsCount(questionTemplatesService.startUsingQuestionTemplates, 1);

            reviewFormTemplates.forEach((reviewFormTemplate) => {
                const coreForm = reviewFormTemplate.coreForm;

                expect(coreForm).toBeInstanceOf(CoreForm);
            });
        });

        it('should succeed if form templates have questions', async () => {
            mockImplementationTimes(
                formTemplatesService.publishFormTemplate,
                (formTemplate: FormTemplate) => {
                    const coreForm = new CoreForm(
                        fakePositiveInteger(),
                        formTemplate.draftId as string,
                        fakePositiveInteger(),
                        { description: null, steps: [], title: formTemplate.title },
                        faker.helpers.multiple(faker.number.int),
                        faker.date.recent(),
                        faker.date.recent(),
                        null,
                        null,
                    );
                    return Promise.resolve(coreForm);
                },
                reviewFormTemplates.length,
            );
            formTemplatesService.startUsingFormTemplates.mockResolvedValueOnce();
            questionTemplatesService.startUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.publishFormTemplates(reviewFormTemplates)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesService.publishFormTemplate, reviewFormTemplates.length);
            checkMockCallsCount(formTemplatesService.startUsingFormTemplates, 1);
            checkMockCallsCount(questionTemplatesService.startUsingQuestionTemplates, 1);

            reviewFormTemplates.forEach((reviewFormTemplate) => {
                const coreForm = reviewFormTemplate.coreForm;

                expect(coreForm).toBeInstanceOf(CoreForm);
            });
        });

        it('should fail if the publish operation fails', async () => {
            formTemplatesService.publishFormTemplate.mockRejectedValueOnce(new Error());

            await expect(performanceReviewsFormTemplatesAdapter.publishFormTemplates(reviewFormTemplates)).rejects.toThrow(Error);
        });

        it('should throw illegal state error if one of the templates has no id', async () => {
            const formTemplate = generatePerformanceReviewFormTemplateWithValues(fakePositiveInteger(), { templateId: null });
            reviewFormTemplates = [formTemplate];

            await expect(performanceReviewsFormTemplatesAdapter.publishFormTemplates(reviewFormTemplates)).rejects.toThrow(IllegalStateError);
        });
    });

    describe('stopUsingFormTemplates', () => {
        let reviewFormTemplates: PerformanceReviewFormTemplate[];
        let questionTemplateIdByTag: { [tag: string]: number };
        let formTemplateUsages: { [formTemplateId: number]: number };
        let formTemplateIds: number[];

        beforeEach(() => {
            const reviewCycleId = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            const formTemplatesCount = faker.number.int({ max: 3, min: 1 });
            reviewFormTemplates = Array(formTemplatesCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycleId, instanceId));
            questionTemplateIdByTag = reviewFormTemplates
                .map((reviewFormTemplate) => reviewFormTemplate.template?.formTemplate)
                .reduce((records, formTemplate) => {
                    const castedFormTemplate = formTemplate as FormTemplate;
                    records[castedFormTemplate.draftId as string] = fakePositiveInteger();
                    return records;
                }, {} as { [tag: string]: number });
            formTemplateUsages = Object.values(reviewFormTemplates).reduce((records, reviewFormTemplate) => {
                if (reviewFormTemplate.template!.formTemplateId !== null)
                    records[reviewFormTemplate.template!.formTemplateId!] = faker.number.int({ min: 1 });
                return records;
            }, {} as { [formTemplateId: number]: number });
            formTemplateIds = Object.keys(formTemplateUsages).map(Number);
        });

        it('should succeed if an empty array is provided', async () => {
            formTemplatesService.stopUsingFormTemplates.mockResolvedValueOnce();
            questionTemplatesService.stopUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.stopUsingFormTemplates([], {}, [])).resolves.not.toThrow();

            checkMockCallsCount(coreFormsService.getVersion, 0);
            checkMockCallsCount(coreFormsService.deleteForm, 0);
            checkMockCallsCount(formTemplatesService.stopUsingFormTemplates, 1);
            checkMockCallsCount(questionTemplatesService.stopUsingQuestionTemplates, 1);
        });

        it('should succeed if form templates have no questions and are unused', async () => {
            formTemplateUsages = Object.values(reviewFormTemplates).reduce((records, reviewFormTemplate) => {
                if (reviewFormTemplate.templateId !== null) records[reviewFormTemplate.templateId] = 0;
                return records;
            }, {} as { [formTemplateId: number]: number });
            mockImplementationTimes(
                coreFormsService.getVersion,
                (tag: string, id: number) => {
                    const coreForm = new CoreForm(
                        id,
                        tag,
                        fakePositiveInteger(),
                        { description: null, steps: [], title: faker.word.words() },
                        [],
                        faker.date.recent(),
                        faker.date.recent(),
                        null,
                        null,
                    );
                    return Promise.resolve(coreForm);
                },
                reviewFormTemplates.length,
            );
            mockResolvedValueTimes(coreFormsService.deleteForm, undefined, reviewFormTemplates.length);
            formTemplatesService.stopUsingFormTemplates.mockResolvedValueOnce();
            coreFormsService.countQuestionReferenceUsages.mockResolvedValueOnce({});
            questionTemplatesService.stopUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(
                performanceReviewsFormTemplatesAdapter.stopUsingFormTemplates(reviewFormTemplates, formTemplateUsages, formTemplateIds),
            ).resolves.not.toThrow();

            checkMockCallsCount(coreFormsService.getVersion, reviewFormTemplates.length);
            checkMockCallsCount(coreFormsService.deleteForm, reviewFormTemplates.length);
            checkMockCallsCount(formTemplatesService.stopUsingFormTemplates, 1);
            checkMockCallsCount(coreFormsService.countQuestionReferenceUsages, 1);
            checkMockCallsCount(questionTemplatesService.stopUsingQuestionTemplates, 1);
        });

        it('should succeed if form templates have no questions and are still in use', async () => {
            mockImplementationTimes(
                coreFormsService.getVersion,
                (tag: string, id: number) => {
                    const coreForm = new CoreForm(
                        id,
                        tag,
                        fakePositiveInteger(),
                        { description: null, steps: [], title: faker.word.words() },
                        [],
                        faker.date.recent(),
                        faker.date.recent(),
                        null,
                        null,
                    );
                    return Promise.resolve(coreForm);
                },
                reviewFormTemplates.length,
            );
            mockResolvedValueTimes(coreFormsService.deleteForm, undefined, reviewFormTemplates.length);
            formTemplatesService.stopUsingFormTemplates.mockResolvedValueOnce();
            coreFormsService.countQuestionReferenceUsages.mockResolvedValueOnce({});
            questionTemplatesService.stopUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(
                performanceReviewsFormTemplatesAdapter.stopUsingFormTemplates(reviewFormTemplates, formTemplateUsages, formTemplateIds),
            ).resolves.not.toThrow();
            expect(formTemplatesService.stopUsingFormTemplates).toHaveBeenCalledWith([]);

            checkMockCallsCount(coreFormsService.getVersion, reviewFormTemplates.length);
            checkMockCallsCount(coreFormsService.deleteForm, reviewFormTemplates.length);
            checkMockCallsCount(formTemplatesService.stopUsingFormTemplates, 1);
            checkMockCallsCount(coreFormsService.countQuestionReferenceUsages, 1);
            checkMockCallsCount(questionTemplatesService.stopUsingQuestionTemplates, 1);
        });

        it('should succeed if form templates have still used questions', async () => {
            mockImplementationTimes(
                coreFormsService.getVersion,
                (tag: string, id: number) => {
                    const coreForm = new CoreForm(
                        id,
                        tag,
                        fakePositiveInteger(),
                        { description: null, steps: [], title: faker.word.words() },
                        [questionTemplateIdByTag[tag]],
                        faker.date.recent(),
                        faker.date.recent(),
                        null,
                        null,
                    );
                    return Promise.resolve(coreForm);
                },
                reviewFormTemplates.length,
            );
            mockResolvedValueTimes(coreFormsService.deleteForm, undefined, reviewFormTemplates.length);
            formTemplatesService.stopUsingFormTemplates.mockResolvedValueOnce();
            coreFormsService.countQuestionReferenceUsages.mockResolvedValueOnce(
                Object.values(questionTemplateIdByTag).reduce((records, questionTemplateId) => {
                    records[questionTemplateId] = faker.number.int({ min: 1 });
                    return records;
                }, {} as { [questionReferenceId: number]: number }),
            );
            questionTemplatesService.stopUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(
                performanceReviewsFormTemplatesAdapter.stopUsingFormTemplates(reviewFormTemplates, formTemplateUsages, formTemplateIds),
            ).resolves.not.toThrow();
            expect(questionTemplatesService.stopUsingQuestionTemplates).toHaveBeenCalledWith([]);

            checkMockCallsCount(coreFormsService.getVersion, reviewFormTemplates.length);
            checkMockCallsCount(coreFormsService.deleteForm, reviewFormTemplates.length);
            checkMockCallsCount(formTemplatesService.stopUsingFormTemplates, 1);
            checkMockCallsCount(coreFormsService.countQuestionReferenceUsages, 1);
            checkMockCallsCount(questionTemplatesService.stopUsingQuestionTemplates, 1);
        });

        it('should succeed if form templates have unused questions', async () => {
            mockImplementationTimes(
                coreFormsService.getVersion,
                (tag: string, id: number) => {
                    const coreForm = new CoreForm(
                        id,
                        tag,
                        fakePositiveInteger(),
                        { description: null, steps: [], title: faker.word.words() },
                        [questionTemplateIdByTag[tag]],
                        faker.date.recent(),
                        faker.date.recent(),
                        null,
                        null,
                    );
                    return Promise.resolve(coreForm);
                },
                reviewFormTemplates.length,
            );
            mockResolvedValueTimes(coreFormsService.deleteForm, undefined, reviewFormTemplates.length);
            formTemplatesService.stopUsingFormTemplates.mockResolvedValueOnce();
            coreFormsService.countQuestionReferenceUsages.mockResolvedValueOnce(
                Object.values(questionTemplateIdByTag).reduce((records, questionTemplateId) => {
                    records[questionTemplateId] = 0;
                    return records;
                }, {} as { [questionReferenceId: number]: number }),
            );
            questionTemplatesService.stopUsingQuestionTemplates.mockResolvedValueOnce();

            await expect(
                performanceReviewsFormTemplatesAdapter.stopUsingFormTemplates(reviewFormTemplates, formTemplateUsages, formTemplateIds),
            ).resolves.not.toThrow();

            checkMockCallsCount(coreFormsService.getVersion, reviewFormTemplates.length);
            checkMockCallsCount(coreFormsService.deleteForm, reviewFormTemplates.length);
            checkMockCallsCount(formTemplatesService.stopUsingFormTemplates, 1);
            checkMockCallsCount(coreFormsService.countQuestionReferenceUsages, 1);
            checkMockCallsCount(questionTemplatesService.stopUsingQuestionTemplates, 1);
        });

        it('should fail if any operation fails', async () => {
            coreFormsService.getVersion.mockRejectedValueOnce(new Error());

            await expect(
                performanceReviewsFormTemplatesAdapter.stopUsingFormTemplates(reviewFormTemplates, formTemplateUsages, formTemplateIds),
            ).rejects.toThrow(Error);
        });
    });

    describe('findQuestionTemplates', () => {
        let template: QuestionTemplate;
        let origin: string;

        beforeEach(() => {
            template = generateRandomQuestionTemplate();
            origin = faker.helpers.arrayElement(Object.values(QUESTION_TEMPLATE_ORIGINS));
        });

        it('successful if no templates exist', async () => {
            questionTemplatesService.listQuestionTemplates.mockResolvedValueOnce({ questionTemplates: [], totalCount: 0 });

            const result = await performanceReviewsFormTemplatesAdapter.findQuestionTemplates([template.id], template.instanceId, origin);

            expect(result).toHaveLength(0);
        });

        it('successful if one template exists', async () => {
            questionTemplatesService.listQuestionTemplates.mockResolvedValueOnce({ questionTemplates: [template], totalCount: 1 });

            const result = await performanceReviewsFormTemplatesAdapter.findQuestionTemplates([template.id], template.instanceId, origin);

            expect(result).toHaveLength(1);
            expect(result[0]).toBe(template);
        });
    });

    describe('deleteQuestionTemplates', () => {
        let template: QuestionTemplate;

        beforeEach(() => {
            template = generateRandomQuestionTemplate();
        });

        it('delete question template', async () => {
            questionTemplatesService.deleteQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.deleteQuestionTemplates([template])).resolves.not.toThrow();

            checkMockCallsCount(questionTemplatesService.deleteQuestionTemplates, 1);
        });
    });

    describe('deleteFormTemplate', () => {
        let template: FormTemplate;

        beforeEach(() => {
            template = generateRandomFormTemplate(fakePositiveInteger());
        });

        it('delete form template', async () => {
            formTemplatesService.deleteFormTemplate.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.deleteFormTemplate(template)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesService.deleteFormTemplate, 1);
        });
    });

    describe('createQuestionTemplate', () => {
        let createQuestionTemplateParams: CreateQuestionTemplateInterface;
        let instanceId: number;
        let fakeId: number;
        let fakeDate: Date;

        beforeEach(() => {
            createQuestionTemplateParams = generateCreateQuestionTemplateDataRandomly();
            instanceId = fakePositiveInteger();
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
        });

        it('create question template successfully', async () => {
            questionTemplatesService.createQuestionTemplate.mockImplementationOnce((instanceId, creatorId, template) => {
                const newTemplate = new QuestionTemplate({
                    answerRequired: template.answerRequired,
                    commentableStatus: template.commentableStatus,
                    createdAt: fakeDate,
                    creator: undefined,
                    creatorId,
                    deletedAt: null,
                    description: template.description,
                    duplicationId: template.duplicationId || faker.word.words(),
                    id: fakeId,
                    instanceId,
                    origin: template.origin,
                    possibleAnswers: template.possibleAnswers || [],
                    status: QUESTION_TEMPLATE_STATUS.DRAFT,
                    title: template.title,
                    topics: [],
                    type: template.type,
                    updatedAt: fakeDate,
                });
                return Promise.resolve(newTemplate);
            });

            const questionTemplateCreated = await performanceReviewsFormTemplatesAdapter.createQuestionTemplate(
                instanceId,
                fakeId,
                createQuestionTemplateParams,
            );

            expect(questionTemplateCreated).toBeDefined();
            expect(questionTemplateCreated.answerRequired).toBe(createQuestionTemplateParams.answerRequired);
            expect(questionTemplateCreated.commentableStatus).toBe(createQuestionTemplateParams.commentableStatus);
            expect(questionTemplateCreated.description).toBe(createQuestionTemplateParams.description);
            expect(questionTemplateCreated.origin).toBe(createQuestionTemplateParams.origin);
            expect(questionTemplateCreated.possibleAnswers).toBe(createQuestionTemplateParams.possibleAnswers);
            expect(questionTemplateCreated.title).toBe(createQuestionTemplateParams.title);
            expect(questionTemplateCreated.type).toBe(createQuestionTemplateParams.type);
            expect(questionTemplateCreated.creatorId).toBe(fakeId);
            expect(questionTemplateCreated.instanceId).toBe(instanceId);
            expect(questionTemplateCreated.status).toBe(QUESTION_TEMPLATE_STATUS.DRAFT);
        });
    });

    describe('editQuestionTemplate', () => {
        let originalQuestion: QuestionTemplate;
        let createQuestionTemplateParams: CreateQuestionTemplateInterface;

        beforeEach(() => {
            originalQuestion = generateRandomQuestionTemplate();
            createQuestionTemplateParams = generateCreateQuestionTemplateDataRandomly();
            createQuestionTemplateParams.origin = originalQuestion.origin;
        });

        it('should edit a question template', async () => {
            questionTemplatesService.editQuestionTemplate.mockImplementationOnce((questionTemplate, updateParams) => {
                const newTemplate = new QuestionTemplate({
                    answerRequired: updateParams.answerRequired,
                    commentableStatus: updateParams.commentableStatus,
                    createdAt: questionTemplate.createdAt,
                    creator: undefined,
                    creatorId: questionTemplate.creatorId,
                    deletedAt: null,
                    description: updateParams.description,
                    duplicationId: updateParams.duplicationId || faker.word.words(),
                    id: questionTemplate.id,
                    instanceId: questionTemplate.instanceId,
                    origin: updateParams.origin,
                    possibleAnswers: updateParams.possibleAnswers || [],
                    status: questionTemplate.status,
                    title: updateParams.title,
                    topics: [],
                    type: updateParams.type,
                    updatedAt: questionTemplate.updatedAt,
                });
                return Promise.resolve(newTemplate);
            });
            const questionTemplate = await performanceReviewsFormTemplatesAdapter.editQuestionTemplate(
                originalQuestion,
                createQuestionTemplateParams,
            );

            expect(questionTemplate).toBeDefined();
            expect(questionTemplate.id).toBe(originalQuestion.id);
            expect(questionTemplate.answerRequired).toBe(createQuestionTemplateParams.answerRequired);
            expect(questionTemplate.commentableStatus).toBe(createQuestionTemplateParams.commentableStatus);
            expect(questionTemplate.description).toBe(createQuestionTemplateParams.description);
            expect(questionTemplate.origin).toBe(createQuestionTemplateParams.origin);
            expect(questionTemplate.possibleAnswers).toBe(createQuestionTemplateParams.possibleAnswers);
            expect(questionTemplate.title).toBe(createQuestionTemplateParams.title);
            expect(questionTemplate.type).toBe(createQuestionTemplateParams.type);
            expect(questionTemplate.creatorId).toBe(originalQuestion.creatorId);
            expect(questionTemplate.instanceId).toBe(originalQuestion.instanceId);
            expect(questionTemplate.status).toBe(originalQuestion.status);
        });
    });

    describe('addQuestionTemplatesToFormTemplate', () => {
        let template: FormTemplate;

        beforeEach(() => {
            template = generateRandomFormTemplate(fakePositiveInteger());
        });

        it('delete question template', async () => {
            formTemplatesService.addQuestionTemplatesToFormTemplate.mockResolvedValueOnce();

            await expect(performanceReviewsFormTemplatesAdapter.addQuestionTemplatesToFormTemplate(template)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesService.addQuestionTemplatesToFormTemplate, 1);
        });
    });

    describe('questionTemplateToCoreFormQuestion', () => {
        let questionTemplate: QuestionTemplate;
        let coreFormQuestion: FormQuestionWithAnswerWithoutId;

        beforeEach(() => {
            questionTemplate = generateRandomQuestionTemplate();
            coreFormQuestion = generateRandomCoreFormQuestion();
        });

        it('should get core form question', () => {
            formTemplatesService.questionTemplateToCoreFormQuestion.mockReturnValueOnce(coreFormQuestion);
            const questionTemplateCreated = performanceReviewsFormTemplatesAdapter.questionTemplateToCoreFormQuestion(questionTemplate);

            expect(questionTemplateCreated).toBeDefined();
            expect(questionTemplateCreated.required).toBe(questionTemplateCreated.required);
            expect(questionTemplateCreated.commentableStatus).toBe(questionTemplateCreated.commentableStatus);
            expect(questionTemplateCreated.description).toBe(questionTemplateCreated.description);
            expect(questionTemplateCreated.title).toBe(questionTemplateCreated.title);
        });
    });
});
