// REGEXS
import {
  addSeconds,
  differenceInCalendarYears,
  endOfDay,
  isAfter,
  isBefore,
  isEqual,
  isWithinInterval,
  startOfDay,
  subSeconds,
} from 'date-fns';
import i18next, { TFunction } from 'i18next';
import { isValidPhoneNumber } from 'libphonenumber-js';
import { Theme } from '@material-hu/mui/styles';

import { CheckboxGridValue, Option, Row } from 'src/types/forms';
import { PasswordType } from 'src/types/instance';
import { LokaliseTFunction } from 'src/utils/i18n';
import { addPhoneNumberPrefix, isPhoneNumberEmpty } from 'src/utils/phone';

const emailRegex =
  /^\s*(([^<>()·=~ºªÇ¨?¿*^|#¢∞¬÷"$%"≠´}{![\]\\.,;:\s@"]+(\.[^<>·$%&/=~ºªÇ¨?¿*^|#¢∞¬÷""≠´}{!()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))\s*$/;
const urlRegex =
  /^(http(s)?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/;
const alphanumericRegex = /^[a-záéíóúäëïºöüA-ZÁÉÍÓÚÄËÏÖÜ0-9 ]*$/g;
const onlyTextRegex = /^[a-zA-Z\s]*$/;
const onlyNumberRegex = /^[0-9]*$/g;
const validNumberFormat = /^[+]?\d+$/;
const validDecimalFormat = /^(-)?(\d)+((\.|,)?\d{1,2})?$/;
const validDecimalFormatDotOnly = /^(-)?(\d)+(\.\d{1,4})?$/;
const userRegex = /^(?:\w|[-.@+]){1,100}$/;
// for strong password
export const MIN_PASSWORD_LENGTH = 8;
const withUpperCaseLetter = /^(?=.*?[A-Z])/g;
const withLowerCaseLetter = /^(?=.*?[a-z])/g;
/* const withSpecialCharacter = /^(?=.*?[#?!@$%^*-])/g; */
const withSpecialCharacter = /^(?=.*?[#?!@$%^*-/(){}=.,;:[[\]\\_+])/g;
const withNumber = /^(?=.*?[0-9])/g;
const withMinLength = new RegExp(`^.{${MIN_PASSWORD_LENGTH},100}$`);

export const zeroWidthSpaceRegex = /\u200B/g;

// VALIDATIONS

export const validateMinLengthWithoutMessage = {
  validate: (value: string) =>
    !!value.match(withMinLength) ||
    i18next.t('validations:min_password_required'),
};

export const validateUpperCaseLetter = {
  validate: (value: string) =>
    !!value.match(withUpperCaseLetter) ||
    i18next.t('validations:min_password_required'),
};
export const validateLowerCaseLetter = {
  validate: (value: string) =>
    !!value.match(withLowerCaseLetter) ||
    i18next.t('validations:min_password_required'),
};

export const validateWithSpecialCharacter = {
  validate: (value: string) =>
    !!value.match(withSpecialCharacter) ||
    i18next.t('validations:min_password_required'),
};

export const validateWithNumber = {
  validate: (value: string) =>
    !!value.match(withNumber) || i18next.t('validations:min_password_required'),
};

const possibleRulesStrongPassword = {
  validateMinLength: { validateMinLengthWithoutMessage: true },
  validateUpperCaseLetter: { validateUpperCaseLetter: true },
  validateLowerCaseLetter: { validateLowerCaseLetter: true },
  validateNumbers: { containsNumbers: true },
  validateSpecialCharacter: { containsSpecialCharacter: true },
};

const possibleValidationStrongPassword = (
  t: TFunction | LokaliseTFunction,
) => ({
  validateMinLengthWithoutMessage: {
    validation: (value: string) =>
      typeof validateMinLengthWithoutMessage.validate(value) === 'boolean',
    text: t('validations:min_length_password'),
  },
  validateUpperCaseLetter: {
    validation: (value: string) =>
      typeof validateUpperCaseLetter.validate(value) === 'boolean',
    text: t('validations:upper_case_password'),
  },
  validateLowerCaseLetter: {
    validation: (value: string) =>
      typeof validateLowerCaseLetter.validate(value) === 'boolean',
    text: t('validations:lower_case_password'),
  },
  containsNumbers: {
    validation: (value: string) =>
      typeof validateWithNumber.validate(value) === 'boolean',
    text: t('validations:number_password'),
  },
  containsSpecialCharacter: {
    validation: (value: string) =>
      typeof validateWithSpecialCharacter.validate(value) === 'boolean',
    text: t('validations:symbol_password'),
  },
});

export const getRulesOfStrongPassword = (passwordType: PasswordType) => {
  if (!passwordType) return {};

  return Object.entries(passwordType).reduce(
    (total, current) => ({
      ...total,
      ...(current[1] && {
        ...possibleRulesStrongPassword[
          current[0] as keyof typeof possibleRulesStrongPassword
        ],
      }),
    }),
    {},
  );
};

export const checkPasswordType = (
  passwordType: PasswordType,
  t: TFunction | LokaliseTFunction,
  password = '',
) => {
  const rules = getRulesOfStrongPassword(passwordType);
  const validationRules = possibleValidationStrongPassword(t);

  return Object.entries(rules).map(([key]) => {
    const ruleKey = key as keyof typeof validationRules;

    if (ruleKey in validationRules) {
      return {
        isValid: validationRules[ruleKey].validation(password),
        text: validationRules[ruleKey].text,
      };
    }

    return {
      isValid: false,
      text: '',
    };
  });
};

export const getCorrectColorTextStrongPassword = (
  isDirtyFiled: boolean,
  isValid: boolean,
  theme: Theme,
) => {
  if (!isDirtyFiled) {
    return theme.palette.new.text.neutral.lighter;
  }
  if (isValid) {
    return theme.palette.new.text.feedback.success;
  }
  return theme.palette.new.text.feedback.error;
};

export const validateRequired = (message?: string) => ({
  required: {
    value: true,
    message: message || i18next.t('validations:not_empty') || '',
  },
});

export const validateRequiredWithoutMessage = (isRequired: boolean) => ({
  required: {
    value: isRequired,
  },
});

export const validateRequiredPhoneNumber = (code: string) => ({
  validate: (value: string) => {
    const errorMessage =
      (i18next.t('validations:not_empty') as string) || false;

    if (!value) return errorMessage;

    return isPhoneNumberEmpty(value, code) ? errorMessage : true;
  },
});

export const validateRequiredCheckboxGrid = (
  rows: Row[],
  options: Option[],
) => ({
  validate: (value: CheckboxGridValue) => {
    if (!value) return false;

    return rows.every(row =>
      options.some(option => {
        if (!value[row.id]) return false;
        return option.description === value[row.id][0];
      }),
    );
  },
});

export const validateEmail = () => ({
  pattern: {
    value: emailRegex,
    message: i18next.t('validations:invalid_format_email'),
  },
});

export const validateUrl = () => ({
  pattern: {
    value: urlRegex,
    message: i18next.t('validations:invalid_format_url'),
  },
});

export const validateUser = () => ({
  pattern: {
    value: userRegex,
    message: i18next.t('validations:invalid_user'),
  },
});

export const validateNumberFormat = () => ({
  pattern: {
    value: validNumberFormat,
    message: i18next.t('validations:invalid_number_field'),
  },
});

export const validateDecimalFormat = () => ({
  pattern: {
    value: validDecimalFormat,
    message: i18next.t('validations:invalid_decimal_field'),
  },
});

export const validateDecimalFormatDotOnly = () => ({
  pattern: {
    value: validDecimalFormatDotOnly,
    message: i18next.t('validations:invalid_decimal_field_dot_only'),
  },
});

export const validateAlphanumeric = {
  validate: (value: string) =>
    !!value.match(alphanumericRegex) ||
    (i18next.t('validations:alphanumeric') as string),
};

export const validateOnlyText = {
  validate: (value: string) =>
    !!value.match(onlyTextRegex) ||
    (i18next.t('validations:only_text') as string),
};

export const validatePhoneNumber = (code: string) => ({
  validate: (value: string) => {
    if (!value) return true;

    const fullCode = addPhoneNumberPrefix(code);
    const fullNumber = addPhoneNumberPrefix(value);
    const isEmpty = fullCode === fullNumber;

    return (
      isEmpty ||
      isValidPhoneNumber(fullNumber) ||
      (i18next.t('validations:invalid_phone_number') as string)
    );
  },
});

export const validateOnlyNumber = {
  validate: (value: string) =>
    !value ||
    !!value.match(onlyNumberRegex) ||
    (i18next.t('validations:only_numbers') as string),
};

export const validateMinLength = (minValue: number) => ({
  minLength: {
    value: minValue,
    message: i18next.t('validations:min_length', { count: minValue }),
  },
});

export const validateMaxLength = (maxValue: number) => ({
  maxLength: {
    value: maxValue,
    message: i18next.t('validations:max_length', { count: maxValue }),
  },
});

export const validateArrayMaxLength = (maxValue: number, message?: string) => ({
  validate: (value: any[]) =>
    value.length <= maxValue ||
    message ||
    (i18next.t('validations:array_max_length', { count: maxValue }) as string),
});

export const validateEqualLength = (equalValue: number) => ({
  validate: (value: string) =>
    value.length === equalValue ||
    (i18next.t('validations:equal_length', { count: equalValue }) as string),
});

export const isDate = (date: any) =>
  date instanceof Date && !isNaN(date as any);

export const validateIsAfterDate = (
  dateToCompare: string | Date,
  allowEqual: boolean,
  message: string,
) => ({
  validate: (date: Date) => {
    if (
      !isDate(date) ||
      !isDate(dateToCompare) ||
      (allowEqual && isEqual(date, dateToCompare as Date))
    ) {
      return true;
    }
    return isAfter(date, dateToCompare as Date) || message;
  },
});

export const validateIsBeforeDate = (
  dateToCompare: string | Date,
  allowEqual: boolean,
  message: string,
) => ({
  validate: (date: Date) => {
    if (
      !isDate(date) ||
      !isDate(dateToCompare) ||
      (allowEqual && isEqual(date, dateToCompare as Date))
    ) {
      return true;
    }
    return isBefore(date, dateToCompare as Date) || message;
  },
});

export const validateEqual = (equalValue: any, message: string) => ({
  validate: (value: any) => value === equalValue || message,
});

export const validateNotEqualArray = (
  equalValueArray: any[],
  message: string,
) => ({
  validate: (value: any) => {
    let counter = 0;
    equalValueArray.forEach((item: any) => {
      if (item.email === value) {
        counter++;
      }
    });
    return counter < 2 || message;
  },
});

export const validateFormatDate = (message?: string) => ({
  validate: (date: Date) => {
    if (!date) return true;
    const value = date.getDate();
    return (
      !Number.isNaN(value) ||
      message ||
      (i18next.t('validations:invalid_date_field') as string)
    );
  },
});

const MAXIMUM_YEARS_TO_ASK = 5;

export const validateDateYearsBetweenAndBefore = {
  validate: (date: Date) => {
    if (!date) return true;
    const today = new Date();
    const difference = Math.abs(differenceInCalendarYears(today, date));
    return (
      difference <= MAXIMUM_YEARS_TO_ASK ||
      (i18next.t('validations:maximum_years_to_ask') as string)
    );
  },
};

export const validateDatesCollision = (
  datesRange: { start: string; end: string }[],
  errorMessage: string,
  allowNightShift = false,
) => ({
  validate: (date: Date) => {
    if (!isDate(date) || datesRange.length === 0) {
      return true;
    }
    const dateToCheck = new Date(date);
    // It could happen that one of the range's dates are undefined so in that case we compare against start/end of date
    return (
      !datesRange.some(range => {
        const intervalStart = addSeconds(
          range.start ? new Date(range.start) : startOfDay(new Date(range.end)),
          1,
        );
        const intervalEnd = subSeconds(
          range.end ? new Date(range.end) : endOfDay(new Date(range.start)),
          1,
        );
        if (allowNightShift && intervalStart > intervalEnd) {
          intervalEnd.setDate(intervalEnd.getDate() + 1);
        }
        return isWithinInterval(dateToCheck, {
          start: intervalStart,
          end: intervalEnd,
        });
      }) || errorMessage
    );
  },
});

export const validateMinValueAllowed = (minValue: number) => ({
  min: {
    value: minValue,
    message: i18next.t('validations:min_value', { min: minValue }),
  },
});

export const validateRequiredWithOptional = (
  optionalFields: any[],
  message: string,
) => ({
  validate: (value: any) => {
    if (!value && optionalFields.some(field => field)) {
      return message;
    }
    return true;
  },
});

export const validateAtLeastOneChecked = (values: boolean[]) => ({
  validate: () => {
    if (!values.length) return false;
    const hasAnyChecked = values.some(v => v);
    return hasAnyChecked;
  },
});
