import _get from 'lodash/get'
import _has from 'lodash/has'
import {SubmissionError} from 'redux-form/es/SubmissionError'
import {api} from 'tocco-util'

import rest from '../rest'
import formErrors from './formErrors'
import {formValuesToFlattenEntity, getDirtyFields, validationErrorToFormError} from './reduxForm'
import validators from './validators'

const OUTDATED_ENTITY_ERROR_CODE = 'OUTDATED_ENTITY'

const hasError = errors => errors && Object.keys(errors).length > 0

const localeValidation = async (values, fieldDefinitions) => {
  let errors = {}
  for (const fieldDefinition of fieldDefinitions) {
    const value = values[fieldDefinition.path]
    if (validators.valueDefined(value)) {
      for (const asyncValidatorKey in validators.asyncValidators) {
        if (_has(fieldDefinition, ['validation', asyncValidatorKey])) {
          const validatorValue = _get(fieldDefinition, ['validation', asyncValidatorKey])
          const validator = validators.asyncValidators[asyncValidatorKey]
          const validatorErrors = await validator(value, validatorValue)
          if (validatorErrors) {
            errors = formErrors.addErrors(errors, fieldDefinition.path, validatorErrors)
          }
        }
      }
    }
  }
  return errors
}

const validateRequest = (formValues, initialValues, mode) => {
  const dirtyFields = getDirtyFields(initialValues, formValues)
  const flattenEntity = formValuesToFlattenEntity(formValues)
  const entity = api.toEntity(flattenEntity, dirtyFields)
  const options = {
    queryParams: {_validate: true},
    method: mode === 'create' ? 'POST' : 'PATCH',
    headers: {'X-Client': 'rest'},
    body: entity,
    acceptedStatusCodes: [403],
    acceptedErrorCodes: [OUTDATED_ENTITY_ERROR_CODE]
  }

  return rest.simpleRequest(`entities/2.0/${entity.model}${entity.key ? `/${entity.key}` : ''}`, options).then(resp => {
    const body = resp.body
    if (resp.status === 403) {
      return {}
    }
    if (body.valid) {
      return {}
    }

    if (body.errorCode === OUTDATED_ENTITY_ERROR_CODE) {
      return formErrors.outdatedResponseToFormError(body, entity)
    }

    return validationErrorToFormError(entity, body.errors)
  })
}

export const submitValidation = (formValues, initialValues, mode) =>
  validateRequest(formValues, initialValues, mode).then(errors => {
    if (hasError(errors)) {
      throw new SubmissionError(errors)
    }
  })

export const asyncValidation = async (formValues, initialValues, fieldDefinitions, mode) => {
  const localeValidationErrors = await localeValidation(formValues, fieldDefinitions)
  if (hasError(localeValidationErrors)) {
    throw localeValidationErrors
  }

  const validateRequestErrors = await validateRequest(formValues, initialValues, mode)
  if (hasError(validateRequestErrors)) {
    throw validateRequestErrors
  }
}

import _get from 'lodash/get'
import _has from 'lodash/has'
import {SubmissionError} from 'redux-form/es/SubmissionError'
import {api} from 'tocco-util'

import rest from '../rest'
import formErrors from './formErrors'
import {formValuesToFlattenEntity, getDirtyFields, validationErrorToFormError} from './reduxForm'
import validators from './validators'

const OUTDATED_ENTITY_ERROR_CODE = 'OUTDATED_ENTITY'

const hasError = errors => errors && Object.keys(errors).length > 0

const localeValidation = async (values, fieldDefinitions) => {
  let errors = {}
  for (const fieldDefinition of fieldDefinitions) {
    const value = values[fieldDefinition.path]
    if (validators.valueDefined(value)) {
      for (const asyncValidatorKey in validators.asyncValidators) {
        if (_has(fieldDefinition, ['validation', asyncValidatorKey])) {
          const validatorValue = _get(fieldDefinition, ['validation', asyncValidatorKey])
          const validator = validators.asyncValidators[asyncValidatorKey]
          const validatorErrors = await validator(value, validatorValue)
          if (validatorErrors) {
            errors = formErrors.addErrors(errors, fieldDefinition.path, validatorErrors)
          }
        }
      }
    }
  }
  return errors
}

const validateRequest = (formValues, initialValues, mode) => {
  const dirtyFields = getDirtyFields(initialValues, formValues)
  const flattenEntity = formValuesToFlattenEntity(formValues)
  const entity = api.toEntity(flattenEntity, dirtyFields)
  const options = {
    queryParams: {_validate: true},
    method: mode === 'create' ? 'POST' : 'PATCH',
    headers: {'X-Client': 'rest'}, // client type REST does not use client questions, which would interrupt validation
    body: entity,
    acceptedStatusCodes: [403],
    acceptedErrorCodes: [OUTDATED_ENTITY_ERROR_CODE]
  }

  return rest.simpleRequest(`entities/2.0/${entity.model}${entity.key ? `/${entity.key}` : ''}`, options).then(resp => {
    const body = resp.body
    if (resp.status === 403) {
      return {}
    }
    if (body.valid) {
      return {}
    }

    if (body.errorCode === OUTDATED_ENTITY_ERROR_CODE) {
      return formErrors.outdatedResponseToFormError(body, entity)
    }

    return validationErrorToFormError(entity, body.errors)
  })
}

export const submitValidation = (formValues, initialValues, mode) =>
  validateRequest(formValues, initialValues, mode).then(errors => {
    if (hasError(errors)) {
      throw new SubmissionError(errors)
    }
  })

export const asyncValidation = async (formValues, initialValues, fieldDefinitions, mode) => {
  const localeValidationErrors = await localeValidation(formValues, fieldDefinitions)
  if (hasError(localeValidationErrors)) {
    throw localeValidationErrors
  }

  const validateRequestErrors = await validateRequest(formValues, initialValues, mode)
  if (hasError(validateRequestErrors)) {
    throw validateRequestErrors
  }
}

add explanation for validation client type