/*
 * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
 * Ministerpräsidenten des Landes Schleswig-Holstein
 * Staatskanzlei
 * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */
import { AbstractControl, FormControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { faker } from '@faker-js/faker';
import { createInvalidParam, createIssue, createProblemDetail } from '../../../test/error';
import { InvalidParam, Issue } from '../tech.model';
import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages';
import {
  getControlForInvalidParam,
  getControlForIssue,
  getFieldPath,
  getMessageForInvalidParam,
  getMessageForIssue,
  getMessageReason,
  setInvalidParamValidationError,
  setIssueValidationError,
} from './tech.validation.util';

describe('ValidationUtils', () => {
  const baseField1Control: FormControl = new UntypedFormControl();
  const baseField2Control: FormControl = new UntypedFormControl();
  const subGroupFieldControl: FormControl = new UntypedFormControl();

  const form: FormGroup = new UntypedFormGroup({
    baseField1: baseField1Control,
    baseField2: baseField2Control,
    subGroup: new UntypedFormGroup({
      subGroupField1: subGroupFieldControl,
    }),
  });

  describe('set issue validation error', () => {
    describe('get control for issue', () => {
      it('should return base field control', () => {
        const issue: Issue = { ...createIssue(), field: 'baseField1' };

        const control: AbstractControl = getControlForIssue(form, issue);

        expect(control).toBe(baseField1Control);
      });

      it('should return sub group field', () => {
        const issue: Issue = { ...createIssue(), field: 'class.resource.subGroup.subGroupField1' };

        const control: AbstractControl = getControlForIssue(form, issue, 'resource');

        expect(control).toBe(subGroupFieldControl);
      });

      it('should ignore path prefix', () => {
        const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' };

        const control: AbstractControl = getControlForIssue(form, issue, 'resource');

        expect(control).toBe(baseField1Control);
      });
    });

    describe('in base field', () => {
      const issue: Issue = { ...createIssue(), field: 'baseField1' };

      it('should set error in control', () => {
        setIssueValidationError(form, issue);

        expect(baseField1Control.errors).not.toBeNull();
      });

      it('should set message code in control', () => {
        setIssueValidationError(form, issue);

        expect(baseField1Control.hasError(issue.messageCode)).toBe(true);
      });

      it('should set control touched', () => {
        setIssueValidationError(form, issue);

        expect(baseField1Control.touched).toBe(true);
      });

      it('should not set error in other control', () => {
        setIssueValidationError(form, issue);

        expect(baseField2Control.errors).toBeNull();
      });
    });

    describe('in subGroup Field', () => {
      const issue: Issue = { ...createIssue(), field: 'class.resource.subGroup.subGroupField1' };

      it('should set error in control', () => {
        setIssueValidationError(form, issue, 'resource');

        expect(subGroupFieldControl.errors).not.toBeNull();
      });
    });
  });

  describe('get message for issue', () => {
    const fieldLabel: string = 'Field Label';

    it('should return message', () => {
      const msg: string = getMessageForIssue(fieldLabel, {
        ...createIssue(),
        messageCode: 'validation_field_size',
      });

      expect(msg).toContain('muss mindestens');
    });

    it('should set field label', () => {
      const msg: string = getMessageForIssue(fieldLabel, {
        ...createIssue(),
        messageCode: 'validation_field_size',
      });

      expect(msg).toContain(fieldLabel);
    });

    it('should replace min param', () => {
      const msg: string = getMessageForIssue(fieldLabel, {
        ...createIssue(),
        messageCode: 'validation_field_size',
        parameters: [{ name: 'min', value: '3' }],
      });

      expect(msg).toContain('3');
    });
  });

  describe('set invalidParam validation error', () => {
    describe('get control for invalidParam', () => {
      it('should return base field control', () => {
        const invalidParam: InvalidParam = {
          ...createInvalidParam(),
          name: 'baseField1',
        };

        const control: AbstractControl = getControlForInvalidParam(form, invalidParam);

        expect(control).toBe(baseField1Control);
      });

      it('should return sub group field', () => {
        const invalidParam: InvalidParam = {
          ...createInvalidParam(),
          name: 'resource.subGroup.subGroupField1',
        };

        const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource');

        expect(control).toBe(subGroupFieldControl);
      });

      it('should ignore path prefix', () => {
        const invalidParam: InvalidParam = {
          ...createInvalidParam(),
          name: 'resource.baseField1',
        };

        const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource');

        expect(control).toBe(baseField1Control);
      });
    });

    describe('in base field', () => {
      const invalidParam: InvalidParam = {
        ...createInvalidParam(),
        name: 'baseField1',
      };

      it('should set error in control', () => {
        setInvalidParamValidationError(form, invalidParam);

        expect(baseField1Control.errors).not.toBeNull();
      });

      it('should set message code in control', () => {
        setInvalidParamValidationError(form, invalidParam);

        expect(baseField1Control.hasError(invalidParam.reason)).toBe(true);
      });

      it('should set control touched', () => {
        setInvalidParamValidationError(form, invalidParam);

        expect(baseField1Control.touched).toBe(true);
      });

      it('should not set error in other control', () => {
        setInvalidParamValidationError(form, invalidParam);

        expect(baseField2Control.errors).toBeNull();
      });
    });

    describe('in subGroup Field', () => {
      const invalidParam: InvalidParam = {
        ...createInvalidParam(),
        name: 'resource.subGroup.subGroupField1',
      };

      it('should set error in control', () => {
        setInvalidParamValidationError(form, invalidParam, 'resource');

        expect(subGroupFieldControl.errors).not.toBeNull();
      });
    });
  });

  describe('getFieldPath', () => {
    const resource: string = 'resource';
    const backendClassName: string = 'class';

    it('should return field path', () => {
      const fieldPath: string = 'field1';
      const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`;

      const result: string = getFieldPath(fullPath, resource);

      expect(result).toBe(fieldPath);
    });

    it('should get all parts after the prefix', () => {
      const fieldPath: string = 'group.field1';
      const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`;

      const result: string = getFieldPath(fullPath, resource);

      expect(result).toBe(fieldPath);
    });

    it('should return field from full path when resource is undefined', () => {
      const result: string = getFieldPath('field1', undefined);

      expect(result).toBe('field1');
    });

    it('should return field from field when resource is undefined', () => {
      const fieldPath: string = 'field1';

      const result: string = getFieldPath(fieldPath, undefined);

      expect(result).toBe(fieldPath);
    });
  });

  describe('getMessageReason', () => {
    it('should return reason', () => {
      const problemDetail = createProblemDetail();

      const reason: ValidationMessageCode = getMessageReason(problemDetail);

      expect(reason).toEqual(problemDetail.invalidParams[0].reason);
    });

    it('should return null', () => {
      const problemDetail = createProblemDetail([{ ...createInvalidParam(), reason: null }]);

      const reason: ValidationMessageCode = getMessageReason(problemDetail);

      expect(reason).toBeNull();
    });
  });

  describe('getMessageForInvalidParam', () => {
    const label: string = faker.word.sample();

    it('should return undefined reason', () => {
      const invalidParam: InvalidParam = createInvalidParam();

      const message: string = getMessageForInvalidParam(label, {
        ...invalidParam,
        reason: undefined,
      });

      expect(message).toBeUndefined();
    });

    it('should return message', () => {
      const invalidParam: InvalidParam = createInvalidParam();

      const message: string = getMessageForInvalidParam(label, {
        ...invalidParam,
        reason: ValidationMessageCode.FIELD_DATE_FORMAT_INVALID,
      });
      expect(message).toEqual(VALIDATION_MESSAGES[ValidationMessageCode.FIELD_DATE_FORMAT_INVALID]);
    });

    it('should return message with field placeholder', () => {
      const invalidParam: InvalidParam = createInvalidParam();

      const message: string = getMessageForInvalidParam(label, {
        ...invalidParam,
        reason: ValidationMessageCode.FIELD_INVALID,
      });
      expect(message).toEqual(VALIDATION_MESSAGES[ValidationMessageCode.FIELD_INVALID].replace('{field}', label));
    });

    it('should return message with placeholders', () => {
      const invalidParam: InvalidParam = createInvalidParam();
      const min: string = '1';
      const max: string = '5';

      const message: string = getMessageForInvalidParam(label, {
        ...invalidParam,
        reason: ValidationMessageCode.FIELD_SIZE,
        constraintParameters: [
          { name: 'min', value: min },
          { name: 'max', value: max },
        ],
      });
      expect(message).toEqual(
        VALIDATION_MESSAGES[ValidationMessageCode.FIELD_SIZE]
          .replace('{field}', label)
          .replace('{min}', min)
          .replace('{max}', max),
      );
    });
  });
});