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

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

import toUpper from 'lodash/toUpper'

import { useObjectState } from 'hooks'

import { useCardsStore } from 'features/CardsStore'
import { useLexicsStore } from 'features/LexicsStore'

import { SelectedCountry, 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',
}

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

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

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

export const useFormSubmit = ({ onAddSuccess }: Props) => {
  const stripe = useStripe()
  const elements = useElements()
  const { translate } = useLexicsStore()
  const { onAddCard, setError: setCardError } = useCardsStore()
  const [name, setName] = useState('')
  const [city, setCity] = useState('')
  const [address, setAddress] = useState('')
  const [inputStates, setInputStates] = useObjectState(initialState)
  const [errorMessage, setErrorMessage] = useState('')
  const [loader, setLoader] = useState(false)

  const {
    countries,
    selectedCountry,
    setSelectedCountry,
  } = useCountries()

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

  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)

    setElementTypeState(ElementTypes.CardCountry, country?.name_eng || '')
  }, [resetErrors, selectedCountry?.id, setElementTypeState, setSelectedCountry])

  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 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 handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    resetErrors()

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

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

    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,
        name,
      },
    )

    if (tokenError) {
      setLoader(false)
      setErrorMessage(tokenError.message || '')
    } else if (token) {
      setLoader(true)
      onAddCard(token.id)
        .then(onAddSuccess)
        .finally(() => setLoader(false))
    }
  }

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

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