import { useState } from 'react';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { PaymentMethod, StripeCardElementChangeEvent } from '@stripe/stripe-js';

import { call } from 'utils/call';

import { Box, Button, Text } from 'components/common';
import { Input } from 'components/inputs';
import { ModalContainer } from 'components/modals';
import { useSetupIntent } from 'components/stripe/useSetupIntent';
import { StripeElements } from 'components/stripe/StripeElements';

const options = {
  style: { base: { fontSize: '16px', fontWeight: 'bold' } },
};

type BillingDetails = {
  email?: string;
  name?: string;
  phone?: string;
  city?: string;
  country?: string;
  line1?: string;
  line2?: string;
  postalCode?: string;
  state?: string;
};

function mapBillingDetails(state: BillingDetails = {}) {
  return {
    address: {
      city: state.city,
      country: state.country,
      line1: state.line1,
      line2: state.line2,
      postal_code: state.postalCode,
      state: state.state,
    },
    email: state.email,
    name: state.name,
    phone: state.phone,
  };
}

export type NewPaymentMethod = PaymentMethod;

export interface HandleIntentSuccess {
  (paymentMethod: PaymentMethod): void;
}

interface PaymentMethodFormProps {
  message?: string;
  cardholderName?: string;
  billingDetails?: BillingDetails;
  onSuccess?: HandleIntentSuccess;
  onDismiss?: () => void;
  withSetupIntent?: boolean;
}

const PaymentMethodForm = ({
  message,
  cardholderName = '',
  billingDetails,
  onSuccess,
  onDismiss,
  withSetupIntent,
}: PaymentMethodFormProps) => {
  const stripe = useStripe();
  const elements = useElements();
  const handleSetupIntent = useSetupIntent(stripe!);

  const [name, setName] = useState(cardholderName);
  const [loading, setLoading] = useState(false);
  const [complete, setComplete] = useState<boolean | null>(null);
  const [error, setError] = useState<string | null | undefined>(null);

  const handleInput: React.ChangeEventHandler<HTMLInputElement> = (ev) => {
    setName(ev.target.value);
  };

  const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (ev) => {
    ev.preventDefault();

    try {
      setLoading(true);

      const { error, paymentMethod } = await stripe!.createPaymentMethod({
        type: 'card',
        card: elements!.getElement(CardElement)!,
        billing_details: mapBillingDetails({ ...billingDetails, name }),
      });

      if (paymentMethod) {
        if (withSetupIntent) {
          const setupIntent = await handleSetupIntent(paymentMethod.id);

          if (setupIntent.error) {
            setLoading(false);
            setError(setupIntent.error.message);
          } else {
            call(onSuccess, paymentMethod);
            call(onDismiss);
          }
        } else {
          call(onSuccess, paymentMethod);
          call(onDismiss);
        }
      } else if (error) {
        setLoading(false);
        setError(error.message!);
      }
    } catch (error) {
      setLoading(false);
      if (error instanceof Error) {
        setError(error.message);
      }
    }
  };

  const handleChange = (ev: StripeCardElementChangeEvent) => {
    if (ev.error) {
      setError(ev.error.message);
    } else if (error) {
      setError(null);
    }
    if (ev.complete) {
      setComplete(true);
    } else if (complete) {
      setComplete(false);
    }
  };

  return (
    <form className="spacingY4" onSubmit={handleSubmit}>
      {message && <Text size="s">{message}</Text>}
      <Box spacingY={2}>
        <Input
          name="name"
          value={name}
          onChange={handleInput}
          placeholder="Cardholder Name"
          autoFocus
        />
        <CardElement
          className="CardElement"
          options={options}
          onChange={handleChange}
        />
        {error && (
          <Text size="s" className="c-red">
            {error}
          </Text>
        )}
      </Box>
      <Box display="flex" marginY={4} spacing={2}>
        <Button color="gray" onClick={onDismiss} disabled={loading} block>
          Cancel
        </Button>
        <Button
          type="submit"
          color="blue"
          block
          disabled={loading || !complete || !name}
        >
          {error ? 'Retry' : 'Add'}
        </Button>
      </Box>
    </form>
  );
};

export const AddCardModal = ({
  title = 'Add a card',
  ...props
}: { title?: string } & PaymentMethodFormProps) => {
  return (
    <ModalContainer title={title}>
      <StripeElements>
        <PaymentMethodForm {...props} />
      </StripeElements>
    </ModalContainer>
  );
};
