import {generateAccountPlanFixture, getSiteData, getProductsData, getOfferData, getProductData, getPriceData, getMemberData, getSubscriptionData} from '../../../../src/utils/fixtures-generator';
import {render, fireEvent} from '../../../utils/test-utils';
import AccountPlanPage from '../../../../src/components/pages/account-plan-page';

const setup = (overrides) => {
    const {mockDoActionFn, context, ...utils} = render(
        <AccountPlanPage />,
        {
            overrideContext: {
                ...overrides
            }
        }
    );
    const monthlyCheckboxEl = utils.getByTestId('monthly-switch');
    const yearlyCheckboxEl = utils.getByTestId('yearly-switch');
    const continueBtn = utils.queryByRole('button', {name: 'Continue'});
    const chooseBtns = utils.queryAllByRole('button', {name: 'Choose'});
    return {
        monthlyCheckboxEl,
        yearlyCheckboxEl,
        continueBtn,
        chooseBtns,
        mockDoActionFn,
        context,
        ...utils
    };
};

const customSetup = (overrides) => {
    const {mockDoActionFn, context, ...utils} = render(
        <AccountPlanPage />,
        {
            overrideContext: {
                ...overrides
            }
        }
    );

    return {
        mockDoActionFn,
        context,
        ...utils
    };
};

describe('Account Plan Page', () => {
    test('renders', () => {
        const {monthlyCheckboxEl, yearlyCheckboxEl, queryAllByRole} = setup();
        const continueBtn = queryAllByRole('button', {name: 'Continue'});
        expect(monthlyCheckboxEl).toBeInTheDocument();
        expect(yearlyCheckboxEl).toBeInTheDocument();
        expect(continueBtn).toHaveLength(1);
    });

    test('shows an informative message when no paid plans are available', () => {
        // Reproduces the deep-link / theme-button entry point (#/portal/account/plans)
        // on a site with members enabled but no paid plan configured. Without the
        // empty state the body renders blank under the "Choose a plan" header.
        const siteData = getSiteData({
            paidMembersEnabled: false,
            products: getProductsData({numOfProducts: 0})
        });
        const {getByTestId, queryByTestId} = customSetup({site: siteData});

        const message = getByTestId('no-plans-available-notification-text');
        expect(message).toBeInTheDocument();
        expect(message).toHaveTextContent('Sorry, no paid plans are available.');

        // The plan selectors should not render in the empty state.
        expect(queryByTestId('monthly-switch')).not.toBeInTheDocument();
        expect(queryByTestId('yearly-switch')).not.toBeInTheDocument();
    });

    test('shows paid plans to complimentary members on non-USD sites', () => {
        // Regression test for https://linear.app/ghost/issue/ONC-1877
        // Ghost core builds a synthetic subscription for comped members with a
        // hardcoded USD currency and an empty price_id (see
        // attachSubscriptionsToMember in member-bread-service.js). Filtering
        // the available prices by that synthetic currency wrongly emptied the
        // plans list on non-USD sites, showing "Sorry, no paid plans are
        // available." instead of the upgrade options.
        const products = [
            getProductData({
                name: 'Bronze',
                monthlyPrice: getPriceData({interval: 'month', amount: 700, currency: 'eur'}),
                yearlyPrice: getPriceData({interval: 'year', amount: 7000, currency: 'eur'}),
                numOfBenefits: 2
            })
        ];
        const siteData = getSiteData({products});
        const memberData = getMemberData({
            status: 'comped',
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    id: '',
                    status: 'active',
                    currency: 'USD',
                    interval: 'year',
                    amount: 0,
                    nickname: 'Complimentary',
                    priceId: '',
                    offer: null,
                    tier: products[0]
                })
            ]
        });
        const {getByTestId, queryByTestId} = customSetup({site: siteData, member: memberData});

        expect(queryByTestId('no-plans-available-notification-text')).not.toBeInTheDocument();
        expect(getByTestId('monthly-switch')).toBeInTheDocument();
        expect(getByTestId('yearly-switch')).toBeInTheDocument();
    });

    test('can choose plan and continue', async () => {
        const siteData = getSiteData({
            products: getProductsData({numOfProducts: 1})
        });
        const {mockDoActionFn, monthlyCheckboxEl, yearlyCheckboxEl, queryAllByRole} = setup({site: siteData});
        const continueBtn = queryAllByRole('button', {name: 'Continue'});

        fireEvent.click(monthlyCheckboxEl);
        expect(monthlyCheckboxEl.className).toEqual('gh-portal-btn active');
        fireEvent.click(yearlyCheckboxEl);
        expect(yearlyCheckboxEl.className).toEqual('gh-portal-btn active');
        fireEvent.click(continueBtn[0]);
        expect(mockDoActionFn).toHaveBeenCalledWith('checkoutPlan', {plan: siteData.products[0].yearlyPrice.id});
    });

    test('can cancel subscription for member on hidden tier', async () => {
        const overrides = generateAccountPlanFixture();
        const {queryByRole, queryByText} = customSetup(overrides);
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});
        expect(cancelButton).toBeInTheDocument();
        fireEvent.click(cancelButton);

        // Check that the cancellation message is present
        const cancellationMessage = queryByText(/If you cancel your subscription now, you will continue to have access until/i);
        expect(cancellationMessage).toBeInTheDocument();

        // Ensure the message doesn't contain the raw interpolation placeholder
        expect(cancellationMessage.textContent).not.toContain('{periodEnd}');

        const confirmCancelButton = queryByRole('button', {name: 'Confirm cancellation'});
        expect(confirmCancelButton).toBeInTheDocument();
    });

    test('triggers cancellation flow when opened with cancel pageData', () => {
        const overrides = generateAccountPlanFixture();
        const subscriptionId = overrides.member.subscriptions[0].id;
        const {queryByText, queryByRole} = customSetup({
            ...overrides,
            pageData: {action: 'cancel', subscriptionId}
        });

        // Should immediately show cancellation confirmation
        const cancellationMessage = queryByText(/If you cancel your subscription now, you will continue to have access until/i);
        expect(cancellationMessage).toBeInTheDocument();

        const confirmCancelButton = queryByRole('button', {name: 'Confirm cancellation'});
        expect(confirmCancelButton).toBeInTheDocument();
    });

    test('shows retention offer during cancellation flow', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });
        const subscriptionId = member.subscriptions[0].id;
        const retentionOffer = getOfferData({
            redemptionType: 'retention',
            name: 'Stay with us',
            amount: 20,
            type: 'percent',
            cadence: 'month',
            tierId: paidProduct.id,
            tierName: paidProduct.name
        });
        const {findByRole} = customSetup({
            site,
            member,
            offers: [retentionOffer],
            pageData: {action: 'cancel', subscriptionId}
        });

        // Should show retention offer section instead of cancellation confirmation
        const acceptOfferButton = await findByRole('button', {name: 'Continue subscription'});
        expect(acceptOfferButton).toBeInTheDocument();
    });

    test('shows retention offer during cancellation flow for members on an archived tier', async () => {
        const paidTier = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });

        const archivedTier = getProductData({
            name: 'Archived',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });

        // As archived products are not returned by the /tiers endpoint, omit archivedTier
        const site = getSiteData({
            products: [paidTier, getProductData({type: 'free'})],
            portalProducts: [paidTier.id]
        });

        // Subscription is linked to archivedTier
        const subscription = getSubscriptionData({
            status: 'active',
            interval: 'month',
            amount: 1200,
            currency: 'USD',
            priceId: archivedTier.monthlyPrice.id,
            tier: {
                id: archivedTier.id,
                name: archivedTier.name
            }
        });
        subscription.price.product.product_id = subscription.tier.id;

        const member = getMemberData({
            paid: true,
            subscriptions: [subscription]
        });

        const retentionOffer = {
            ...getOfferData({
                redemptionType: 'retention',
                type: 'percent',
                amount: 20,
                cadence: 'month'
            }),
            tier: null
        };

        const {findByRole, queryByText} = customSetup({
            site,
            member,
            offers: [retentionOffer],
            pageData: {action: 'cancel', subscriptionId: subscription.id}
        });

        const acceptOfferButton = await findByRole('button', {name: 'Continue subscription'});
        expect(acceptOfferButton).toBeInTheDocument();
        expect(queryByText(`${archivedTier.name} - Monthly`)).toBeInTheDocument();
    });

    test('refreshes retention preview details when offer context changes', () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });
        const subscriptionId = member.subscriptions[0].id;

        const firstOffer = getOfferData({
            redemptionType: 'retention',
            displayTitle: 'First retention title',
            displayDescription: 'First retention description',
            type: 'percent',
            amount: 10,
            cadence: 'month',
            tierId: paidProduct.id,
            tierName: paidProduct.name
        });
        const secondOffer = {
            ...firstOffer,
            amount: 25,
            display_title: 'Second retention title',
            display_description: 'Second retention description'
        };

        const {queryByText, context, rerender} = customSetup({
            site,
            member,
            offers: [firstOffer],
            pageData: {action: 'cancel', subscriptionId}
        });

        expect(queryByText('First retention title')).toBeInTheDocument();
        expect(queryByText('10% off')).toBeInTheDocument();

        context.offers = [secondOffer];
        context.pageData = {action: 'cancel', subscriptionId};
        rerender(<AccountPlanPage />);

        expect(queryByText('Second retention title')).toBeInTheDocument();
        expect(queryByText('25% off')).toBeInTheDocument();
    });

    test('clears pageData after triggering cancellation so it does not re-trigger on remount', () => {
        const overrides = generateAccountPlanFixture();
        const subscriptionId = overrides.member.subscriptions[0].id;
        const pageData = {action: 'cancel', subscriptionId};

        // First mount: should trigger cancellation flow
        const {unmount, queryByText} = customSetup({
            ...overrides,
            pageData
        });

        const cancellationMessage = queryByText(/If you cancel your subscription now, you will continue to have access until/i);
        expect(cancellationMessage).toBeInTheDocument();

        // pageData.action should have been cleared
        expect(pageData.action).toBeNull();

        unmount();

        // Second mount with the same pageData object: should NOT trigger cancellation flow
        const {queryByText: queryByText2, queryByRole: queryByRole2} = customSetup({
            ...overrides,
            pageData
        });

        const cancellationMessage2 = queryByText2(/If you cancel your subscription now/i);
        expect(cancellationMessage2).not.toBeInTheDocument();

        // Should show the normal plan page
        const cancelButton = queryByRole2('button', {name: 'Cancel subscription'});
        expect(cancelButton).toBeInTheDocument();
    });

    test('does not trigger cancellation flow when pageData has no cancel action', () => {
        const overrides = generateAccountPlanFixture();
        const {queryByText, queryByRole} = customSetup({
            ...overrides,
            pageData: {someOtherData: true}
        });

        // Should not show cancellation confirmation
        const cancellationMessage = queryByText(/If you cancel your subscription now/i);
        expect(cancellationMessage).not.toBeInTheDocument();

        // Should show the normal plan page with cancel button available
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});
        expect(cancelButton).toBeInTheDocument();
    });

    test('renders percent retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 20,
                cadence: 'month',
                duration: 'once',
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('20% off')).toBeInTheDocument();
        expect(queryByText('Save 20% on your next billing cycle. Then $10/month.')).toBeInTheDocument();
    });

    test('renders yearly retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'year',
                    amount: paidProduct.yearlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.yearlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 20,
                cadence: 'year',
                duration: 'once',
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('20% off')).toBeInTheDocument();
        expect(queryByText('Save 20% on your next billing cycle. Then $100/year.')).toBeInTheDocument();
    });

    test('renders rounded cents for percent retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 599, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 20,
                cadence: 'month',
                duration: 'once',
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {container, queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        const discountedAmount = container.querySelector('.gh-portal-product-price .amount');
        expect(discountedAmount).not.toBeNull();
        expect(discountedAmount).toHaveTextContent('4.79');
        expect(queryByText('Save 20% on your next billing cycle. Then $5.99/month.')).toBeInTheDocument();
    });

    test('renders grouped and padded prices for larger fractional retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 123450, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 20,
                cadence: 'month',
                duration: 'once',
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {container, queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        const discountedAmount = container.querySelector('.gh-portal-product-price .amount');
        expect(discountedAmount).not.toBeNull();
        expect(discountedAmount).toHaveTextContent('987.60');
        expect(queryByText('Save 20% on your next billing cycle. Then $1,234.50/month.')).toBeInTheDocument();
    });

    test('renders fixed retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'fixed',
                amount: 300,
                cadence: 'month',
                duration: 'once',
                currency: 'USD',
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('$3 off')).toBeInTheDocument();
        expect(queryByText('Save $3 on your next billing cycle. Then $10/month.')).toBeInTheDocument();
    });

    test('renders free months retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 100,
                cadence: 'month',
                duration: 'repeating',
                durationInMonths: 1,
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('1 month free')).toBeInTheDocument();
        expect(queryByText('Enjoy a free month on us. You won\'t be charged until 5 Nov 2022.')).toBeInTheDocument();
    });

    test('renders multi-month free months retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 100,
                cadence: 'month',
                duration: 'repeating',
                durationInMonths: 3,
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('3 months free')).toBeInTheDocument();
        expect(queryByText('Enjoy 3 free months on us. You won\'t be charged until 5 Jan 2023.')).toBeInTheDocument();
    });

    test('renders forever percent retention offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 20,
                cadence: 'month',
                duration: 'forever',
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('20% off')).toBeInTheDocument();
        expect(queryByText('Enjoy 20% off forever.')).toBeInTheDocument();
    });

    test('renders repeating percent offers', async () => {
        const paidProduct = getProductData({
            name: 'Basic',
            monthlyPrice: getPriceData({interval: 'month', amount: 1000, currency: 'usd'}),
            yearlyPrice: getPriceData({interval: 'year', amount: 10000, currency: 'usd'})
        });
        const products = [paidProduct, getProductData({type: 'free'})];
        const site = getSiteData({
            products,
            portalProducts: [paidProduct.id]
        });
        const member = getMemberData({
            paid: true,
            subscriptions: [
                getSubscriptionData({
                    status: 'active',
                    interval: 'month',
                    amount: paidProduct.monthlyPrice.amount,
                    currency: 'USD',
                    priceId: paidProduct.monthlyPrice.id
                })
            ]
        });

        const retentionOffer = {
            ...getOfferData({
                type: 'percent',
                amount: 20,
                cadence: 'month',
                duration: 'repeating',
                durationInMonths: 3,
                tierId: paidProduct.id,
                tierName: paidProduct.name
            }),
            redemption_type: 'retention'
        };

        const {queryByRole, queryByText} = customSetup({site, member, offers: [retentionOffer]});
        const cancelButton = queryByRole('button', {name: 'Cancel subscription'});

        fireEvent.click(cancelButton);

        expect(queryByText('20% off')).toBeInTheDocument();
        expect(queryByText('Save 20% on your next 3 billing cycles. Then $10/month.')).toBeInTheDocument();
    });

    describe('gift members', () => {
        const buildGiftMember = ({tier}) => {
            const subscription = getSubscriptionData({
                status: 'active',
                interval: 'month',
                amount: 1200,
                currency: 'USD',
                priceId: '',
                tier: {
                    id: tier.id,
                    name: tier.name,
                    expiry_at: '2027-01-11T00:00:00.000Z'
                }
            });
            subscription.id = '';
            subscription.price.id = '';
            subscription.price.product.product_id = tier.id;

            return getMemberData({
                status: 'gift',
                paid: true,
                subscriptions: [subscription]
            });
        };

        describe('on an archived tier', () => {
            test('hides the Cancel subscription button and allows to upgrade to a new plan', async () => {
                const premiumTier = getProductData({
                    name: 'Premium',
                    monthlyPrice: getPriceData({interval: 'month', amount: 1500, currency: 'usd'}),
                    yearlyPrice: getPriceData({interval: 'year', amount: 15000, currency: 'usd'})
                });
                const ultraTier = getProductData({
                    name: 'Ultra',
                    monthlyPrice: getPriceData({interval: 'month', amount: 2500, currency: 'usd'}),
                    yearlyPrice: getPriceData({interval: 'year', amount: 25000, currency: 'usd'})
                });

                const archivedTier = getProductData({
                    name: 'Archived Tier',
                    monthlyPrice: getPriceData({interval: 'month', amount: 1200, currency: 'usd'}),
                    yearlyPrice: getPriceData({interval: 'year', amount: 12000, currency: 'usd'})
                });

                // Archived tiers are filtered out of site.products by the backend
                const site = getSiteData({
                    products: [premiumTier, ultraTier, getProductData({type: 'free'})],
                    portalProducts: [premiumTier.id, ultraTier.id]
                });

                const member = buildGiftMember({tier: archivedTier});

                const {queryByRole, queryAllByRole, mockDoActionFn} = customSetup({site, member});

                expect(queryByRole('button', {name: 'Cancel subscription'})).not.toBeInTheDocument();
                expect(queryByRole('button', {name: 'Confirm cancellation'})).not.toBeInTheDocument();

                const chooseButtons = queryAllByRole('button', {name: 'Choose'});
                expect(chooseButtons.length).toBeGreaterThan(0);

                fireEvent.click(chooseButtons[0]);

                expect(mockDoActionFn).toHaveBeenCalledWith('checkoutPlan', expect.objectContaining({plan: expect.any(String)}));
                expect(mockDoActionFn).not.toHaveBeenCalledWith('updateSubscription', expect.anything());
                expect(mockDoActionFn).not.toHaveBeenCalledWith('cancelSubscription', expect.anything());
            });
        });

        describe('on an active tier', () => {
            test('hides the cancellations button and allows to continue on the same tier', () => {
                const activeTier = getProductData({
                    name: 'Basic',
                    monthlyPrice: getPriceData({interval: 'month', amount: 1200, currency: 'usd'}),
                    yearlyPrice: getPriceData({interval: 'year', amount: 12000, currency: 'usd'})
                });
                const otherTier = getProductData({
                    name: 'Premium',
                    monthlyPrice: getPriceData({interval: 'month', amount: 2000, currency: 'usd'}),
                    yearlyPrice: getPriceData({interval: 'year', amount: 20000, currency: 'usd'})
                });

                const site = getSiteData({
                    products: [activeTier, otherTier, getProductData({type: 'free'})],
                    portalProducts: [activeTier.id, otherTier.id]
                });

                const member = buildGiftMember({tier: activeTier});

                const {mockDoActionFn, queryByRole} = customSetup({site, member});

                // No "Cancel subscription" — gift subs cannot be cancelled.
                expect(queryByRole('button', {name: 'Cancel subscription'})).not.toBeInTheDocument();

                // Active-tier gift members are bounced back to AccountHomePage where their only
                // option is to "Continue" on the same tier (via continueGiftSubscription). The
                // change-plan UI is not available to them.
                expect(mockDoActionFn).toHaveBeenCalledWith('switchPage', {page: 'accountHome'});
                expect(mockDoActionFn).not.toHaveBeenCalledWith('checkoutPlan', expect.anything());
                expect(mockDoActionFn).not.toHaveBeenCalledWith('updateSubscription', expect.anything());
            });
        });
    });
});
