import React from 'react';
import { ApolloError } from '@apollo/client';
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
import { createIdentity, renderInAppContext } from '@theorchard/suite-testing';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { worksheetPaymentContractAdvanceResponse } from 'src/__fixtures__/graphql/worksheet-payment-contract-advance-response';
import * as worksheetPaymentContractAdvanceMutation from 'src/apollo/mutations/contract-advance';
import * as worksheetPaymentContractAdvanceQuery from 'src/apollo/queries/payment-group-payment';
import * as payoneerOutageAlert from 'src/components/shared/payoneer-service-outage-alert';
import {
    PAYMENT_CAN_BE_RESENT_ALERT,
    PAYMENT_IS_FAILED_ALERT,
    USER_FEATURES,
    PAYONEER_OUTAGE_MESSAGE_TITLE,
    PAYONEER_OUTAGE_MESSAGE_CONTENT,
    PAYONEER_OUTAGE_TYPES,
} from 'src/constants';
import { IS_RESENT_PAYMENT_ALLOWED_SERVER_MSG } from 'src/utils/payment-overview';
import AdvanceDetail from '../advance-detail';
import type { WorksheetPaymentContractAdvanceQuery } from 'src/apollo/queries/payment-group-payment/__generated__/worksheet-payment-contract-advance';
import { CLASSNAME as GENERIC_PAYMENT_APPROVAL_SIDECAR_CLASSNAME } from 'src/components/generic-payment-approval/generic-payment-approval';

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

describe('<AdvanceDetail/>', () => {
    let worksheetPaymentContractAdvanceSpy: jest.SpyInstance<{
        data: WorksheetPaymentContractAdvanceQuery | undefined;
        error: ApolloError | undefined;
        loading: boolean;
    }>;
    let mockUsePayoneerOutageStatus: jest.SpyInstance;
    const mockDeletePayment = jest.fn().mockResolvedValue({
        data: {
            deleteWorksheetPaymentContractAdvance: {
                deleted: true,
            },
        },
    });
    const render = (features: { [x: number]: boolean }) =>
        renderInAppContext(<AdvanceDetail />, {
            identity: createIdentity({ features }),
        });

    let features = {
        [USER_FEATURES.ABACUS_CREATE_AND_APPROVE_PAYMENTS]: true,
        [USER_FEATURES.ABACUS_ADVANCE_PAYMENT_APPROVAL_SIDECAR]: false,
    };

    afterEach(jest.restoreAllMocks);

    beforeEach(() => {
        worksheetPaymentContractAdvanceSpy = jest
            .spyOn(
                worksheetPaymentContractAdvanceQuery,
                'useWorksheetPaymentContractAdvance'
            )
            .mockReturnValue({
                data: worksheetPaymentContractAdvanceResponse as WorksheetPaymentContractAdvanceQuery,
                error: undefined,
                loading: false,
                stopPolling: jest.fn(),
                startPolling: jest.fn(),
            });
        jest.spyOn(
            worksheetPaymentContractAdvanceMutation,
            'useDeletePaymentAdvance'
        ).mockReturnValue({
            deleteWorksheet: mockDeletePayment,
            loading: false,
        });

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

    it('calls the worksheetPaymentContractAdvance query with param', () => {
        render(features);
        expect(worksheetPaymentContractAdvanceSpy).toHaveBeenCalledWith(321);
    });

    it('renders header deletable payment', () => {
        render(features);
        expect(screen.getByText('Statement Period:')).toBeDefined();
        expect(screen.getByText('February 2024')).toBeDefined();

        expect(screen.getByText('Created:')).toBeDefined();
        expect(screen.getByText('2024-01-23')).toBeDefined();

        expect(
            screen.getByRole('button', {
                name: /Payment Approval/i,
            })
        ).toBeDefined();
        expect(screen.getByTestId('TrashGlyphIcon')).toBeTruthy();

        expect(
            screen.getByText('JPY Advance (WHT + VAT) 1000', {
                ignore: '.breadcrumb-item',
            })
        ).toBeDefined();
        expect(screen.getByText('Not paid')).toBeDefined();
    });

    it("doesn't render delete button if FF is disabled", () => {
        features = {
            [USER_FEATURES.ABACUS_CREATE_AND_APPROVE_PAYMENTS]: false,
        };
        render(features);

        expect(screen.queryByTestId('TrashGlyphIcon')).toBeNull();
    });

    it('allows you to delete a deletable payment', async () => {
        const history = createMemoryHistory();
        jest.spyOn(history, 'push');
        renderInAppContext(
            <Router history={history}>
                {' '}
                <AdvanceDetail />{' '}
            </Router>,
            {
                identity: createIdentity({
                    features: {
                        [USER_FEATURES.ABACUS_CREATE_AND_APPROVE_PAYMENTS]: true,
                    },
                }),
            }
        );
        expect(screen.getByTestId('TrashGlyphIcon')).toBeTruthy();
        fireEvent.click(screen.getByTestId('TrashGlyphIcon'));
        expect(mockDeletePayment).toHaveBeenCalledWith({
            variables: { worksheetPaymentContractAdvanceId: 321 },
        });
        expect(mockDeletePayment).toHaveReturnedTimes(1);
        await waitFor(() => {
            expect(history.push).toHaveBeenCalledWith(`/payments`);
        });
    });

    it('renders header non deletable payment', () => {
        const mockWorksheetResponse = {
            ...worksheetPaymentContractAdvanceResponse,
        };
        mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates =
            mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates.map(
                state => ({ ...state, actionStatus: 'complete' })
            );
        worksheetPaymentContractAdvanceSpy = jest
            .spyOn(
                worksheetPaymentContractAdvanceQuery,
                'useWorksheetPaymentContractAdvance'
            )
            .mockReturnValue({
                data: mockWorksheetResponse as WorksheetPaymentContractAdvanceQuery,
                error: undefined,
                loading: false,
                stopPolling: jest.fn(),
                startPolling: jest.fn(),
            });
        render(features);
        expect(screen.getByText('Statement Period:')).toBeDefined();
        expect(screen.getByText('February 2024')).toBeDefined();

        expect(screen.getByText('Created:')).toBeDefined();
        expect(screen.getByText('2024-01-23')).toBeDefined();

        expect(
            screen.getByRole('button', {
                name: /Payment Approval/i,
            })
        ).toBeDefined();
        expect(screen.queryByTestId('TrashGlyphIcon')).toBeNull();

        expect(
            screen.getByText('JPY Advance (WHT + VAT) 1000', {
                ignore: '.breadcrumb-item',
            })
        ).toBeDefined();
        expect(screen.getByText('Paid')).toBeDefined();
    });

    it('renders failed payment', () => {
        const mockWorksheetResponse = {
            ...worksheetPaymentContractAdvanceResponse,
        };
        mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates =
            mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates.map(
                state => ({
                    ...state,
                    actionStatus: 'error',
                    message: 'test error',
                })
            );
        worksheetPaymentContractAdvanceSpy = jest
            .spyOn(
                worksheetPaymentContractAdvanceQuery,
                'useWorksheetPaymentContractAdvance'
            )
            .mockReturnValue({
                data: mockWorksheetResponse as WorksheetPaymentContractAdvanceQuery,
                error: undefined,
                loading: false,
                stopPolling: jest.fn(),
                startPolling: jest.fn(),
            });
        render(features);
        expect(screen.getByText(PAYMENT_IS_FAILED_ALERT.title)).toBeDefined();
        expect(screen.getByText(PAYMENT_IS_FAILED_ALERT.text)).toBeDefined();

        expect(screen.getByText('Error Message')).toBeDefined();
        expect(screen.getByText('test error')).toBeDefined();
    });

    it('renders "Payment can be resent"', () => {
        const mockWorksheetResponse = {
            ...worksheetPaymentContractAdvanceResponse,
        };
        mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates =
            mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates.map(
                state => ({
                    ...state,
                    actionStatus: 'error',
                    message: IS_RESENT_PAYMENT_ALLOWED_SERVER_MSG,
                })
            );
        worksheetPaymentContractAdvanceSpy = jest
            .spyOn(
                worksheetPaymentContractAdvanceQuery,
                'useWorksheetPaymentContractAdvance'
            )
            .mockReturnValue({
                data: mockWorksheetResponse as WorksheetPaymentContractAdvanceQuery,
                error: undefined,
                loading: false,
                stopPolling: jest.fn(),
                startPolling: jest.fn(),
            });
        render(features);
        expect(
            screen.getByText(PAYMENT_CAN_BE_RESENT_ALERT.title)
        ).toBeDefined();
        expect(
            screen.getByText(PAYMENT_CAN_BE_RESENT_ALERT.text)
        ).toBeDefined();
    });

    it('renders non failed payment', () => {
        const mockWorksheetResponse = {
            ...worksheetPaymentContractAdvanceResponse,
        };
        mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates =
            mockWorksheetResponse.worksheetPaymentContractAdvance.actionStates.map(
                state => ({ ...state, actionStatus: 'complete' })
            );
        worksheetPaymentContractAdvanceSpy = jest
            .spyOn(
                worksheetPaymentContractAdvanceQuery,
                'useWorksheetPaymentContractAdvance'
            )
            .mockReturnValue({
                data: mockWorksheetResponse as WorksheetPaymentContractAdvanceQuery,
                error: undefined,
                loading: false,
                stopPolling: jest.fn(),
                startPolling: jest.fn(),
            });
        render(features);
        expect(screen.queryByText(PAYMENT_IS_FAILED_ALERT.title)).toBeNull();
        expect(screen.queryByText(PAYMENT_IS_FAILED_ALERT.text)).toBeNull();

        expect(screen.queryByText('Error Message')).toBeNull();
    });

    it('renders Overview section', () => {
        render(features);
        const links: HTMLAnchorElement[] = screen.getAllByRole('link');

        expect(screen.getByText('Overview')).toBeDefined();

        expect(screen.getByText('Account ID')).toBeDefined();
        expect(screen.getByText('35975')).toBeDefined();

        expect(screen.getByText('Payment Entity')).toBeDefined();
        expect(screen.getByText('AWAL-UK')).toBeDefined();

        expect(screen.getByText('Account Name')).toBeDefined();
        expect(links[1].textContent).toEqual(
            'Account Name TEST GDA - EUR - VAT'
        );
        expect(links[1].href).toContain('/account/35975');

        expect(screen.getByText('Program Name')).toBeDefined();
        expect(screen.getByText('AWAL Core - EUR')).toBeDefined();

        expect(screen.getByText('Program ID')).toBeDefined();
        expect(screen.getByText('100176770')).toBeDefined();

        expect(screen.getByText('Contract ID')).toBeDefined();
        expect(screen.getByText('500009')).toBeDefined();

        expect(screen.getByText('Contract Name')).toBeDefined();
        expect(links[2].textContent).toEqual(
            'Contract Name TEST GDA - EUR - VAT'
        );
        expect(links[2].href).toContain('/contract/500009');
    });

    it('renders the Contract Advance Information section', () => {
        render(features);
        expect(screen.getByText('Contract Advance Information')).toBeDefined();

        expect(screen.getByText('Advance Id')).toBeDefined();
        expect(screen.getByText('54321')).toBeDefined();

        expect(screen.getByText('Advance Currency')).toBeDefined();
        expect(screen.getByText('JPY')).toBeDefined();

        expect(
            within(screen.getByTestId('advanceInformation')).getByText(
                'Pre Tax Amount'
            )
        ).toBeDefined();
        expect(
            within(screen.getByTestId('advanceInformation')).getByText('1000')
        ).toBeDefined();

        expect(
            within(screen.getByTestId('advanceInformation')).getByText('WHT')
        ).toBeDefined();
        expect(
            within(screen.getByTestId('advanceInformation')).getByText('-200')
        ).toBeDefined();

        expect(
            within(screen.getByTestId('advanceInformation')).getByText('VAT')
        ).toBeDefined();
        expect(
            within(screen.getByTestId('advanceInformation')).getByText('180')
        ).toBeDefined();

        expect(
            within(screen.getByTestId('advanceInformation')).getByText(
                'Post Tax Payable Amount'
            )
        ).toBeDefined();
        expect(
            within(screen.getByTestId('advanceInformation')).getByText('980')
        ).toBeDefined();
    });

    it('renders Payment Information section', () => {
        render(features);

        expect(screen.getByText('Payment Information')).toBeDefined();

        expect(screen.getByText('Payment Currency')).toBeDefined();
        expect(screen.getByText('EUR')).toBeDefined();

        expect(
            within(screen.getByTestId('paymentInformation')).getByText(
                'Post Tax Payable Amount'
            )
        ).toBeDefined();
        expect(screen.getByTestId('PostTaxPayableTooltip')).toBeDefined();
        expect(screen.getByText('6.92')).toBeDefined();

        expect(
            within(screen.getByTestId('paymentInformation')).getByText(
                'Pre Tax Amount'
            )
        ).toBeDefined();
        expect(screen.getByTestId('AmountTooltip')).toBeDefined();
        expect(screen.getByText('7.06')).toBeDefined();

        expect(
            within(screen.getByTestId('paymentInformation')).getByText('WHT')
        ).toBeDefined();
        expect(screen.getByTestId('WhtTooltip')).toBeDefined();
        expect(screen.getByText('-1.42')).toBeDefined();

        expect(
            within(screen.getByTestId('paymentInformation')).getByText('VAT')
        ).toBeDefined();
        expect(screen.getByTestId('VatTooltip')).toBeDefined();
        expect(screen.getByText('1.28')).toBeDefined();

        expect(screen.getByText('Date Sent to Payoneer')).toBeDefined();
    });

    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: any, isOutage = false) => {
            mockUsePayoneerOutageStatus.mockReturnValue({
                alert,
                isPayoneerOutage: isOutage,
            });
        };

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

            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(features);

            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(features);

            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();
        });
    });

    describe(`FF ${USER_FEATURES.ABACUS_ADVANCE_PAYMENT_APPROVAL_SIDECAR}`, () => {
        it('shows old sidecar when FF disabled', async () => {
            const { baseElement } = render(features);
            const button = screen.getByText('Payment Approval');

            expect(button).toBeInTheDocument();

            fireEvent.click(button);

            await waitFor(() => {
                expect(
                    screen.getByTestId('AdvancePaymentApprovalSidecar')
                ).toBeInTheDocument();
                expect(
                    baseElement.querySelector(
                        `.${GENERIC_PAYMENT_APPROVAL_SIDECAR_CLASSNAME}`
                    )
                ).not.toBeInTheDocument();
            });
        });

        it('shows new sidecar when FF enabled', async () => {
            const { baseElement } = render({
                ...features,
                [USER_FEATURES.ABACUS_ADVANCE_PAYMENT_APPROVAL_SIDECAR]: true,
            });
            const button = screen.getByText('Payment Approval');

            expect(button).toBeInTheDocument();

            fireEvent.click(button);

            await waitFor(() => {
                expect(
                    screen.queryByTestId('AdvancePaymentApprovalSidecar')
                ).not.toBeInTheDocument();
                expect(
                    baseElement.querySelector(
                        `.${GENERIC_PAYMENT_APPROVAL_SIDECAR_CLASSNAME}`
                    )
                ).toBeInTheDocument();
            });
        });
    });
});
