import React, { type JSX } from 'react';
import { ApolloError } from '@apollo/client/errors';
import { screen } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import * as accountDetail from 'src/__fixtures__/graphql/account-detail-response.json';
import paymentAccounts from 'src/__fixtures__/graphql/payment-group-payment-accounts-response.json';
import * as paymentGroupPaymentResponse from 'src/__fixtures__/graphql/payment-group-payment-response.json';
import * as accountQuery from 'src/apollo/queries/account';
import * as paymentGroupPaymentQuery from 'src/apollo/queries/payment-group-payment';
import * as paymentGroupPaymentAccountQuery from 'src/apollo/queries/payment-group-payment-account';
import PaymentGroupDetail from 'src/components/payment-group-detail/payment-group-detail';
import * as payoneerOutageAlert from 'src/components/shared/payoneer-service-outage-alert';
import {
    PAYMENT_GROUP_PAYMENT_ACTION_STATUSES,
    PAYMENT_GROUP_PAYMENT_ACTIONS,
    PAYONEER_OUTAGE_MESSAGE_TITLE,
    PAYONEER_OUTAGE_MESSAGE_CONTENT,
    PAYONEER_OUTAGE_TYPES,
} from 'src/constants';

const mockPaymentGroupPayment =
    paymentGroupPaymentResponse.abacusPaymentGroupPayment;

const mockGroupDetailsResponse = {
    groupCriteria: {
        currencyCodes: ['AUD', 'EUR', 'JPY', 'NOK', 'SEK'],
        referencePaymentEntities: [
            {
                referencePaymentEntityId: 1,
                paymentEntityName: 'AWA-UK',
            },
        ],
        paymentSchedules: [
            '45 days after month end',
            '60 days after month end',
        ],
    },
    groupName: 'Orange Peal Records, Inc.',
    isReusable: true,
    paymentGroupId: mockPaymentGroupPayment.paymentGroup.paymentGroupId,
};

jest.mock('react-router-dom', () => ({
    ...jest.requireActual('react-router-dom'),
    useParams: jest.fn().mockReturnValue({ paymentGroupPaymentId: '583' }),
}));

describe('<PaymentGroupDetail>', () => {
    let usePaymentGroupPaymentSpy: jest.SpyInstance;
    let mockUsePayoneerOutageStatus: jest.SpyInstance;

    afterAll(jest.restoreAllMocks);

    beforeEach(() => {
        usePaymentGroupPaymentSpy = jest
            .spyOn(paymentGroupPaymentQuery, 'useFullPaymentGroupPayment')
            .mockReturnValue({
                data: paymentGroupPaymentResponse,
                error: new ApolloError({}),
                loading: false,
                refetch: jest.fn(),
                startPolling: () => null,
                stopPolling: () => null,
            });
        jest.spyOn(
            paymentGroupPaymentAccountQuery,
            'usePaymentGroupPaymentAccounts'
        ).mockReturnValue({
            data: paymentAccounts,
            loading: false,
            error: undefined,
        });
        jest.spyOn(
            paymentGroupPaymentAccountQuery,
            'usePendingPaymentGroupPaymentAccounts'
        ).mockReturnValue({
            data: {
                abacusPendingPaymentGroupPaymentAccounts: {
                    items: [],
                    totalCount: 0,
                },
            },
            error: undefined,
            loading: false,
        });
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            // @ts-expect-error: fixture type doesn't exactly match
            data: accountDetail,
        });

        // Default mock for usePayoneerOutageStatus (no alert)
        mockUsePayoneerOutageStatus = jest
            .spyOn(payoneerOutageAlert, 'usePayoneerOutageStatus')
            .mockReturnValue({
                alert: null,
                isPayoneerOutage: false,
                isLoading: false,
                error: undefined,
            });
    });

    const render = () => renderInAppContext(<PaymentGroupDetail />);

    it('renders', () => {
        render();

        const overviewTab = screen.getByTestId('tab-payment-overview');
        expect(overviewTab.classList.contains('active')).toBe(true);

        const paymentsTab = screen.getByTestId('tab-payments');
        expect(paymentsTab.classList.contains('active')).toBe(false);

        const paymentPendingTab = screen.getByTestId('tab-payment-pending');
        expect(paymentPendingTab.classList.contains('active')).toBe(false);

        expect(screen.getByText('Group:')).toBeDefined();
    });

    it('renders statement period name', () => {
        render();
        expect(screen.getByText('Statement Period:')).toBeDefined();
        expect(screen.getByText('June 2021 (270)')).toBeDefined();
    });

    it('renders all three tabs for non reusable payment group', () => {
        const nonReusableResponse = {
            ...mockPaymentGroupPayment.paymentGroup,
            isReusable: false,
        };

        usePaymentGroupPaymentSpy.mockReturnValue({
            data: {
                abacusPaymentGroupPayment: {
                    ...mockGroupDetailsResponse,
                    paymentGroup: nonReusableResponse,
                },
            },
            error: false,
            loading: false,
            refetch: jest.fn(),
        });

        render();

        const tabCaptions = ['Overview', 'Payments', 'Payment Pending (0)'];
        const tabs = screen.getAllByRole('tab');
        expect(Object.keys(tabs).length).toEqual(3);
        Object.values(tabs).forEach(tab =>
            expect(tabCaptions).toContain(tab.textContent)
        );
    });

    it('displays errors when present', () => {
        const paymentGroupPaymentError = {
            ...mockPaymentGroupPayment,
            actionStates: [
                {
                    abacusStateId: 1,
                    actionName: PAYMENT_GROUP_PAYMENT_ACTIONS.GENERATE_PAYMENTS,
                    actionStatus: PAYMENT_GROUP_PAYMENT_ACTION_STATUSES.ERROR,
                },
            ],
        };

        usePaymentGroupPaymentSpy.mockReturnValue({
            data: { abacusPaymentGroupPayment: paymentGroupPaymentError },
            error: false,
            loading: false,
            refetch: jest.fn(),
        });

        render();
        const error = screen.getByTestId('alert');
        expect(error.textContent?.includes('error')).toBe(true);
        expect(error.textContent).toEqual(
            'Generate Payments has encountered an error'
        );
    });

    describe('payments already generated', () => {
        beforeEach(() => {
            const data = {
                ...paymentGroupPaymentResponse,
                abacusPaymentGroupPayment: {
                    ...paymentGroupPaymentResponse.abacusPaymentGroupPayment,
                    actionStates: [],
                },
            };
            usePaymentGroupPaymentSpy = jest
                .spyOn(paymentGroupPaymentQuery, 'useFullPaymentGroupPayment')
                .mockReturnValue({
                    data,
                    error: new ApolloError({}),
                    loading: false,
                    refetch: jest.fn(),
                    startPolling: () => null,
                    stopPolling: () => null,
                });
        });

        it('has an enabled payment approval button', async () => {
            render();
            expect(
                screen.getByRole('button', { name: 'Payment Approval' })
            ).toBeEnabled();
        });
    });

    describe('generating payments', () => {
        it('displays a message when payments are generating', () => {
            render();
            expect(screen.getByText('Payments are generating')).toBeDefined();
        });

        it('disables tabs when payments are generating', () => {
            const nonReusableResponse = {
                ...mockPaymentGroupPayment.paymentGroup,
                isReusable: true,
            };
            usePaymentGroupPaymentSpy.mockReturnValue({
                data: {
                    abacusPaymentGroupPayment: {
                        ...mockPaymentGroupPayment,
                        paymentGroup: nonReusableResponse,
                    },
                },
                error: false,
                loading: false,
                refetch: jest.fn(),
            });

            render();
            const tabTitles = ['Overview', 'Payments', 'Payment Pending (0)'];
            const disabledTabs = screen.getAllByRole('tab');

            expect(Object.keys(disabledTabs).length).toEqual(3);
            Object.values(disabledTabs).forEach(tab =>
                expect(tabTitles).toContain(tab.textContent)
            );
        });

        it('disables the payment approval button when payments are generating', () => {
            render();
            expect(
                screen.getByRole('button', { name: 'Payment Approval' })
            ).toBeDisabled();
        });
    });

    describe('calculating payments', () => {
        it('displays a message and disables tabs when payments are calculating', () => {
            usePaymentGroupPaymentSpy.mockReturnValue({
                data: {
                    abacusPaymentGroupPayment: {
                        ...mockPaymentGroupPayment,
                        actionStates: [
                            {
                                abacusStateId: '333629',
                                actionName: 'calculate_payments',
                                actionStatus: 'running',
                            },
                        ],
                    },
                },
                error: false,
                loading: false,
                refetch: jest.fn(),
            });
            render();
            expect(screen.getByText('Payments are calculating')).toBeDefined();

            const disabledTabs = screen.getAllByRole('tab');
            expect(Object.keys(disabledTabs).length).toEqual(3);
        });
    });

    describe('Payoneer Service Status Integration Tests', () => {
        const createMockAlert = (type: string) => (
            <div role="alert" data-variant="warn">
                <div>{PAYONEER_OUTAGE_MESSAGE_TITLE[type]}</div>
                <div>{PAYONEER_OUTAGE_MESSAGE_CONTENT[type]}</div>
            </div>
        );

        const mockPayoneerStatus = (
            alert: JSX.Element | null,
            isOutage = false
        ) => {
            mockUsePayoneerOutageStatus.mockReturnValue({
                alert,
                isPayoneerOutage: isOutage,
            });
        };

        it('does not display alert when Payoneer service is healthy (statusCode: 200)', () => {
            mockPayoneerStatus(null, false);
            render();

            expect(screen.queryByRole('alert')).toBeNull();
            expect(
                screen.queryByText(
                    PAYONEER_OUTAGE_MESSAGE_TITLE[PAYONEER_OUTAGE_TYPES.OUTAGE]
                )
            ).toBeNull();
            expect(
                screen.queryByText(
                    PAYONEER_OUTAGE_MESSAGE_TITLE[PAYONEER_OUTAGE_TYPES.UNKNOWN]
                )
            ).toBeNull();
        });

        it('displays outage alert when Payoneer service indicates outage (statusCode: 100)', () => {
            const alert = createMockAlert(PAYONEER_OUTAGE_TYPES.OUTAGE);
            mockPayoneerStatus(alert, true);
            render();

            expect(screen.getByRole('alert')).toBeInTheDocument();
            expect(
                screen.getByText(
                    PAYONEER_OUTAGE_MESSAGE_TITLE[PAYONEER_OUTAGE_TYPES.OUTAGE]
                )
            ).toBeInTheDocument();
            expect(
                screen.getByText(
                    PAYONEER_OUTAGE_MESSAGE_CONTENT[
                        PAYONEER_OUTAGE_TYPES.OUTAGE
                    ]
                )
            ).toBeInTheDocument();
        });

        it('displays unknown status alert when Payoneer service status is unknown (statusCode: 99)', () => {
            const alert = createMockAlert(PAYONEER_OUTAGE_TYPES.UNKNOWN);
            mockPayoneerStatus(alert, false);
            render();

            expect(screen.getByRole('alert')).toBeInTheDocument();
            expect(
                screen.getByText(
                    PAYONEER_OUTAGE_MESSAGE_TITLE[PAYONEER_OUTAGE_TYPES.UNKNOWN]
                )
            ).toBeInTheDocument();
            expect(
                screen.getByText(
                    PAYONEER_OUTAGE_MESSAGE_CONTENT[
                        PAYONEER_OUTAGE_TYPES.UNKNOWN
                    ]
                )
            ).toBeInTheDocument();
        });
    });
});
