import { useFormik } from 'formik'
import React, { useMemo, useState } from 'react'
import * as Yup from 'yup'

import { ButtonAria, ErrorPopover, Icon } from '$core-components'
import { FormControlType } from '$theme-components/personalInfo/constants'
import FieldByModel from '$theme-components/personalInfo/FieldByModel'
import TextField from '$theme-components/personalInfo/TextField'
import { useOnSubmitInvalid, useTemporaryToggle } from '$themehooks'
import { t } from '$themelocalization'
import { UStoreProvider } from '@ustore/core'

import './Form.scss'

const Form = ({ fields, onCancel }) => {
  const [isValidationPopoverOpen, setIsValidationPopoverOpen] = useState(false)
  const [isJustUpdated, setIsJustUpdated] = useTemporaryToggle(2000)

  const formik = useFormik({
    initialValues: useMemo(
      () => ({
        oldPassword: '',
        newPassword: '',
        confirmNewPassword: '',
        ...Object.fromEntries(fields.map((field) => [field.Name, field.Value])),
      }),
      [fields],
    ),
    validationSchema: useMemo(
      () =>
        Yup.object().shape(
          {
            oldPassword: Yup.string().when('newPassword', ([newPassword], schema) =>
              newPassword
                ? schema.required(
                    t('PersonalInformation.PleaseEnter', { FieldName: t('PersonalInformation.OldPassword') }),
                  )
                : schema,
            ),
            newPassword: Yup.string().when('oldPassword', ([oldPassword], schema) =>
              oldPassword
                ? schema.required(
                    t('PersonalInformation.PleaseEnter', { FieldName: t('PersonalInformation.NewPassword') }),
                  )
                : schema,
            ),
            confirmNewPassword: Yup.string()
              .oneOf([Yup.ref('newPassword')], t('PersonalInformation.PasswordsShouldMatch'))
              .when('newPassword', ([newPassword], schema) =>
                newPassword
                  ? schema.required(
                      t('PersonalInformation.PleaseEnter', {
                        FieldName: t('PersonalInformation.ConfirmNewPassword'),
                      }),
                    )
                  : schema,
              ),
            ...Object.fromEntries(fields.map((field) => [field.Name, makeSchema(field)])),
          },
          // Cross-dependent fields (https://github.com/jquense/yup/issues/176#issuecomment-369925782):
          [['oldPassword', 'newPassword']],
        ),
      [fields],
    ),
    onSubmit: async ({ oldPassword, newPassword, confirmNewPassword, ...accountInformation }, { setErrors }) => {
      const dateFieldNames = new Set(
        fields.filter((f) => f.ControlConfiguration?.Type === FormControlType.DateTimePicker).map((f) => f.Name),
      )
      const response = await UStoreProvider.api.personalInfo.updatePersonalInfo({
          Fields: Object.entries(accountInformation).map(([Name, Value]) => ({
            Name,
            Value: dateFieldNames.has(Name) ? formatDateValue(Value) : Value,
          })),
        })
      const errorEntries = response.Errors?.filter((error) => error.Error?.Type === ErrorType.FormValueInvalid)
        .map((error) => {
          return [error.FieldName, error.Error?.Message]
        })
        ?.filter(([Name, Message]) => Name && Message)

      if (errorEntries?.length) {
        setErrors(Object.fromEntries(errorEntries))
        return
      }

      if (oldPassword && newPassword) {
        try {
          await UStoreProvider.api.changePassword.changePassword(oldPassword, newPassword)
        } catch (error) {
          if (error?.Message) {
            const isOldPasswordError = error.Message.includes('Please enter the Old Password')
              setErrors({ [isOldPasswordError ? 'oldPassword' : 'newPassword']: error.Message })
            return
          }
          throw error
        }
      }
      await UStoreProvider.state.store.loadCurrentUser()

      setIsJustUpdated()
    },
  })

  useOnSubmitInvalid(() => setIsValidationPopoverOpen(true), formik)

  return (
    <>
      <div className="form">
        <h3 className="form-heading">
          <Icon name="lock.svg" size={23} />
          {t('PersonalInformation.PasswordSettings')}
        </h3>
        <TextField
          autoComplete="off"
          error={formik.touched.oldPassword && formik.errors.oldPassword}
          label={t('PersonalInformation.OldPassword')}
          name="oldPassword"
          onBlur={formik.handleBlur}
          onInput={formik.handleChange}
          type="password"
          value={formik.values.oldPassword}
        />
        <TextField
          autoComplete="new-password"
          error={renderNewPasswordError(formik.touched.newPassword && formik.errors.newPassword)}
          label={t('PersonalInformation.NewPassword')}
          name="newPassword"
          onBlur={formik.handleBlur}
          onInput={formik.handleChange}
          type="password"
          value={formik.values.newPassword}
        />
        <TextField
          autoComplete="new-password"
          error={formik.touched.confirmNewPassword && formik.errors.confirmNewPassword}
          label={t('PersonalInformation.ConfirmNewPassword')}
          name="confirmNewPassword"
          onBlur={formik.handleBlur}
          onInput={formik.handleChange}
          type="password"
          value={formik.values.confirmNewPassword}
        />
        <h3 className="form-heading">
          <Icon name="paper.svg" size={22} />
          {t('PersonalInformation.AccountInformation')}
        </h3>
        {fields.map((field) => (
          <FieldByModel
            error={formik.touched[field.Name] && formik.errors[field.Name]}
            field={field}
            key={field.Name}
            label={field.DisplayName}
            name={field.Name}
            onBlur={formik.handleBlur}
            onInput={formik.handleChange}
            value={formik.values[field.Name]}
          />
        ))}
      </div>
      <div className="form-actions">
        <div className="form-actions-col">
          <ButtonAria className="button button-secondary" onClick={onCancel} text={t('PersonalInformation.Cancel')} />
        </div>
        {isJustUpdated ? (
          <div className="form-actions-col success">
            <span className="gte-xs">{t('PersonalInformation.UpdatedLong')}</span>
            <span className="xs">{t('PersonalInformation.Updated')}</span>
            <Icon name="personal-information-success.svg" size={15} />
          </div>
        ) : (
          <ErrorPopover
            className="form-actions-col"
            content={
              <>
                <Icon name="personal-information-error.svg" size={18} />
                {t('PersonalInformation.FormIsInvalid')}
              </>
            }
            onClose={() => setIsValidationPopoverOpen(false)}
            open={isValidationPopoverOpen}
          >
            <ButtonAria
              className="button button-primary"
              isLoading={formik.isSubmitting}
              onClick={formik.handleSubmit}
              text={t('PersonalInformation.Update')}
            />
          </ErrorPopover>
        )}
      </div>
    </>
  )
}

function makeSchema(field) {
  let schema = Yup.string()

  for (const validation of field?.Validations) {
    if (validation.Type === FormControlValidationType.Mandatory) {
      schema = schema.required(
        validation.Message || t('PersonalInformation.MustEnter', { FieldName: field.DisplayName }),
      )
    }

      if (validation.Type === FormControlValidationType.RegularExpression && validation.RegularExpression) {
          let regexString = validation.RegularExpression
          if (regexString.startsWith('/') && regexString.endsWith('/')) {
              regexString = regexString.slice(1, -1)
          }
          regexString = regexString.replace(/^\\\//, '')
          regexString = regexString.replace(/\\\/$/, '')
          schema = schema.matches(new RegExp(regexString), {
              excludeEmptyString: true,
              message: validation.Message || t('PersonalInformation.MustEnterCorrect', {FieldName: field.DisplayName}),
          })
      }
  }

  return schema
}

const FormControlValidationType = {
  Mandatory: 1,
  RegularExpression: 2,
}

const ErrorType = {
  // Other error types are ommited since they are irrelevant to this module.
  FormValueInvalid: 17,
}

function formatDateValue(value) {
  if (!value) return value
  const [year, month, day] = value.split('-')
  return (year && month && day) ? `${day}/${month}/${year}` : value
}

function renderNewPasswordError(error) {
  if (!error || typeof error !== 'string' || !error.includes('&')) return error
  const parts = error.split('&').map((e) => e.trim()).filter(Boolean)
  if (parts.length <= 1) return error
  return (
    <>
      {parts[0]}
      <span className="password-error-info">
        <Icon name="info.svg" width="16px" height="16px" className="info-icon" title="" />
        <ul className="password-error-info-text">
          {parts.slice(1).map((err, i) => (
            <li key={i}>{err}</li>
          ))}
        </ul>
      </span>
    </>
  )
}

export default Form
