/* eslint-disable react/no-unstable-nested-components */
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
import Toast from '@arcblock/ux/lib/Toast';
import {
  LoadingButton,
  PaymentSummary,
  PricingTable,
  api,
  formatBNStr,
  formatError,
  formatTime,
  usePaymentContext,
} from '@blocklet/payment-react';
import type { TLineItemExpanded, TPricingTableExpanded, TSubscriptionExpanded } from '@blocklet/payment-types';
import { ArrowBackOutlined } from '@mui/icons-material';
import { Alert, Box, CircularProgress, Divider, Stack, Typography } from '@mui/material';
import { useRequest, useSetState } from 'ahooks';
import { useRef } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';

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

/**
 * Calculate total amount for items using live exchange rate
 * For dynamic pricing items, calculate: base_amount / rate
 * For fixed pricing items, use unit_amount directly
 */
const calculateTotalWithLiveRate = (
  items: TLineItemExpanded[],
  liveRate: string | undefined,
  currencyDecimal: number
): string | null => {
  if (!items?.length) return null;

  let total = new BigNumber(0);
  const hasAnyDynamicPricing = items.some((item) => {
    const price = (item.upsell_price || item.price) as any;
    return price?.pricing_type === 'dynamic';
  });

  // If there's dynamic pricing but no live rate, return null
  if (hasAnyDynamicPricing && !liveRate) return null;

  for (const item of items) {
    const price = (item.upsell_price || item.price) as any;
    const quantity = item.quantity || 1;

    if (price?.pricing_type === 'dynamic' && price?.base_amount && liveRate) {
      // Dynamic pricing: amount = base_amount / rate * 10^decimal
      const baseAmount = new BigNumber(price.base_amount);
      const rate = new BigNumber(liveRate);
      if (rate.gt(0)) {
        const amount = baseAmount.dividedBy(rate).times(new BigNumber(10).pow(currencyDecimal)).times(quantity);
        // Round up to ensure we don't undercharge
        total = total.plus(amount.integerValue(BigNumber.ROUND_CEIL));
      }
    } else if (price?.unit_amount) {
      // Fixed pricing: use unit_amount directly
      total = total.plus(new BigNumber(price.unit_amount).times(quantity));
    }
  }

  return total.toString();
};

/**
 * Proration type with additional info from backend
 */
type ProrationItem = {
  price_id: string;
  amount: string;
  quantity: number;
  description: string;
  pricing_type?: string;
  base_amount_usd?: string | null;
  proration_rate?: number;
};

/**
 * Recalculate prorations using live exchange rate for dynamic pricing items
 * For fixed pricing items, use the original amount from backend
 *
 * IMPORTANT: The recalculated amount is capped at the original payment amount.
 * This prevents the case where token depreciation causes the refund to exceed
 * what the user originally paid.
 */
const recalculateProrations = (
  prorations: ProrationItem[],
  liveRate: string | undefined,
  currencyDecimal: number
): ProrationItem[] => {
  if (!prorations?.length) return [];

  return prorations.map((p) => {
    // Only recalculate for dynamic pricing items with base_amount_usd
    if (p.pricing_type === 'dynamic' && p.base_amount_usd && p.proration_rate && liveRate) {
      const baseAmountUsd = new BigNumber(p.base_amount_usd);
      const rate = new BigNumber(liveRate);
      const prorationRate = new BigNumber(p.proration_rate);

      if (rate.gt(0)) {
        // Calculate: base_amount_usd / rate * proration_rate * quantity * 10^decimal
        const calculatedAmount = baseAmountUsd
          .dividedBy(rate)
          .times(prorationRate)
          .times(p.quantity || 1)
          .times(new BigNumber(10).pow(currencyDecimal))
          .integerValue(BigNumber.ROUND_CEIL);

        // Get the original amount from backend (absolute value)
        const originalAmount = new BigNumber(p.amount.replace('-', ''));

        // Cap at original amount: don't refund more than what was paid
        // This handles the case where token depreciation would cause
        // the calculated refund to exceed the original payment
        const finalAmount = BigNumber.min(calculatedAmount, originalAmount);

        return {
          ...p,
          amount: `-${finalAmount.toString()}`, // Proration is negative (credit)
        };
      }
    }
    // For fixed pricing or if we can't recalculate, use original amount
    return p;
  });
};

const fetchData = async (
  id: string
): Promise<{ subscription: TSubscriptionExpanded; table: TPricingTableExpanded }> => {
  const [subscription, table] = await Promise.all([
    api.get(`/api/subscriptions/${id}`).then((res: any) => res.data),
    api.get(`/api/subscriptions/${id}/change-plan`).then((res: any) => res.data),
  ]);

  return {
    subscription,
    table,
  };
};

type UpdateItem = {
  id?: string;
  deleted?: boolean;
  clear_usage?: boolean;
  price_id?: string;
  quantity?: number;
};

const simulateUpdate = ({ id, items }: { id: string; items: UpdateItem[] }) => {
  return api.post(`/api/subscriptions/${id}/change-plan`, { items }).then((res: any) => res.data);
};

export default function CustomerSubscriptionChangePlan() {
  const navigate = useNavigate();
  const { id } = useParams() as { id: string };
  const { t, locale } = useLocaleContext();
  const { connect, session } = usePaymentContext();
  const [searchParams] = useSearchParams();
  const redirectUrl = searchParams.get('redirectUrl');
  const confirmRef = useRef<HTMLButtonElement>(null);
  const inArcsphere = useArcsphere();

  const { loading, error, data } = useRequest(() => fetchData(id));
  const [state, setState] = useSetState({
    loading: false,
    priceId: '',
    total: '',
    due: '',
    newCredit: '',
    appliedCredit: '',
    setup: null,
    prorations: [],
    items: [],
    paying: false,
    paid: false,
  });

  let planItems: TLineItemExpanded[] = [];
  if (state.items.length > 0) {
    planItems = state.items as TLineItemExpanded[];
  } else if (data?.subscription?.items) {
    planItems = data.subscription.items as unknown as TLineItemExpanded[];
  }
  const planHasDynamicPricing =
    planItems?.length &&
    planItems.some((item) => {
      const price = (item.upsell_price || item.price) as any;
      return price?.pricing_type === 'dynamic';
    });
  const planNeedsExchangeRate = planHasDynamicPricing && data?.subscription?.paymentMethod?.type !== 'stripe';
  const { liveRateInfo, liveRateUnavailable } = useSubscriptionExchangeRate({
    subscriptionId: data?.subscription?.id,
    currencyId: data?.subscription?.paymentCurrency?.id,
    enabled: !!planNeedsExchangeRate,
  });

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

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

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

  const planItemsAfterData = state.items.length
    ? (state.items as TLineItemExpanded[])
    : (data.subscription.items as unknown as TLineItemExpanded[]);
  const planHasDynamicPricingAfterData =
    planItemsAfterData?.length &&
    planItemsAfterData.some((item) => {
      const price = (item.upsell_price || item.price) as any;
      return price?.pricing_type === 'dynamic';
    });
  const planNeedsExchangeRateAfterData =
    planHasDynamicPricingAfterData && data.subscription.paymentMethod?.type !== 'stripe';

  const handleSelect = async (priceId: string) => {
    try {
      if (state.priceId === priceId) {
        return;
      }

      const deleted = data.subscription.items.find((si) => data.table.items.some((ti) => ti.price_id === si.price_id));
      if (deleted!.price_id === priceId) {
        setState({ priceId: '', total: '', due: '', setup: null, prorations: [], items: [] });
        return;
      }

      const result = await simulateUpdate({
        id: data.subscription.id,
        items: [
          {
            id: deleted!.id,
            deleted: true,
            clear_usage: false,
          },
          {
            price_id: priceId,
            quantity: 1, // FIXME: support customized quantity
          },
        ],
      });

      setState({ priceId, ...result });

      setTimeout(() => confirmRef.current?.scrollIntoView({ behavior: 'smooth' }), 100);
    } catch (err) {
      Toast.error(formatError(err));
      setState({ priceId: '', total: '', due: '', setup: null, prorations: [], items: [] });
    }
  };

  const handleBack = () => {
    if (redirectUrl) {
      window.location.href = redirectUrl;
    } else {
      navigate(`/customer/subscription/${data.subscription.id}`);
    }
  };

  const handleConfirm = async () => {
    try {
      setState({ loading: true });
      const deleted = data.subscription.items.find((si) => data.table.items.some((ti) => ti.price_id === si.price_id));
      const items = [
        {
          id: deleted!.id,
          deleted: true,
          clear_usage: false,
        },
        {
          price_id: state.priceId,
          quantity: 1, // FIXME: support customized quantity
        },
      ];

      // FIXME: support more proration_behavior
      const result = await api.put(`/api/subscriptions/${id}`, { proration_behavior: 'create_prorations', items });
      if (result.data.connectAction) {
        setState({ paying: true });
        try {
          setState({ paying: true });
          connect.open({
            locale: locale as 'en' | 'zh',
            action: result.data.connectAction,
            saveConnect: false,
            messages: {
              title: t('payment.customer.changePlan.pay'),
              scan: t('payment.customer.changePlan.scan'),
              success: t('payment.customer.changePlan.success'),
              error: t('payment.customer.changePlan.error'),
              confirm: '',
            } as any,
            extraParams: {
              invoiceId: result.data.pending_update?.updates?.latest_invoice_id,
              subscriptionId: result.data.id,
              sessionUserDid: session?.user?.did,
            },
            onSuccess: () => {
              setState({ paid: true, paying: false });
              setTimeout(() => {
                connect.close();
                handleBack();
              }, 2000);
            },
            onClose: () => {
              connect.close();
              setState({ paying: false });
            },
            onError: (err: any) => {
              setState({ paying: false });
              Toast.error(formatError(err));
            },
          });
        } catch (err) {
          Toast.error(formatError(err));
        } finally {
          setState({ paying: false });
        }
      } else {
        Toast.success(t('payment.customer.changePlan.success'));
        setState({ paid: true });
        setTimeout(handleBack, 2000);
      }
    } catch (err) {
      console.error(err);
      Toast.error(formatError(err));
    } finally {
      setState({ loading: false });
    }
  };

  const table = { ...data.table, currency: data.subscription.paymentCurrency };
  (table.items || []).forEach((x: any) => {
    x.is_selected = x.price_id === state.priceId;
    if (data.subscription.items.find((y) => y.price_id === x.price_id)) {
      x.is_highlight = true;
      x.is_disabled = true;
      x.highlight_text = t('payment.customer.changePlan.current');
      if (!state.priceId) {
        x.is_selected = true;
      }
    }
  });

  const { recurring } = data.subscription.items.find((x) => x.price.type === 'recurring')?.price || {};
  const interval = [recurring?.interval, recurring?.interval_count].join('-');

  // Calculate display values using live exchange rate for dynamic pricing
  // For fixed pricing, use backend's values directly
  const needsLiveRateCalculation = planNeedsExchangeRateAfterData && liveRateInfo?.rate;

  // Recalculate prorations with current exchange rate for dynamic pricing items
  const displayProrations = needsLiveRateCalculation
    ? recalculateProrations(
        state.prorations as ProrationItem[],
        liveRateInfo?.rate,
        data.subscription.paymentCurrency.decimal
      )
    : state.prorations;

  // Calculate total with current exchange rate
  const calculatedTotal = needsLiveRateCalculation
    ? calculateTotalWithLiveRate(planItemsAfterData, liveRateInfo?.rate, data.subscription.paymentCurrency.decimal)
    : null;
  const displayTotal = calculatedTotal || state.total;

  // Recalculate due and newCredit: total + sum(prorations) - appliedCredit
  const calculateDisplayValues = (): { due: string; newCredit: string } => {
    if (!needsLiveRateCalculation) {
      return { due: state.due, newCredit: state.newCredit };
    }
    const totalBN = new BigNumber(displayTotal || '0');
    const prorationsSum = displayProrations.reduce(
      (sum: BigNumber, p: any) => sum.plus(new BigNumber(p.amount || '0')),
      new BigNumber(0)
    );
    const appliedCreditBN = new BigNumber(state.appliedCredit || '0');
    const balanceBN = totalBN.plus(prorationsSum).minus(appliedCreditBN);

    // If balance is negative, due=0 and the excess becomes newCredit
    // If balance is positive, due=balance and newCredit=0
    if (balanceBN.lt(0)) {
      return {
        due: '0',
        newCredit: balanceBN.abs().toString(),
      };
    }
    return {
      due: balanceBN.toString(),
      newCredit: '0',
    };
  };
  const { due: displayDue, newCredit: displayNewCredit } = calculateDisplayValues();

  const getInfoRow = (label: string, value: string, prefix?: string) => {
    return (
      <Stack
        direction="row"
        key={value}
        sx={{
          justifyContent: 'space-between',
        }}>
        <Typography variant="h6" sx={{ fontWeight: 'normal' }}>
          {label}
        </Typography>
        <Typography component="p" style={{ fontWeight: 'bold' }}>
          {prefix}
          {formatBNStr(value, data.subscription.paymentCurrency.decimal, 2)} {data.subscription.paymentCurrency.symbol}
        </Typography>
      </Stack>
    );
  };

  return (
    <Stack direction="column" spacing={4} sx={{ mb: 4 }}>
      <Stack
        className="page-header"
        direction="row"
        sx={{
          justifyContent: 'space-between',
          alignItems: 'center',
        }}>
        {!inArcsphere ? (
          <Stack
            direction="row"
            onClick={() => goBackOrFallback(`/customer/subscription/${data.subscription.id}`)}
            sx={{
              alignItems: 'center',
              fontWeight: 'normal',
              cursor: 'pointer',
            }}>
            <ArrowBackOutlined fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} />
            <SubscriptionDescription subscription={data.subscription} variant="h5" />
          </Stack>
        ) : (
          <Box />
        )}
      </Stack>
      <Stack direction="column" sx={{ marginTop: 32 }}>
        <SectionHeader title={t('payment.customer.changePlan.config')} />
        <PricingTable
          mode="select"
          alignItems="left"
          interval={interval}
          table={table}
          onSelect={handleSelect}
          hideCurrency
        />
      </Stack>
      {!data.table && <Alert severity="error">{t('payment.customer.changePlan.tableNotFound')}</Alert>}
      {state.priceId && state.total && state.setup && (
        <Stack direction="column" spacing={3} sx={{ maxWidth: 640 }}>
          <SectionHeader title={t('payment.customer.changePlan.confirm')} />
          <PaymentSummary
            items={planItemsAfterData}
            currency={data.subscription.paymentCurrency}
            trialInDays={0}
            billingThreshold={0}
            showStaking={false}
            liveRate={liveRateInfo}
            rateUnavailable={!!(planNeedsExchangeRateAfterData && liveRateUnavailable)}
            action={t('payment.customer.changePlan.summary', {
              // @ts-ignore
              date: formatTime(state.setup.period.end * 1000, 'lll', locale),
            })}
            completed
            isStripePayment={data.subscription.paymentMethod?.type === 'stripe'}
          />
          <Divider />
          <Stack direction="column" spacing={1}>
            {getInfoRow(t('payment.customer.changePlan.total'), displayTotal)}
            {displayProrations.map((x: any) => getInfoRow(x.description, x.amount))}
            {state.appliedCredit > '0' &&
              getInfoRow(t('payment.customer.changePlan.appliedCredit'), state.appliedCredit, '-')}
            {new BigNumber(displayNewCredit).gt(0) &&
              getInfoRow(t('payment.customer.changePlan.newCredit'), displayNewCredit)}
            <Divider />
            {getInfoRow(t('payment.customer.changePlan.remaining'), displayDue)}
            <Divider />
            <Stack
              direction="row"
              spacing={2}
              sx={{
                alignItems: 'center',
                justifyContent: 'flex-end',
                pt: 2,
              }}>
              <LoadingButton
                disabled={state.loading || state.paying}
                onClick={() => setState({ priceId: '' })}
                variant="text"
                size="large">
                {t('common.cancel')}
              </LoadingButton>
              <LoadingButton
                ref={confirmRef}
                onClick={handleConfirm}
                variant="contained"
                size="large"
                sx={{ width: 150 }}
                loading={state.loading || state.paying}
                loadingPosition="end">
                {t('common.confirm')}
              </LoadingButton>
            </Stack>
          </Stack>
        </Stack>
      )}
    </Stack>
  );
}
