import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import {
    ABACUS_ACTION_STATUSES,
    ABACUS_ACTIONS,
    PAYMENT_SERVICES,
} from '@theorchard/accounting-apps-shared';
import { type Identity } from '@theorchard/suite-frontend';
import { renderInAppContext } from '@theorchard/suite-testing';
import * as accountDetailResponse from 'src/__fixtures__/graphql/account-detail-response.json';
import * as paymentHoldResponse from 'src/__fixtures__/graphql/payment-hold-response.json';
import * as accountQuery from 'src/apollo/queries/account';
import * as ledgerQuery from 'src/apollo/queries/ledger';
import * as paymentMinimumsQuery from 'src/apollo/queries/payment-minimums';
import * as paymentSearchQuery from 'src/apollo/queries/payment-search';
import {
    AccountDetail,
    PAYONEER_PROGRAM_MOVE_FAILED_MESSAGE,
} from 'src/components/account-detail/account-detail';
import { USER_FEATURES } from 'src/constants';
import { getAccountDetail as getMHAccountDetail } from 'src/urls/externals/moneyhub';
import { getAccountDetail as getOAAccountDetail } from 'src/urls/externals/oa';

const render = (identity?: Identity) =>
    renderInAppContext(<AccountDetail />, { identity });

describe('<AccountDetail>', () => {
    beforeEach(() => {
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            // @ts-expect-error: fixture type doesn't exactly match
            data: accountDetailResponse,
            error: undefined,
            loading: false,
        });
        // @ts-expect-error: fixture type doesn't exactly match
        jest.spyOn(ledgerQuery, 'useAccountLedgerList').mockResolvedValue({
            data: {
                abacusAccountLedgerList: {
                    items: [],
                    totalCount: 0,
                },
            },
            loading: false,
        });
        jest.spyOn(
            paymentMinimumsQuery,
            'usePaymentMinimumByCurrencyCode'
            // @ts-expect-error: this type error is wrong
        ).mockResolvedValue({});
    });

    afterEach(() => jest.restoreAllMocks());

    it('renders with payment-info as default tab', () => {
        render();

        expect(screen.getByTestId('accountDetailPage')).toBeDefined();
        const payeeInfoTab = screen.getByTestId('tabPaymentInfo');
        expect(payeeInfoTab.classList.contains('active')).toBeTruthy();
    });

    it('renders the <AccountLedger /> on the Accounting tab', () => {
        render();
        const accountingTab = screen.getByRole('tab', { name: 'Accounting' });
        fireEvent.click(accountingTab);
        expect(accountingTab.classList.contains('active')).toBe(true);

        expect(screen.queryByTestId('accountAccountingDetails')).toBeNull();
        expect(screen.getByTestId('accountLedgerActivities')).toBeDefined();
    });

    it('renders the <PaymentGeneralDetail /> with paymentType', () => {
        const mockIdentity = {
            features: {},
            id: '',
        };
        render(mockIdentity);
        expect(screen.getByTestId('paymentGeneralDetails')).toBeDefined();
        expect(screen.getByText('PAYMENT_SERVICE')).toBeDefined();
    });

    it('renders the <PaymentDetail />', () => {
        render();
        expect(screen.getByTestId('payeePaymentInfo')).toBeDefined();
    });

    it.each([
        PAYMENT_SERVICES.PAYONEER_WHITELABEL,
        PAYMENT_SERVICES.PAYCHEX,
        PAYMENT_SERVICES.PAYONEER_WIRE,
    ])('renders the <PaymentDetailWhitelabel />', paymentService => {
        const data = { ...accountDetailResponse };
        data.abacusAccount.accountPayee.referencePaymentType.paymentService =
            paymentService;
        data.abacusAccount.accountPaymentTerm.paymentType = paymentService;

        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            // @ts-expect-error: fixture type doesn't exactly match
            data: data,
            error: undefined,
            loading: false,
        });
        render();
        expect(screen.getByTestId('PaymentDetailWhitelabel')).toBeDefined();
    });

    it('renders the <PaymentHoldDetail />', () => {
        render();
        expect(screen.getByTestId('accountPaymentHold')).toBeDefined();
    });

    it('does NOT render the <PaymentHoldHistory /> when no history', () => {
        render();
        expect(screen.queryByTestId('accountPaymentHoldHistory')).toBeNull();
    });

    it('renders the <PaymentHoldHistory /> when history', () => {
        const data = { ...accountDetailResponse };
        // @ts-expect-error: fixture type doesn't exactly match
        data.abacusAccount.paymentHoldHistory =
            paymentHoldResponse.abacusPaymentHold.paymentHoldHistory;
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            // @ts-expect-error: fixture type doesn't exactly match
            data: data,
            error: undefined,
            loading: false,
        });
        render();
        expect(screen.getByTestId('accountPaymentHoldHistory')).toBeDefined();
    });

    it('renders the <AttachedContractsCards />', () => {
        render();
        expect(screen.getByTestId('paymentDetailContracts')).toBeDefined();
        const cards = screen.getAllByTestId('contractCard-testId');
        expect(cards.length).toBeGreaterThan(0);
    });

    it('renders the <AccountIcon />', () => {
        render();
        expect(
            screen.getByTestId('AccountDetail-header-main-test').innerHTML
        ).toContain('<img data-testid="OrchardBrandIcon"');
    });

    it('renders "View in application" links', () => {
        const { container } = render();
        expect(container).toBeDefined();

        const links = new Set(
            // @ts-expect-error: href does exist on elem
            screen.getAllByRole('link').map(elem => elem.href)
        );
        const oaLink = getOAAccountDetail(
            accountDetailResponse.abacusAccount.accountId
        );
        const mhLink = getMHAccountDetail(
            accountDetailResponse.abacusAccount.accountId
        );
        expect(links.has(oaLink)).toBe(true);
        expect(links.has(mhLink)).toBe(true);
        expect(screen.getByText('View in Application:')).toBeDefined();
    });

    describe('Payment History tab', () => {
        it('does not render the tab when the FF is disabled', () => {
            render({ features: {}, id: '' });

            expect(
                screen.queryByRole('tab', { name: 'Payment History' })
            ).toBeNull();
            expect(screen.queryByTestId('tabPaymentHistory')).toBeNull();
        });

        it('renders the account payment search table when the FF is enabled', () => {
            jest.spyOn(
                paymentSearchQuery,
                'usePaymentSearchQuery'
            ).mockReturnValue({
                data: undefined,
                loading: false,
                error: undefined,
            });

            const mockIdentity = {
                features: {
                    [USER_FEATURES.TAP_PAYMENT_HISTORY]: true,
                },
                id: '',
            };
            render(mockIdentity);

            const paymentHistoryTab = screen.getByRole('tab', {
                name: 'Payment History',
            });
            expect(paymentHistoryTab).toBeDefined();

            fireEvent.click(paymentHistoryTab);
            expect(paymentHistoryTab.classList.contains('active')).toBe(true);
            expect(
                screen.getByTestId('accountPaymentHistory')
            ).toBeInTheDocument();
            expect(
                screen.getByTestId('PaymentSearch-Table')
            ).toBeInTheDocument();
            // Account-scoped toolbar: contract dropdown shown, account filter hidden
            expect(screen.getByTestId('ContractDropdown')).toBeInTheDocument();
            expect(
                screen.queryByPlaceholderText('Filter by Account ID')
            ).toBeNull();
        });

        it('does not mount the tab (or fire its query) until it is opened', () => {
            const querySpy = jest
                .spyOn(paymentSearchQuery, 'usePaymentSearchQuery')
                .mockReturnValue({
                    data: undefined,
                    loading: false,
                    error: undefined,
                });

            render({
                features: { [USER_FEATURES.TAP_PAYMENT_HISTORY]: true },
                id: '',
            });

            // The tab control exists, but its content is not mounted while
            // another tab is active, so the account payment query has not fired.
            expect(
                screen.getByRole('tab', { name: 'Payment History' })
            ).toBeDefined();
            expect(screen.queryByTestId('PaymentSearch-Table')).toBeNull();
            expect(querySpy).not.toHaveBeenCalled();

            fireEvent.click(
                screen.getByRole('tab', { name: 'Payment History' })
            );

            // Opening the tab mounts the content and fires the query.
            expect(
                screen.getByTestId('PaymentSearch-Table')
            ).toBeInTheDocument();
            expect(querySpy).toHaveBeenCalled();
        });
    });

    describe('Payoneer Program Update Failed alert', () => {
        const mockIdentity = {
            features: {},
            id: '',
        };
        const buildDataWithEligibilityState = (
            actionStatus: (typeof ABACUS_ACTION_STATUSES)[keyof typeof ABACUS_ACTION_STATUSES],
            message: string
        ) => ({
            ...accountDetailResponse,
            abacusAccount: {
                ...accountDetailResponse.abacusAccount,
                accountPayee: {
                    ...accountDetailResponse.abacusAccount.accountPayee,
                    actionStates: [
                        ...accountDetailResponse.abacusAccount.accountPayee.actionStates.filter(
                            s =>
                                s.actionName !==
                                ABACUS_ACTIONS.PAYMENT_ELIGIBILITY
                        ),
                        {
                            abacusStateId: '9999',
                            actionName: ABACUS_ACTIONS.PAYMENT_ELIGIBILITY,
                            actionStatus,
                            createdAt: '2024-01-01T00:00:00.000000',
                            lastModified: '2024-01-01T00:00:00.000000',
                            message,
                        },
                    ],
                },
            },
        });

        it('does not show the alert when payment_eligibility status is approved', () => {
            jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
                data: buildDataWithEligibilityState(
                    ABACUS_ACTION_STATUSES.APPROVED,
                    PAYONEER_PROGRAM_MOVE_FAILED_MESSAGE
                ) as any,
                error: undefined,
                loading: false,
            });

            render(mockIdentity);

            expect(
                screen.queryByText('Payoneer Program Update Failed')
            ).toBeNull();
        });

        it('does not show the alert when message does not contain the failure phrase', () => {
            jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
                data: buildDataWithEligibilityState(
                    ABACUS_ACTION_STATUSES.INIT,
                    'Some other eligibility message'
                ) as any,
                error: undefined,
                loading: false,
            });

            render(mockIdentity);

            expect(
                screen.queryByText('Payoneer Program Update Failed')
            ).toBeNull();
        });

        it('shows the alert when status is not approved and message contains the failure phrase', () => {
            jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
                data: buildDataWithEligibilityState(
                    ABACUS_ACTION_STATUSES.INIT,
                    `Payee has not started sign up. ${PAYONEER_PROGRAM_MOVE_FAILED_MESSAGE}`
                ) as any,
                error: undefined,
                loading: false,
            });

            render(mockIdentity);

            expect(
                screen.getByText('Payoneer Program Update Failed')
            ).toBeInTheDocument();
        });
    });
});
