import React from 'react';
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
import { type Identity } from '@theorchard/suite-frontend';
import { renderInAppContext } from '@theorchard/suite-testing';
import accountDetailResponse from 'src/__fixtures__/graphql/account-detail-response.json';
import * as accountPayeeMutations from 'src/apollo/mutations/account-payee';
import * as accountPaymentTermMutations from 'src/apollo/mutations/account-payment-term';
import * as accountQuery from 'src/apollo/queries/account';
import * as abacusPayoneerProgramMoveTypeQuery from 'src/apollo/queries/abacus-payoneer-program-move-type';
import { ProgramMoveType } from 'src/apollo/definitions/globalTypes';
import PayeeForm from 'src/components/payee-form/payee-form';
import { getAccountDetail } from 'src/urls/frontend-royalties';
import * as validation from 'src/utils/form-validations';
import * as paymentNameUtil from 'src/utils/getPaymentEntityNameById';
import { BRAND_AWAL, BRAND_KNR } from '@theorchard/constants';
import * as payoneerProgramQuery from 'src/apollo/queries/payment-entity-payoneer-program';
import { payoneerProgramsList } from 'src/__fixtures__/graphql/payment-entity-payoneer-programs';
import type { GetPaymentEntityPayoneerProgramsListQuery } from 'src/apollo/queries/payment-entity-payoneer-program/__generated__/get-payment-entity-payoneer-programs-list';
import { NetworkStatus } from '@apollo/client';

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

describe('<PayeeForm>', () => {
    const createAccountPaymentTerm = jest.fn().mockResolvedValue({});
    const updateAccountPaymentTerm = jest.fn().mockResolvedValue({});
    const resyncPaymentReadiness = jest.fn().mockResolvedValue({});
    let useResyncPaymentReadinessSpy: jest.SpyInstance;

    afterEach(jest.restoreAllMocks);

    beforeEach(() => {
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            data: {
                ...accountDetailResponse,
                // @ts-expect-error: fixture type does not exactly match
                abacusAccount: {
                    ...accountDetailResponse?.abacusAccount,
                    accountPayee: {
                        ...accountDetailResponse?.abacusAccount?.accountPayee,
                        payoneerProgram: {
                            payoneerProgramId: 1,
                            payoneerProgramName: 'AWAL Core',
                        },
                    },
                },
            },
        });
        jest.spyOn(
            payoneerProgramQuery,
            'usePayoneerProgramsList'
        ).mockReturnValue({
            data: {
                ...payoneerProgramsList,
                abacusPaymentEntityPayoneerProgramsList: {
                    ...payoneerProgramsList.abacusPaymentEntityPayoneerProgramsList,
                    items: [
                        ...payoneerProgramsList
                            .abacusPaymentEntityPayoneerProgramsList.items,
                        {
                            referenceAgreementTypeId: '1',
                            paymentCurrency: 'USD',
                            referencePaymentTypeId: 1,
                            referencePaymentEntity: {
                                paymentEntityName: 'AWAL-UK',
                                referencePaymentEntityId: '1',
                            },
                            payoneerProgram: {
                                fundingCurrency: 'USD',
                                payoneerProgramId: 101176711,
                                payoneerProgramName: 'AWAL Core - USD',
                            },
                        },
                    ],
                },
            } as GetPaymentEntityPayoneerProgramsListQuery,
            fetchMore: jest.fn(),
            loading: false,
            error: undefined,
            networkStatus: NetworkStatus.ready,
        });
        jest.spyOn(
            accountPaymentTermMutations,
            'useCreateAccountPaymentTerm'
        ).mockReturnValue({ createAccountPaymentTerm, loading: false });
        jest.spyOn(
            accountPaymentTermMutations,
            'useUpdateAccountPaymentTerm'
        ).mockReturnValue({ updateAccountPaymentTerm, loading: false });
        useResyncPaymentReadinessSpy = jest
            .spyOn(accountPayeeMutations, 'useResyncPaymentReadiness')
            .mockReturnValue({ resyncPaymentReadiness, loading: false });
        jest.spyOn(
            abacusPayoneerProgramMoveTypeQuery,
            'useAbacusPayoneerProgramMoveType'
        ).mockReturnValue({
            data: undefined,
            loading: false,
            error: undefined,
        });
        jest.spyOn(paymentNameUtil, 'getPaymentEntityNameById').mockReturnValue(
            BRAND_AWAL
        );
    });

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

    it('renders', () => {
        const { container } = render();
        expect(container).toBeDefined();
        expect(screen.getByText('Paid By')).toBeDefined();
        expect(screen.getByText('Payment Currency')).toBeDefined();
        expect(screen.getByText('Payment Schedule')).toBeDefined();
        expect(screen.getByText('Payment Minimum')).toBeDefined();
    });

    it('redirects to account detail when edit form cancel button is clicked', () => {
        render();

        const cancelButton = screen.getByText('Cancel') as HTMLAnchorElement;

        expect(cancelButton?.href).toContain(getAccountDetail(123));
    });

    it('submits the form when all fields are filled', async () => {
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            data: {
                // @ts-expect-error: fixture type does not exactly match
                abacusAccount: accountDetailResponse.abacusAccount,
            },
        });
        // @ts-expect-error: allow empty return value
        jest.spyOn(validation, 'payeeFormValidation').mockReturnValue({});
        render();

        const createButton = await screen.findByRole('button', {
            name: 'Save',
        });
        fireEvent.click(createButton);

        await waitFor(() => {
            expect(updateAccountPaymentTerm).toHaveBeenCalled();
            expect(useResyncPaymentReadinessSpy).toHaveBeenCalledWith(
                { accountPayeeId: '' },
                { accountId: 123 }
            );
            expect(resyncPaymentReadiness).toHaveBeenCalledWith({
                variables: { accountPayeeId: '12345' },
            });
        });
    });

    it('submits the paymentEntity with paymentService', async () => {
        const mockIdentity = {
            features: {},
            id: '',
        };

        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            data: {
                abacusAccount: {
                    ...accountDetailResponse.abacusAccount,
                    // @ts-expect-error: fixture type does not exactly match
                    accountPaymentTerm: {
                        ...accountDetailResponse.abacusAccount
                            .accountPaymentTerm,
                    },
                },
            },
        });
        // @ts-expect-error: allow empty return value
        jest.spyOn(validation, 'payeeFormValidation').mockReturnValue({});
        render(mockIdentity);

        const createButton = await screen.findByRole('button', {
            name: 'Save',
        });
        fireEvent.click(createButton);

        await waitFor(() => {
            expect(updateAccountPaymentTerm).toHaveBeenCalledWith({
                variables: {
                    accountId: '25153',
                    accountPaymentTermId: '1',
                    agreementType: {
                        referenceAgreementTypeId: '1',
                        agreementType: 'AWAL Core',
                    },
                    currencyCode: 'USD',
                    paymentDescription: '',
                    paymentEntity: {
                        referencePaymentEntityId: '1',
                        paymentEntityName: 'AWAL-UK',
                    },
                    paymentMinimum: '33.00',
                    paymentSchedule: '30_days_after_quarter_end',
                    accountPayeeId: '12345',
                    payoneerPayeeId: '',
                    payoneerProgram: {
                        fundingCurrency: 'USD',
                        payoneerProgramId: 101176711,
                        payoneerProgramName: 'AWAL Core - USD',
                    },
                    paymentType: 'PAYMENT_SERVICE',
                    referencePaymentTypeId: '1',
                    sapVendorId: '6006038',
                    paymentEntityId: '1',
                    agreementTypeId: '1',
                },
            });
        });
    });

    it('renders warning when payee is included in payment this Statement Period', async () => {
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            data: {
                // @ts-expect-error: fixture type does not exactly match
                abacusAccount: {
                    ...accountDetailResponse.abacusAccount,
                    paymentPendingInCurrentStatementPeriod: {
                        accountId: '47679',
                    },
                },
            },
        });
        render();

        const warning = await screen.findByTestId('PendingPaymentAlert');
        expect(warning).toBeVisible();
    });

    it('saves the form when fields are updated', async () => {
        // @ts-expect-error: allow empty return value
        jest.spyOn(validation, 'payeeFormValidation').mockReturnValue({});
        render();

        const createButton = await screen.findByRole('button', {
            name: 'Save',
        });
        fireEvent.click(createButton);

        await waitFor(() => {
            expect(updateAccountPaymentTerm).toHaveBeenCalled();
        });
    });

    it('initializes agreementTypeId from accountDetails on load', async () => {
        render();

        await waitFor(() => {
            expect(screen.getByText('Payment Details Edit')).toBeDefined();
        });

        const validationSpy = jest.spyOn(validation, 'payeeFormValidation');
        const saveButton = await screen.findByRole('button', { name: 'Save' });

        fireEvent.click(saveButton);

        await waitFor(() => {
            expect(validationSpy).toHaveBeenCalledWith(
                expect.objectContaining({ agreementTypeId: '1' })
            );
        });
    });

    it('shows paymentSchedule error when paymentSchedule is missing', async () => {
        jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
            data: {
                abacusAccount: {
                    ...accountDetailResponse.abacusAccount,
                    // @ts-expect-error: fixture type does not exactly match
                    accountPaymentTerm: {
                        ...accountDetailResponse.abacusAccount
                            .accountPaymentTerm,
                        paymentSchedule: null,
                    },
                },
            },
        });
        render();

        const saveButton = await screen.findByRole('button', { name: 'Save' });
        fireEvent.click(saveButton);

        await waitFor(() => {
            expect(
                screen.getByText('Payment Schedule cannot be blank')
            ).toBeInTheDocument();
        });
    });

    describe('RELEASE_AND_WARN program move confirmation modal', () => {
        const ffEnabledIdentity: Identity = {
            features: {},
            id: '',
        };

        beforeEach(() => {
            updateAccountPaymentTerm.mockClear();
            resyncPaymentReadiness.mockClear();
            // @ts-expect-error: allow empty return value
            jest.spyOn(validation, 'payeeFormValidation').mockReturnValue({});
        });

        it('opens confirmation modal when move type is RELEASE_AND_WARN', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: {
                    abacusPayoneerProgramMoveType:
                        ProgramMoveType.RELEASE_AND_WARN,
                },
                loading: false,
                error: undefined,
            });

            render(ffEnabledIdentity);

            fireEvent.click(
                await screen.findByRole('button', { name: 'Save' })
            );

            expect(
                await screen.findByText('Confirm Payoneer Program Change')
            ).toBeInTheDocument();
        });

        it('calls executeSave when confirmation modal is confirmed', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: {
                    abacusPayoneerProgramMoveType:
                        ProgramMoveType.RELEASE_AND_WARN,
                },
                loading: false,
                error: undefined,
            });

            render(ffEnabledIdentity);

            fireEvent.click(
                await screen.findByRole('button', { name: 'Save' })
            );

            const confirmButton = await screen.findByRole('button', {
                name: 'Confirm change',
            });
            fireEvent.click(confirmButton);

            await waitFor(() => {
                expect(updateAccountPaymentTerm).toHaveBeenCalled();
            });
        });

        it('closes confirmation modal without saving when closed', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: {
                    abacusPayoneerProgramMoveType:
                        ProgramMoveType.RELEASE_AND_WARN,
                },
                loading: false,
                error: undefined,
            });

            render(ffEnabledIdentity);

            fireEvent.click(
                await screen.findByRole('button', { name: 'Save' })
            );

            const modal = screen.getByTestId(
                'PayeeForm-ConfirmMoveProgramModal'
            );

            expect(modal).toBeInTheDocument();

            const closeButton = within(modal).getByText('cancel', {
                selector: 'button',
            });

            fireEvent.click(closeButton);

            await waitFor(() => {
                expect(
                    screen.queryByTestId('PayeeForm-ConfirmMoveProgramModal')
                ).not.toBeInTheDocument();
            });
            expect(updateAccountPaymentTerm).not.toHaveBeenCalled();
        });

        it('saves directly without modal when move type is ADOPT_AND_RELEASE', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: {
                    abacusPayoneerProgramMoveType:
                        ProgramMoveType.ADOPT_AND_RELEASE,
                },
                loading: false,
                error: undefined,
            });

            render(ffEnabledIdentity);

            fireEvent.click(
                await screen.findByRole('button', { name: 'Save' })
            );

            await waitFor(() => {
                expect(updateAccountPaymentTerm).toHaveBeenCalled();
            });
            expect(
                screen.queryByText('Confirm Payoneer Program Change')
            ).not.toBeInTheDocument();
        });

        it('disables Save button while program move type is loading', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: undefined,
                loading: true,
                error: undefined,
            });

            render(ffEnabledIdentity);

            const saveButton = await screen.findByRole('button', {
                name: 'Save',
            });
            expect(saveButton).toBeDisabled();
        });
    });

    describe('moveTypeError handling', () => {
        const ffEnabledIdentity: Identity = {
            features: {},
            id: '',
        };
        const mockError = { message: 'Program move query failed' };

        it('shows server error alert when moveTypeError is present', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: undefined,
                loading: false,
                error: mockError as any,
            });

            render(ffEnabledIdentity);

            await waitFor(() => {
                expect(
                    screen.getByText('Program move query failed')
                ).toBeInTheDocument();
            });

            const saveButton = await screen.findByRole('button', {
                name: 'Save',
            });

            expect(saveButton).toBeDisabled();
        });

        it('clears server error when moveTypeError resolves after having an error', async () => {
            const spy = jest
                .spyOn(
                    abacusPayoneerProgramMoveTypeQuery,
                    'useAbacusPayoneerProgramMoveType'
                )
                .mockReturnValue({
                    data: undefined,
                    loading: false,
                    error: mockError as any,
                });

            const { rerender } = renderInAppContext(<PayeeForm />, {
                identity: ffEnabledIdentity,
            });

            await waitFor(() => {
                expect(
                    screen.getByText('Program move query failed')
                ).toBeInTheDocument();
            });

            spy.mockReturnValue({
                data: undefined,
                loading: false,
                error: undefined,
            });

            rerender(<PayeeForm />);

            await waitFor(() => {
                expect(
                    screen.queryByText('Program move query failed')
                ).not.toBeInTheDocument();
            });
        });
    });

    describe('Save button behaviour', () => {
        const ffEnabledIdentity: Identity = {
            features: {},
            id: '',
        };

        it('disables Save button when payoneerProgram is null', async () => {
            jest.spyOn(accountQuery, 'useAccountFullDetail').mockReturnValue({
                data: {
                    ...accountDetailResponse,
                    // @ts-expect-error: fixture type does not exactly match
                    abacusAccount: {
                        ...accountDetailResponse?.abacusAccount,
                        accountPayee: {
                            ...accountDetailResponse?.abacusAccount
                                ?.accountPayee,
                            payoneerProgram: null,
                            referencePaymentType: null,
                        },
                    },
                },
            });

            // account-detail-response fixture has payoneerProgram: null
            render(ffEnabledIdentity);

            const saveButton = await screen.findByRole('button', {
                name: 'Save',
            });
            expect(saveButton).toBeDisabled();
        });

        it('does not disable Save button when isKNR', async () => {
            jest.spyOn(
                paymentNameUtil,
                'getPaymentEntityNameById'
            ).mockReturnValue(BRAND_KNR);

            render(ffEnabledIdentity);

            const saveButton = await screen.findByRole('button', {
                name: 'Save',
            });

            expect(saveButton).not.toBeDisabled();
        });
    });

    describe('Enhanced server error handling', () => {
        const ffEnabledIdentity: Identity = {
            features: {},
            id: '',
        };

        const programMoveError = { message: 'Invalid program move detected' };
        const mutationError = 'General submission failed';

        beforeEach(() => {
            updateAccountPaymentTerm.mockReset();
            updateAccountPaymentTerm.mockResolvedValue({});

            // @ts-expect-error: allow empty return value
            jest.spyOn(validation, 'payeeFormValidation').mockReturnValue({});
        });

        it('prioritizes moveTypeError from query over existing server errors in state', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: undefined,
                loading: false,
                error: programMoveError as any,
            });

            render(ffEnabledIdentity);

            await waitFor(() => {
                expect(
                    screen.getByText(programMoveError.message)
                ).toBeInTheDocument();
            });

            expect(screen.queryByText(mutationError)).not.toBeInTheDocument();

            const saveButton = screen.getByRole('button', { name: 'Save' });

            expect(saveButton).toBeDisabled();
        });

        it('shows mutation error when moveTypeError is absent', async () => {
            jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            ).mockReturnValue({
                data: {
                    abacusPayoneerProgramMoveType:
                        ProgramMoveType.ADOPT_AND_RELEASE,
                },
                loading: false,
                error: undefined,
            });

            updateAccountPaymentTerm.mockRejectedValueOnce([
                { message: mutationError },
            ]);

            render(ffEnabledIdentity);

            const saveButton = await screen.findByRole('button', {
                name: 'Save',
            });

            fireEvent.click(saveButton);

            await waitFor(() => {
                expect(screen.getByText(mutationError)).toBeInTheDocument();
            });
        });

        it('immediately disables Save button if moveTypeError appears while editing', async () => {
            const moveTypeSpy = jest.spyOn(
                abacusPayoneerProgramMoveTypeQuery,
                'useAbacusPayoneerProgramMoveType'
            );

            moveTypeSpy.mockReturnValue({
                data: undefined,
                loading: false,
                error: undefined,
            });

            const { rerender } = render(ffEnabledIdentity);

            expect(
                screen.getByRole('button', { name: 'Save' })
            ).not.toBeDisabled();

            moveTypeSpy.mockReturnValue({
                data: undefined,
                loading: false,
                error: programMoveError as any,
            });

            rerender(<PayeeForm />);

            await waitFor(() => {
                expect(
                    screen.getByText(programMoveError.message)
                ).toBeInTheDocument();
                expect(
                    screen.getByRole('button', { name: 'Save' })
                ).toBeDisabled();
            });
        });
    });
});
