import {
  Elements,
  CardNumberElement,
  CardCvcElement,
  CardExpiryElement,
  useStripe,
  useElements
} from '@stripe/react-stripe-js'
import { loadStripe, Stripe, SetupIntentResult, PaymentMethodResult } from '@stripe/stripe-js'
import { Alert, Box, Divider, Group, Stack, TextInput, Typography } from '@tidbcloud/uikit'
import { Form, FormSelect, FormTextInput } from '@tidbcloud/uikit/biz'
import { IconAlertTriangle } from '@tidbcloud/uikit/icons'
import axios from 'axios'
import { cloneDeep } from 'lodash-es'
import { observer } from 'mobx-react'
import React, { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router-dom'

import { log } from 'common/log'
import { LOG_ERROR_SCENE } from 'common/log/logger'
import { FormsBillingDetails, FormsStripePaymentMethod } from 'dbaas/services'
import { setupIntentConfirm, updatePaymentMethod } from 'dbaas/services'
import useStores from 'dbaas/stores/useStores'
import {
  BILLING_COUNTRY_OPTIONS,
  isAllowedOtherCountryOrRegion,
  OTHER_COUNTRY_OR_REGION
} from 'dbaas/utils/countriesData'
import { eventTracking } from 'dbaas/utils/tracking'
import {
  addressRules,
  cardholderNameRules,
  cityRules,
  postalCodeRules,
  provinceRules
} from 'dbaas/utils/validationRules'

import { CardCVCInput, CardExpiryInput, CardNumberInput } from '../CardElement'

import { handleSetupCard } from './helper'

// use custom fonts for stripe iframe elements
// https://github.com/stripe/react-stripe-elements/issues/179
// https://stripe.com/docs/js/appendix/css_font_source_object#css_font_source_object-cssSrc
export const STRIPE_ELEMENTS_FONT_SRC =
  'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'

export const ErrorMessage: React.FC<React.PropsWithChildren<{ title: string }>> = ({ children }) => (
  <Box className="word-break" c="red.7" role="alert" fz={500} sx={{ wordBreak: 'break-word', whiteSpace: 'normal' }}>
    <Box fw={600} fz={18} ta="center"></Box>
    {children}
  </Box>
)

interface IProps {
  originPaymentMethod?: FormsStripePaymentMethod
  onComplete: (payload?: SetupIntentResult | PaymentMethodResult, billingDetails?: PaymentFormValues) => void
  onError?: (error: any) => void
  onModalClose: () => void
  isEdit: boolean
}

const emptyBillingDetails = {
  name: '',
  country_name: ''
}

export type PaymentFormValues = FormsBillingDetails & { country_name?: string }

const PaymentForm = observer(({ isEdit, originPaymentMethod, onComplete, onModalClose, onError }: IProps) => {
  const stripe = useStripe()
  const elements = useElements()
  const { store, actions } = useStores()
  const { stripeSetupIntent, tenantData } = store

  const form = useForm<PaymentFormValues>({
    defaultValues: isEdit ? originPaymentMethod?.billing_details : emptyBillingDetails
  })

  const onSuccessful = async (payload: PaymentMethodResult | SetupIntentResult, billingDetails?: PaymentFormValues) => {
    const {
      payments: { defaultPaymentId }
    } = store

    if (
      !defaultPaymentId &&
      payload &&
      (('setupIntent' in payload && !!payload.setupIntent) || ('paymentMethod' in payload && !!payload.paymentMethod))
    ) {
      // add the first card, became a billed customer
      try {
        // confirm setup intent
        await setupIntentConfirm(
          tenantData.id,
          {
            org_id: tenantData.id
          },
          { isHandleErr: true, _warningStatus: [401, 403, 400, 500, 502, 503, 504] }
        )
        // await actions.setDefaultPayment(payload.setupIntent.payment_method)
        // plan change to on demand, update tenantData in store
        // await actions.fetchTenant()
        await actions.refreshOrgData()
        eventTracking('Became A Billed Customer')
      } catch (e: any) {
        // skip 400,401,400 error
        if (axios.isAxiosError(e) && (e.response?.status || 0) <= 500) {
          return
        }
        log.error('Setup Intent Error: ', e, { tags: { scene: LOG_ERROR_SCENE.CoreJourney } })
      }
    }

    onComplete(payload, billingDetails)
  }

  const handleSubmit = async (data: PaymentFormValues) => {
    // clone deep to avoid mutating the original data
    const { country_name, ...values } = cloneDeep(data)

    if (values.address?.country === OTHER_COUNTRY_OR_REGION && country_name) {
      if (!isAllowedOtherCountryOrRegion(country_name)) {
        throw new Error('Information error.')
      }
      values.address.country = 'TW'
    }

    // edit card
    if (isEdit) {
      await updatePaymentMethod(tenantData.id, originPaymentMethod?.id || '', {
        id: originPaymentMethod?.id,
        card: originPaymentMethod?.card,
        billing_details: {
          ...values,
          name: originPaymentMethod?.billing_details?.name
        }
      })
      onModalClose()
      onComplete(undefined, data)
      return
    }

    // add card
    // check stripe only for add card action
    if (!stripe || !elements) {
      // Stripe.js has not loaded yet. Make sure to disable
      // form submission until Stripe.js has loaded.
      return
    }
    // NOTE: getElement will register all elements, like:
    // https://github.com/stripe/elements-examples/blob/88065e3d56dc551fe0ef28502453d03fbb5959e8/js/example3.js#L71
    elements.getElement(CardNumberElement)
    elements.getElement(CardCvcElement)
    const card = elements.getElement(CardExpiryElement)

    const { errMsg, payload, error } = await handleSetupCard({
      orgId: tenantData.id,
      stripe,
      clientSecret: stripeSetupIntent?.client_secret || '',
      billingDetails: values,
      card
    })

    if (errMsg) {
      eventTracking('Add A New Card Error', {
        msg: errMsg
      })
      onError?.(error)
      throw new Error(errMsg)
    } else {
      await onSuccessful(payload, data)
      onModalClose()
    }

    eventTracking('Add A New Card Successfull')

    // set stripe token is stale, need to fetch a new one for next add action
    actions.setStripeTokenStale()
  }

  const { watch } = form

  const country = watch('address.country')

  useEffect(() => {
    if (country && country !== OTHER_COUNTRY_OR_REGION) {
      form.clearErrors('country_name')
    }
  }, [country, form])

  return (
    <Form
      className="payment-form"
      onSubmit={handleSubmit}
      onCancel={onModalClose}
      actionsProps={{ confirmText: 'Save Card' }}
      form={form}
    >
      <Group justify="space-between" align="flex-start" wrap="nowrap">
        <Stack sx={{ flexBasis: '45%', alignSelf: 'stretch' }}>
          <Typography variant="title-lg">Credit Card Information</Typography>
          <FormTextInput
            name="name"
            rules={cardholderNameRules}
            label="Name on Card"
            withAsterisk
            required
            placeholder="Jane Doe"
            disabled={isEdit}
          />
          {isEdit && (
            <>
              <TextInput
                label="Card Number"
                withAsterisk
                required
                disabled
                value={`**** **** **** ${originPaymentMethod?.card?.last4}`}
              />
              <Group justify="space-between" wrap="nowrap">
                <TextInput label="CVC/CVV" withAsterisk required disabled value="****" />
                <TextInput
                  label="Expiration Date"
                  withAsterisk
                  required
                  disabled
                  value={`${originPaymentMethod?.card?.exp_month || '-'} / ${
                    originPaymentMethod?.card?.exp_year || '-'
                  }`}
                />
              </Group>
            </>
          )}
          {!isEdit && !!stripe && (
            <>
              <CardNumberInput label="Card Number" required withAsterisk />
              <Group justify="space-between" align="flex-start" grow wrap="nowrap">
                <CardCVCInput label="CVC/CVV" required withAsterisk />
                <CardExpiryInput label="Expiration Date" required withAsterisk />
              </Group>
            </>
          )}
          {!isEdit && !stripe && (
            <>
              <TextInput label="Card Number" withAsterisk required disabled />
              <Group justify="space-between" wrap="nowrap">
                <TextInput label="CVC/CVV" withAsterisk required disabled />
                <TextInput label="Expiration Date" withAsterisk required disabled />
              </Group>
            </>
          )}
          {!isEdit && (
            <Alert
              icon={<IconAlertTriangle size={14} />}
              sx={{ padding: '10px 12px', marginTop: 'auto' }}
              color="yellow"
            >
              After you save your credit card, we will verify it by placing a <b>temporary $1 charge</b>. This charge is
              for verification purposes only and will be <b>automatically refunded</b> after the verification is
              completed.
            </Alert>
          )}
        </Stack>
        <Divider orientation="vertical" />
        <Stack sx={{ flexBasis: '45%' }}>
          <Typography variant="title-lg">Credit Card Address</Typography>
          <FormSelect
            name="address.country"
            rules={{ required: 'Please select your country/region.' }}
            required
            searchable
            label="Country / Region"
            placeholder="Country / Region"
            data={BILLING_COUNTRY_OPTIONS.map((el) => ({ value: el.value, label: el.text }))}
          />
          {country === OTHER_COUNTRY_OR_REGION && (
            <FormTextInput
              name="country_name"
              rules={{ required: 'Please input your country/region' }}
              required
              placeholder="Country / Region"
            />
          )}
          <FormTextInput
            name="address.line1"
            rules={{ ...addressRules, required: 'Required' }}
            required
            label="Address Line 1"
            placeholder="185 Berry Street Suite 550"
          />
          <FormTextInput name="address.line2" rules={addressRules} label="Address Line 2 (optional)" />
          <FormTextInput
            name="address.city"
            rules={{ ...cityRules, required: 'Required' }}
            required
            label="City"
            placeholder="San Francisco"
          />
          <Group justify="space-between" align="flex-start" wrap="nowrap">
            <FormTextInput
              name="address.state"
              rules={{ ...provinceRules, required: 'Required' }}
              required
              label="State / Province"
              placeholder="CA"
            />
            <FormTextInput
              name="address.postal_code"
              rules={{ ...postalCodeRules, required: 'Required' }}
              required
              label="ZIP or Postal Code"
              placeholder="94107"
            />
          </Group>
          <Typography variant="body-sm">
            This address will be used as your primary business address if you don't specify your primary business
            address. You can update your primary business address in{' '}
            <Link to="/org-settings/billing/billing-profile">billing profile</Link> anytime.
          </Typography>
        </Stack>
      </Group>
    </Form>
  )
})

export const PaymentFormWrapper: React.FC<React.PropsWithChildren<IProps>> = observer((props) => {
  const { isEdit, originPaymentMethod, onComplete, onModalClose } = props
  const [stripe, setStripe] = useState<Stripe | null>(null)
  const { store, actions } = useStores()
  const { stripeSetupIntent, isStripeTokenStale } = store

  const loadStripeWithKey = async (publicKwy: string) => {
    const stripe = await loadStripe(publicKwy, {
      locale: 'en'
    })
    setStripe(stripe)
  }

  useEffect(() => {
    async function setup() {
      const intent = await actions.setupStripe()
      if (!intent) {
        return
      }
      loadStripeWithKey(intent.public_key)
    }
    // Make sure to call `loadStripe` outside of a component’s render to avoid
    // recreating the `Stripe` object on every render.
    // https://github.com/stripe/stripe-js/blob/master/README.md#loadstripe
    if (!isEdit) {
      // add credit card modal
      if (isStripeTokenStale) {
        setup()
      } else {
        loadStripeWithKey(stripeSetupIntent.public_key)
      }
    }
  }, [actions, isEdit, isStripeTokenStale, stripeSetupIntent.public_key])

  return (
    <Elements
      stripe={stripe}
      options={{
        fonts: [
          {
            cssSrc: STRIPE_ELEMENTS_FONT_SRC
          }
        ]
      }}
    >
      <PaymentForm
        isEdit={isEdit}
        originPaymentMethod={originPaymentMethod}
        onComplete={onComplete}
        onModalClose={onModalClose}
        onError={(error) => {
          // skip API 401,400,403 error
          if (axios.isAxiosError(error) && (error.response?.status || 0) < 500) {
            return
          }
          log.error('Add Credit Card Error: ', JSON.stringify(error), {
            tags: {
              scene: LOG_ERROR_SCENE.CoreJourney
            }
          })
        }}
      />
    </Elements>
  )
})

export default PaymentFormWrapper
