/* eslint-disable react/no-unstable-nested-components */
import 'react-international-phone/style.css';

import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
import Toast from '@arcblock/ux/lib/Toast';
import {
  AddressForm,
  CurrencySelector,
  LoadingButton,
  PaymentSummary,
  StripeForm,
  api,
  findCurrency,
  flattenPaymentMethods,
  formatError,
  getPrefix,
  getPriceCurrencyOptions,
  getStatementDescriptor,
  isValidCountry,
  showStaking,
  usePaymentContext,
} from '@blocklet/payment-react';
import type { SlippageConfigValue } from '@blocklet/payment-react';
import type { TCustomer, TPaymentCurrency, TPaymentMethod, TSubscriptionExpanded } from '@blocklet/payment-types';
import { ArrowBackOutlined } from '@mui/icons-material';
import { Alert, Box, CircularProgress, Stack, Typography } from '@mui/material';
import { useRequest, useSetState } from 'ahooks';
import pWaitFor from 'p-wait-for';
import { useEffect, useState, useMemo } from 'react';
import { Controller, FormProvider, useForm, useFormContext, useWatch } from 'react-hook-form';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { joinURL } from 'ufo';

import SectionHeader from '../../../components/section/header';
import SubscriptionDescription from '../../../components/subscription/description';
import { goBackOrFallback } from '../../../libs/util';
import { useSubscriptionExchangeRate, useUnpaidInvoicesCheckForSubscription } from '../../../hooks/subscription';
import { useArcsphere } from '../../../hooks/browser';

const fetchData = async (id: string): Promise<{ subscription: TSubscriptionExpanded; customer: TCustomer }> => {
  const [subscription, customer] = await Promise.all([
    api.get(`/api/subscriptions/${id}`).then((res: any) => res.data),
    api.get('/api/customers/me').then((res: any) => res.data),
  ]);

  return {
    subscription,
    customer,
  };
};

const defaultSlippageConfig: SlippageConfigValue = { mode: 'percent', percent: 0.5 };

const normalizeSlippageConfig = (rawConfig: any): SlippageConfigValue => {
  if (!rawConfig || typeof rawConfig !== 'object') {
    return defaultSlippageConfig;
  }
  const mode = rawConfig.mode === 'rate' ? 'rate' : 'percent';
  const percentValue = Number(rawConfig.percent);
  const percent = Number.isFinite(percentValue) && percentValue >= 0 ? percentValue : defaultSlippageConfig.percent;
  const minRate = typeof rawConfig.min_acceptable_rate === 'string' ? rawConfig.min_acceptable_rate : undefined;
  const baseCurrency = typeof rawConfig.base_currency === 'string' ? rawConfig.base_currency : undefined;
  const updatedAtMs = Number.isFinite(Number(rawConfig.updated_at_ms)) ? Number(rawConfig.updated_at_ms) : undefined;

  return {
    mode,
    percent,
    ...(mode === 'rate' && minRate ? { min_acceptable_rate: minRate } : {}),
    ...(baseCurrency ? { base_currency: baseCurrency } : {}),
    ...(updatedAtMs ? { updated_at_ms: updatedAtMs } : {}),
  };
};

type Props = {
  subscription: TSubscriptionExpanded;
  customer: TCustomer;
  onComplete: Function;
};

const waitForCheckoutComplete = async (sessionId: string) => {
  let result;

  await pWaitFor(
    async () => {
      const { data } = await api.get(`/api/subscriptions/${sessionId}/change-payment`);
      if (data.setupIntent && data.setupIntent.status === 'requires_action' && data.setupIntent.last_setup_error) {
        throw new Error(data.setupIntent.last_setup_error.message);
      }

      result = data;

      return data.setupIntent?.status === 'succeeded';
    },
    { interval: 2000, timeout: 3 * 60 * 1000 }
  );

  // @ts-ignore
  return result;
};

function CustomerSubscriptionChangePayment({ subscription, customer, onComplete }: Props) {
  const { t, locale } = useLocaleContext();
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const { settings, connect } = usePaymentContext();
  const { checkUnpaidInvoices } = useUnpaidInvoicesCheckForSubscription(subscription.id);
  const { control, setValue, handleSubmit } = useFormContext();
  const inArcsphere = useArcsphere();
  const [slippageConfig, setSlippageConfig] = useState<SlippageConfigValue>(() =>
    normalizeSlippageConfig((subscription as any)?.slippage_config)
  );

  const [state, setState] = useSetState<{
    submitting: boolean;
    paying: boolean;
    paid: boolean;
    stripePaying: boolean;
    setupIntent: any;
    stripeContext: any;
    customer: TCustomer;
  }>({
    submitting: false,
    paying: false,
    paid: false,
    stripePaying: false,
    setupIntent: undefined,
    stripeContext: undefined,
    customer,
  });
  const payee = getStatementDescriptor(subscription.items);
  const supported = getPriceCurrencyOptions(subscription.items[0]?.price!).map((x: any) => x.currency_id);
  const currencies = flattenPaymentMethods(settings.paymentMethods).filter((x: any) => supported.includes(x.id));
  const [selectedCurrencyIndex, setSelectedCurrencyIndex] = useState(
    currencies.findIndex((x: any) => x.id === subscription.currency_id)
  );

  const selectedCurrencyId = useWatch({ control, name: 'payment_currency' });
  const selectedMethodId = useWatch({ control, name: 'payment_method' });

  useEffect(() => {
    setValue('payment_method', (currencies[selectedCurrencyIndex] as any).method.id);
    setValue('payment_currency', currencies[selectedCurrencyIndex]?.id);
  }, [selectedCurrencyIndex, currencies, setValue]);

  const method = settings.paymentMethods.find((x: any) => x.id === selectedMethodId) as TPaymentMethod;
  const changed = selectedCurrencyId !== subscription.currency_id;
  const hasDynamicPricing = useMemo(
    () =>
      (subscription.items || []).some((item: any) => {
        const price = item.upsell_price || item.price;
        return price && (price as any)?.pricing_type === 'dynamic';
      }),
    [subscription.items]
  );
  const isStripePayment = method?.type === 'stripe';
  const needsExchangeRate = hasDynamicPricing && !isStripePayment;
  const { liveRateInfo, liveRateUnavailable } = useSubscriptionExchangeRate({
    subscriptionId: subscription.id,
    currencyId: selectedCurrencyId,
    enabled: needsExchangeRate,
  });
  const handleSlippageChange = async (nextConfig: SlippageConfigValue) => {
    try {
      const payloadConfig = {
        ...nextConfig,
        ...(nextConfig.base_currency ? {} : { base_currency: liveRateInfo?.base_currency || 'USD' }),
      };
      await api.put(`/api/subscriptions/${subscription.id}/slippage`, {
        slippage_config: payloadConfig,
      });
      setSlippageConfig(payloadConfig);
      Toast.success(t('common.saved'));
    } catch (err: any) {
      console.error('Failed to update slippage', err);
      Toast.error(formatError(err));
    }
  };

  const handleCompleted = async () => {
    await onComplete();

    const redirectUrl = searchParams.get('redirectUrl');
    if (redirectUrl) {
      window.location.href = redirectUrl;
    } else {
      navigate(`/customer/subscription/${subscription.id}`);
    }
  };

  const handleConnected = async () => {
    try {
      await waitForCheckoutComplete(subscription.id);
      setState({ paid: true, paying: false });
      Toast.success(t('payment.customer.changePayment.completed', { time: subscription.current_period_end }));
      await handleCompleted();
    } catch (err) {
      Toast.error(formatError(err));
    } finally {
      setState({ paying: false });
    }
  };

  const onSubmit = async (data: any) => {
    setState({ submitting: true });
    try {
      const result = await api.post(`/api/subscriptions/${subscription.id}/change-payment`, data);

      setState({
        setupIntent: result.data.setupIntent,
        stripeContext: result.data.stripeContext,
        submitting: false,
      });

      if (['arcblock', 'ethereum', 'base'].includes(method.type)) {
        setState({ paying: true });
        if (result.data.delegation?.sufficient) {
          await handleConnected();
        } else {
          connect.open({
            locale: locale as 'en' | 'zh',
            containerEl: undefined as unknown as Element,
            action: 'change-payment',
            prefix: joinURL(getPrefix(), '/api/did'),
            saveConnect: false,
            extraParams: { subscriptionId: subscription.id },
            messages: {
              scan: t('common.connect.defaultScan'),
              title: t('payment.customer.changePayment.title'),
              confirm: t('common.connect.confirm'),
            } as any,
            onSuccess: async () => {
              connect.close();
              await handleConnected();
            },
            onClose: () => {
              connect.close();
              setState({ submitting: false, paying: false });
            },
            onError: (err: any) => {
              console.error(err);
              setState({ submitting: false, paying: false });
              Toast.error(formatError(err));
            },
          });
        }
      }
      if (['stripe'].includes(method.type)) {
        if (result.data.setupIntent?.status === 'succeeded') {
          await handleConnected();
        } else if (result.data.stripeContext?.status === 'succeeded') {
          setState({ paying: true });
        } else {
          setState({ stripePaying: true });
        }
      }
    } catch (err: any) {
      console.error(err);
      Toast.error(formatError(err));
    } finally {
      setState({ submitting: false });
    }
  };

  const onConfirm = async () => {
    // only check unpaid invoices when overdraft protection is enabled
    if (subscription.overdraft_protection?.enabled) {
      const result = await checkUnpaidInvoices();
      if (result) {
        return;
      }
    }
    handleSubmit(onSubmit)();
  };

  const onStripeConfirm = async () => {
    setState({ stripePaying: false, paying: true });
    await handleConnected();
  };

  const onStripeCancel = () => {
    setState({ stripePaying: false });
  };

  const currency = findCurrency(settings.paymentMethods, selectedCurrencyId) as TPaymentCurrency;
  return (
    <Stack direction="column" spacing={4} sx={{ maxWidth: '540px' }}>
      <Stack
        className="page-header"
        direction="row"
        sx={{
          justifyContent: 'space-between',
          alignItems: 'center',
        }}>
        {!inArcsphere ? (
          <Stack
            direction="row"
            onClick={() => goBackOrFallback(`/customer/subscription/${subscription.id}`)}
            sx={{
              alignItems: 'center',
              fontWeight: 'normal',
              cursor: 'pointer',
            }}>
            <ArrowBackOutlined fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} />
            <SubscriptionDescription subscription={subscription} variant="h5" />
          </Stack>
        ) : (
          <Box />
        )}
      </Stack>
      <Alert severity="info">{t('payment.customer.changePayment.limit')}</Alert>
      <Stack direction="column" spacing={2}>
        <SectionHeader title={t('payment.customer.changePayment.review')} />
        <PaymentSummary
          items={subscription.items as any[]}
          currency={currency}
          trialInDays={0}
          billingThreshold={0}
          showStaking={showStaking(method, currency, !!subscription.billing_thresholds?.no_stake)}
          liveRate={liveRateInfo}
          rateUnavailable={needsExchangeRate && liveRateUnavailable}
          slippageConfig={slippageConfig}
          onSlippageChange={needsExchangeRate ? handleSlippageChange : undefined}
          isSubscription
          isStripePayment={isStripePayment}
        />
      </Stack>
      <Stack direction="column" spacing={2}>
        <SectionHeader title={t('payment.customer.changePayment.select')} />
        <Controller
          name="payment_currency"
          control={control}
          render={({ field }) => (
            <CurrencySelector
              value={field.value}
              currencies={currencies}
              onChange={(currencyId: string) => {
                const index = currencies.findIndex((x) => x.id === currencyId);
                if (index >= 0) {
                  setSelectedCurrencyIndex(index);
                }
              }}
            />
          )}
        />
        {subscription.paymentMethod.type !== 'stripe' && (
          <Stack direction="column" className="cko-payment-form" spacing={0}>
            <AddressForm mode="auto" />
          </Stack>
        )}
      </Stack>
      {changed && (
        <Stack className="cko-payment-submit">
          <LoadingButton
            variant="contained"
            color="primary"
            size="large"
            onClick={onConfirm}
            fullWidth
            loadingPosition="end"
            disabled={!changed || state.submitting || state.paying || state.stripePaying}
            loading={state.paying}>
            {state.paying ? t('payment.checkout.processing') : t('payment.customer.changePayment.submit')}
          </LoadingButton>
          <Typography
            sx={{
              mt: 1,
              color: 'text.secondary',
              fontSize: '0.7875rem',
              lineHeight: '0.9625rem',
              textAlign: 'center',
            }}>
            {t('payment.customer.changePayment.confirm', {
              payee,
              symbol: findCurrency(settings.paymentMethods, selectedCurrencyId)?.symbol,
            })}
          </Typography>
        </Stack>
      )}
      {state.stripePaying && state.stripeContext && (
        <StripeForm
          clientSecret={state.stripeContext.client_secret}
          intentType={state.stripeContext.intent_type}
          publicKey={state.stripeContext.publishable_key}
          customer={state.customer as TCustomer}
          mode="setup"
          onConfirm={onStripeConfirm}
          onCancel={onStripeCancel}
          returnUrl={window.location.href.replace(/\/[^/]+$/, '')}
        />
      )}
    </Stack>
  );
}

function FormWrapper({ subscription, customer, onComplete }: Props) {
  const { settings } = usePaymentContext();
  const defaultCurrencyId = subscription.currency_id;
  const defaultMethodId = settings.paymentMethods.find((m) =>
    m.payment_currencies.some((c) => c.id === defaultCurrencyId)
  )?.id;

  const methods = useForm({
    defaultValues: {
      payment_method: defaultMethodId,
      payment_currency: defaultCurrencyId,
      billing_address: Object.assign(
        {
          country: '',
          state: '',
          city: '',
          line1: '',
          line2: '',
          postal_code: '',
        },
        customer?.address || {},
        { country: isValidCountry(customer?.address?.country || '') ? customer?.address?.country : 'us' }
      ),
    },
  });

  return (
    <FormProvider {...methods}>
      <CustomerSubscriptionChangePayment subscription={subscription} customer={customer} onComplete={onComplete} />
    </FormProvider>
  );
}

export default function DataWrapper() {
  const { t } = useLocaleContext();
  const { id } = useParams() as { id: string };
  const { loading, error, data, runAsync } = useRequest(() => fetchData(id));

  if (error) {
    return <Alert severity="error">{error.message}</Alert>;
  }

  if (loading || !data) {
    return <CircularProgress />;
  }

  if (!data) {
    return <Alert severity="error">{t('payment.customer.changePlan.subscriptionNotFound')}</Alert>;
  }

  return <FormWrapper subscription={data.subscription} customer={data.customer} onComplete={runAsync} />;
}
