import type {
  FormEvent,
  ReactNode,
  ChangeEvent,
} from 'react'
import {
  useState,
  useEffect,
  useCallback,
} from 'react'

import type { StripeElementChangeEvent } from '@stripe/stripe-js'
import {
  CardNumberElement,
  CardCvcElement,
  CardExpiryElement,
  useStripe,
  useElements,
} from '@stripe/react-stripe-js'

import { useRecoilValue } from 'recoil'

import toUpper from 'lodash/toUpper'

import { useObjectState } from 'hooks'

import { useCardsStore } from 'features/CardsStore'
import { useLexicsStore } from 'features/LexicsStore'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup/store'

import { dataForPayHighlights } from 'pages/HighlightsPage/storeHighlightsAtoms'

import { onePayment } from 'requests/onePayment'

import {
  SelectedCountry,
  SelectedState,
  useCountries,
} from './useCountries'

import {
  isValidAddress,
  isValidName,
  validateFields,
} from './validateFields'

export enum ElementTypes {
  CardAddress = 'cardAddress',
  CardCity = 'cardCity',
  CardCountry = 'cardCountry',
  CardCvc = 'cardCvc',
  CardExpiry = 'cardExpiry',
  CardHolder = 'cardHolder',
  CardNumber = 'cardNumber',
  CardState = 'cardState',
}

export type Props = {
  children?: ReactNode,
  clearInputs?: boolean,
  initialformOpen?: boolean,
  inputsBackground?: string,
  loader?: boolean,
  onAddSuccess?: () => void,
  setClearInputs?: ((clear: boolean) => void) | undefined,
}

const inputState = {
  empty: true,
  focused: false,
}

const initialState = {
  cardAddress: inputState,
  cardCity: inputState,
  cardCountry: inputState,
  cardCvc: inputState,
  cardExpiry: inputState,
  cardHolder: inputState,
  cardNumber: inputState,
  cardState: inputState,
}

export const useFormSubmit = ({
  clearInputs,
  onAddSuccess,
  setClearInputs,
}: Props) => {
  const stripe = useStripe()
  const elements = useElements()
  const { translate } = useLexicsStore()
  const {
    defaultCard,
    isHighlightsPage,
    lastCard,
    onAddCard,
    setError: setCardError,
    setLastCard,
  } = useCardsStore()
  const [name, setName] = useState('')
  const [city, setCity] = useState('')
  const [address, setAddress] = useState('')
  const [usaStateValue, setUsaStateValue] = useState('')
  const [inputStates, setInputStates] = useObjectState(initialState)
  const [errorMessage, setErrorMessage] = useState('')
  const [loader, setLoader] = useState(false)
  const dataHighlights = useRecoilValue(dataForPayHighlights)

  const {
    onSuccessfulHighlights,
    onUnsuccessfulSubscription,
    setShowClearBtn,
  } = useBuyMatchPopupStore()

  const {
    countries,
    selectedCountry,
    selectedState,
    setSelectedCountry,
    setSelectedState,
    usaStates,
  } = useCountries()

  const resetErrors = useCallback(() => {
    setErrorMessage('')
    setCardError('')
  }, [setErrorMessage, setCardError])

  const resetInputs = () => {
    resetErrors()
    setName('')
    setCity('')
    setAddress('')
    setSelectedCountry(null)
    setSelectedState(null)
    elements?.getElement(CardNumberElement)?.clear()
    elements?.getElement(CardCvcElement)?.clear()
    elements?.getElement(CardExpiryElement)?.clear()
    setInputStates(initialState)
    setShowClearBtn(false)
    setClearInputs && setClearInputs(false)
  }

  const setElementTypeState = useCallback((elementType: ElementTypes, value: string) => {
    const elementState = inputStates[elementType]

    setInputStates({
      [elementType]: {
        ...elementState,
        empty: !value,
      },
    })
  }, [inputStates, setInputStates])

  const onNameChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target
    if (isValidName(value)) {
      setName(toUpper(value))
      resetErrors()
      setElementTypeState(ElementTypes.CardHolder, value)
    }
  }

  const onCountryChange = useCallback((country: SelectedCountry) => {
    resetErrors()
    if (country?.id === selectedCountry?.id) return
    setSelectedCountry(country)

    if (selectedCountry?.id !== 194) {
      setSelectedState(null)
    }
    setElementTypeState(ElementTypes.CardCountry, country?.name_eng || '')
  }, [
    resetErrors,
    selectedCountry?.id,
    setElementTypeState,
    setSelectedCountry,
    setSelectedState,
  ])

  const onStateSelect = useCallback((state: SelectedState) => {
    resetErrors()
    if (state?.id === selectedState?.id) return
    setSelectedState(state)
    setUsaStateValue('')
    setElementTypeState(ElementTypes.CardState, state?.name_eng || '')
  }, [resetErrors, selectedState?.id, setElementTypeState, setSelectedState])

  const onCityChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target
    if (!isValidAddress(value)) {
      setErrorMessage(translate('error_city_latin_letters'))
      return
    }
    setCity(value)
    resetErrors()
    setElementTypeState(ElementTypes.CardCity, value)
  }

  const onStateChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target
    setUsaStateValue(value)
  }

  const onAddressChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target
    if (!isValidAddress(value)) {
      setErrorMessage(translate('error_address_latin_letters'))
      return
    }
    setAddress(value)
    resetErrors()
    setElementTypeState(ElementTypes.CardAddress, value)
  }

  const onInputsChange = (e: StripeElementChangeEvent) => {
    const value = inputStates[e.elementType as ElementTypes]

    setInputStates({ [e.elementType]: { ...value, empty: e.empty } })
    resetErrors()
  }

  const onInputsFocus = (elementType: ElementTypes) => () => {
    const value = inputStates[elementType as ElementTypes]
    setInputStates({ [elementType]: { ...value, focused: true } })
  }

  const onInputsBlur = (elementType: ElementTypes) => () => {
    const value = inputStates[elementType as ElementTypes]
    setInputStates({ [elementType]: { ...value, focused: false } })
  }

  const isLabelVisible = (elementType: ElementTypes) => (
    inputStates[elementType].empty && !inputStates[elementType].focused
  )

  const allInputEmpty = Object.values(inputStates).every((inputValue) => inputValue.empty)

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    resetErrors()

    if (!stripe || !elements) {
      // Stripe.js has not loaded yet.
      return
    }

    if (isHighlightsPage && defaultCard && allInputEmpty) {
      setErrorMessage('')
      setLoader(true)
      onePayment({
        cardId: defaultCard?.id,
        order: { ...dataHighlights?.data },
      })
        .then(onSuccessfulHighlights, onUnsuccessfulSubscription)
        .catch(() => setErrorMessage(translate('error_payment_unsuccessful')))
        .finally(() => setLoader(false))
      return
    }

    const fieldError = validateFields({
      address,
      city,
      country: selectedCountry?.name || '',
      name,
      state: (selectedState?.name_eng || ''),
    })

    if (fieldError) {
      setErrorMessage(translate(fieldError))
      return
    }

    const cardNumberElement = elements.getElement(CardNumberElement)
    if (!cardNumberElement) return
    setLoader(true)
    const { error: tokenError, token } = await stripe.createToken(
      cardNumberElement,
      {
        address_city: city,
        address_country: selectedCountry?.name || '',
        address_line1: address,
        address_state: selectedState?.name_eng || '',
        name,
      },
    )

    if (tokenError) {
      setLoader(false)
      setErrorMessage(tokenError.message || '')
    } else if (token) {
      setLoader(true)
      onAddCard(token.id)
        .then(onAddSuccess)
        .then(() => lastCard && isHighlightsPage && setInputStates(initialState))
        .catch(() => setLoader(false))
    }
  }

  useEffect(() => {
    if (lastCard && isHighlightsPage) {
      onePayment({
        cardId: lastCard,
        order: { ...dataHighlights?.data },
      })
        .then(onSuccessfulHighlights, onUnsuccessfulSubscription)
        .catch(() => setErrorMessage(translate('error_payment_unsuccessful')))
        .finally(() => setLoader(false))
    }
    setLastCard(null)
    // eslint-disable-next-line
  }, [lastCard])

  useEffect(() => {
    setCardError('')
  }, [setCardError])

  useEffect(() => {
    if (!allInputEmpty) setShowClearBtn(true)
  }, [allInputEmpty, setShowClearBtn])

  useEffect(() => {
    if (clearInputs) resetInputs()
    // eslint-disable-next-line
  }, [clearInputs])

  return {
    address,
    city,
    countries,
    errorMessage,
    handleSubmit,
    isLabelVisible,
    loader,
    name,
    onAddressChange,
    onCityChange,
    onCountryChange,
    onInputsBlur,
    onInputsChange,
    onInputsFocus,
    onNameChange,
    onStateChange,
    onStateSelect,
    selectedCountry,
    selectedState,
    usaStateValue,
    usaStates,
  }
}
