import { isAfter, isValid } from 'date-fns'

import { extractNumbers } from './extractors'

export interface Field<T> {
  value: T
  invalid: boolean
  success: boolean
  error: boolean
  errorText?: string
}

export function isFilled(value: any) {
  return !(value === '' || value === undefined || value === null || (Array.isArray(value) && value.length === 0))
}

export interface FieldValidation {
  error: boolean
  errorText?: string
}

export type FormValidation<S> = Partial<Record<keyof S, FieldValidation>>

export function validateRequiredField(field: Field<any>): FieldValidation {
  const error = !isFilled(field.value)
  return {
    error,
    errorText: error ? 'O campo é obrigatório.' : undefined,
  }
}

export const validateCPF = (field: Field<string>): FieldValidation => {
  const cpf = field.value
  if (cpf.length < 11) {
    return {
      error: true,
      errorText: 'O CPF informado é inválido.',
    }
  }
  switch (cpf) {
    case '11111111111':
    case '22222222222':
    case '33333333333':
    case '44444444444':
    case '55555555555':
    case '66666666666':
    case '77777777777':
    case '88888888888':
    case '99999999999':
    case '00000000000':
      return {
        error: true,
        errorText: 'O CPF informado é inválido.',
      }
  }

  let firstDigitSum = 0
  let cpfIndex = 0
  for (let i = 10; i > 1; i--) {
    firstDigitSum += i * Number(cpf[cpfIndex])
    cpfIndex++
  }
  if ((firstDigitSum % 11 < 2 ? 0 : 11 - (firstDigitSum % 11)).toString() === cpf[9]) {
    let secondDigitSum = 0
    cpfIndex = 0
    for (let i = 11; i > 1; i--) {
      secondDigitSum += i * Number(cpf[cpfIndex])
      cpfIndex++
    }
    const valid = (secondDigitSum % 11 < 2 ? 0 : 11 - (secondDigitSum % 11)).toString() === cpf[10]
    return {
      error: !valid,
      errorText: !valid ? 'O CPF informado é inválido.' : undefined,
    }
  } else {
    return {
      error: true,
      errorText: 'O CPF informado é inválido.',
    }
  }
}

export const validateCNPJ = (cnpj: string) => {
  const seq = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]

  let firstDigitSum = 0
  let cnpjIndex = 0
  for (let i = 0; i < 12; i++) {
    firstDigitSum += seq[i] * Number(cnpj[cnpjIndex])
    cnpjIndex++
  }
  if ((firstDigitSum % 11 < 2 ? 0 : 11 - (firstDigitSum % 11)).toString() === cnpj[12]) {
    let secondDigitSum = 0
    cnpjIndex = 0
    const seq2 = [6, ...seq]
    for (let i = 0; i < 13; i++) {
      secondDigitSum += seq2[i] * Number(cnpj[cnpjIndex])
      cnpjIndex++
    }

    const valid = (secondDigitSum % 11 < 2 ? 0 : 11 - (secondDigitSum % 11)).toString() === cnpj[13]
    return {
      error: !valid,
      errorText: !valid ? 'O CNPJ informado é inválido.' : undefined,
    }
  } else {
    return {
      error: true,
      errorText: 'O CNPJ informado é inválido.',
    }
  }
}

// export const isValidCPFCNPJ = (cpfOrCnpj: string) => {
//   switch (cpfOrCnpj.length) {
//     case 0:
//       return false
//     case 11:
//       return validateCPF(cpfOrCnpj)
//     case 14:
//       return validateCNPJ(cpfOrCnpj)

//     default:
//       return false
//   }
// }

export const isValidEmail = (email: string) => !!email && email.indexOf('@') !== -1

export function validateEmail(field: Field<string>): FieldValidation {
  const error = !isValidEmail(field.value)
  return {
    error,
    errorText: error ? 'O email é inválido.' : undefined,
  }
}

export const isValidCPF = (text: string) => {
  const cpf = extractNumbers(text)
  return cpf.length === 11
}

export function validateDate(field: Field<Date>): FieldValidation {
  try {
    if (field.value !== null) field.value.toISOString()
    return {
      error: false,
    }
  } catch {
    return {
      error: true,
      errorText: 'A data é inválida.',
    }
  }
}

interface DateIntervalState {
  startDate: Field<Date>
  endDate: Field<Date>
}
export function validateDateInterval(state: DateIntervalState): FormValidation<DateIntervalState> {
  if (isValid(state.startDate.value) && isValid(state.endDate.value) && isAfter(state.startDate.value, state.endDate.value)) {
    return {
      endDate: {
        error: true,
        errorText: 'Data de fim é inferior à data de início.',
      },
    }
  }
  return {}
}

/**
 * Tests whether a given numeric value is beween a range.
 * If no value is provided to `min` or `max`,
 * they are respectivelly assumed to be `NEGATIVE_INFINITY` and `POSITIVE_INFINITY`.
 * @param value The number to check
 * @param min The lower bound of the range
 * @param max The upper bound of the range
 * @returns *true* if `value` is in range, *false* otherwise
 */
export function isBetween(value: number, min = Number.NEGATIVE_INFINITY, max = Number.POSITIVE_INFINITY) {
  return value >= min && value <= max
}

/**
 * Tests whether a given numeric value is less than the specified upper bound.
 * @param value The number to check
 * @param max The upper bound to be checked
 * @returns *true* if `value` is less than `max`, *false* otherwise
 */
export function isLessThan(value: number, max: number) {
  return value <= max
}

/**
 * Tests whether a given numeric value is greater than the specified lower bound.
 * @param value The number to check
 * @param min The lower bound to be checked
 * @returns *true* if `value` is greater than `min`, *false* otherwise
 */
export function isGreaterThan(value: number, min: number) {
  return min <= value
}

export const createLengthValidator =
  (minLength?: number, maxLength?: number) =>
  (field: Field<string>): FieldValidation => {
    const length = field.value.length
    if (maxLength && !isLessThan(length, maxLength)) {
      return {
        error: true,
        errorText: `O campo deve ter no máximo ${maxLength} caracteres.`,
      }
    } else if (minLength && !isGreaterThan(length, minLength)) {
      return {
        error: true,
        errorText: `O campo deve ter no mínimo ${minLength} caracteres.`,
      }
    }
    return {
      error: false,
      errorText: undefined,
    }
  }

const phoneRegexp = /[1-9]{2}9[1-9][0-9]{7}/
export const isValidPhone = (text: string) => {
  const phone = extractNumbers(text)
  return phone.length === 11 && phoneRegexp.test(phone)
}
