import React from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import { referenceFlowthroughCalculations } from 'src/__fixtures__/graphql/reference-flowthrough-calculations';
import * as getReferenceFlowthroughCalculationQuery from 'src/apollo/queries/reference-flowthrough-calculation';
import { CONTRACT_FLOWTHROUGH_RECOUPMENT_CAP_ERROR_MSG } from 'src/constants';
import { ContractFlowthroughFormFields } from '../contract-flowthrough-form-fields';
import type { AbacusContractFlowthroughInput } from '@theorchard/accounting-apps-shared/dist/esm/src/apollo/definitions/globalTypes';
import { AbacusContractFlowthroughStatus } from 'src/apollo/definitions/globalTypes';

describe('<ContractFlowthroughFormFields />', () => {
    const flowthroughInput: Partial<AbacusContractFlowthroughInput> = {
        contractId: '1',
        referenceFlowthroughCalculationId: undefined,
        flowthroughRate: undefined,
        recoupmentCap: undefined,
    };

    const defaultProps = {
        abacusContractFlowthrough: flowthroughInput,
        errors: {},
        isEditForm: false,
        setAbacusContractFlowthrough: jest.fn(),
        setErrors: jest.fn(),
        serverErrors: {},
        setServerErrors: jest.fn(),
    };

    const render = (props = defaultProps) =>
        renderInAppContext(<ContractFlowthroughFormFields {...props} />);

    it('renders calculation select field', () => {
        render();

        expect(
            screen.getByTestId('contractFlowthroughCalculation')
        ).toBeInTheDocument();
    });

    it('renders rate input field when rate is used', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '1',
            },
        });

        expect(screen.getByText('Rate')).toBeInTheDocument();
        expect(
            screen.getByTestId('contractFlowthroughRate')
        ).toBeInTheDocument();
    });

    it('renders recoupment cap input field when recoupment cap is used', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '5',
            },
        });

        expect(screen.getByText('Recoupment Cap')).toBeInTheDocument();
        expect(
            screen.getByTestId('contractFlowthroughRecoupmentCap')
        ).toBeInTheDocument();
    });

    it('renders calculation comment input field when comment is used', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '6',
            },
        });

        expect(screen.getByText('Calculation Comment')).toBeInTheDocument();
        expect(
            screen.getByTestId('contractFlowthroughCalculationComment')
        ).toBeInTheDocument();
    });

    it('renders status field when edit form is true', () => {
        const props = {
            ...defaultProps,
            isEditForm: true,
        };
        render(props);
        expect(
            screen.getByTestId('contractFlowthroughStatus')
        ).toBeInTheDocument();
    });

    it('renders flowthrough not supported message', () => {
        render();

        expect(
            screen.getByText(
                'Abacus does not support automatic flowthrough payments yet.'
            )
        ).toBeInTheDocument();
    });

    it('updates calculation on change', () => {
        jest.spyOn(
            getReferenceFlowthroughCalculationQuery,
            'useGetReferenceFlowthroughCalculations'
        ).mockReturnValue({
            data: {
                abacusReferenceFlowthroughCalculations: {
                    __typename: 'AbacusReferenceFlowthroughCalculationList',
                    totalCount:
                        referenceFlowthroughCalculations
                            ?.abacusReferenceFlowthroughCalculation
                            ?.totalCount || 0,
                    items: referenceFlowthroughCalculations.abacusReferenceFlowthroughCalculation.items.map(
                        item => ({
                            ...item,
                            __typename: 'AbacusReferenceFlowthroughCalculation',
                        })
                    ),
                },
            },
            loading: false,
            error: undefined,
        });
        render();

        const calculationSelect = screen.getByTestId('SuiteSelectInputValue');
        fireEvent.click(calculationSelect);
        fireEvent.click(screen.getByText('Gross Revenue * FT%'));
        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                referenceFlowthroughCalculationId: '4',
            })
        );
    });

    it('updates flowthrough rate on change', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '1',
            },
        });
        const flowthroughRateInput = screen.getByTestId(
            'contractFlowthroughRate'
        );
        fireEvent.change(flowthroughRateInput, {
            target: { value: '25' },
        });

        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                flowthroughRate: '25',
            })
        );
    });

    it('allows user to input 0 flowthrough rate value', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '1',
            },
        });
        const flowthroughRateInput = screen.getByTestId(
            'contractFlowthroughRate'
        );
        fireEvent.change(flowthroughRateInput, {
            target: { value: '0' },
        });

        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                flowthroughRate: '0',
            })
        );
    });

    it('updates recoupment cap on change', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '5',
            },
        });
        const recoupmentCapInput = screen.getByTestId(
            'contractFlowthroughRecoupmentCap'
        );
        fireEvent.change(recoupmentCapInput, {
            target: { value: '5000' },
        });

        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                recoupmentCap: '5000',
            })
        );

        fireEvent.blur(recoupmentCapInput);
    });

    it('updates recoupment cap with decimal value on change', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '5',
            },
        });
        const recoupmentCapInput = screen.getByTestId(
            'contractFlowthroughRecoupmentCap'
        );
        fireEvent.change(recoupmentCapInput, {
            target: { value: '1500.75' },
        });

        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                recoupmentCap: '1500.75',
            })
        );
    });

    it('rejects recoupment cap with more than 2 decimal places on blur', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '5',
            },
        });
        const recoupmentCapInput = screen.getByTestId(
            'contractFlowthroughRecoupmentCap'
        );
        fireEvent.change(recoupmentCapInput, {
            target: { value: '1500.123' },
        });
        fireEvent.blur(recoupmentCapInput);

        // updateError uses a functional setErrors updater — invoke the last call to verify the error message
        const lastSetErrorsArg =
            defaultProps.setErrors.mock.calls[
                defaultProps.setErrors.mock.calls.length - 1
            ][0];
        expect(lastSetErrorsArg({})).toEqual({
            recoupmentCap: CONTRACT_FLOWTHROUGH_RECOUPMENT_CAP_ERROR_MSG,
        });
        // parseFloat path must not have been reached
        expect(
            defaultProps.setAbacusContractFlowthrough
        ).not.toHaveBeenCalledWith(
            expect.objectContaining({ recoupmentCap: 1500.123 })
        );
    });

    it('updates calculation comment on change', () => {
        render({
            ...defaultProps,
            abacusContractFlowthrough: {
                ...flowthroughInput,
                referenceFlowthroughCalculationId: '6',
            },
        });
        const calculationCommentInput = screen.getByTestId(
            'contractFlowthroughCalculationComment'
        );
        fireEvent.change(calculationCommentInput, {
            target: { value: 'comment' },
        });

        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                calculationComment: 'comment',
            })
        );

        fireEvent.blur(calculationCommentInput);
    });

    it('updates flowthrough status on change', () => {
        window.HTMLElement.prototype.scrollTo = jest.fn();
        const props = {
            ...defaultProps,
            isEditForm: true,
            abacusContractFlowthrough: {
                ...defaultProps.abacusContractFlowthrough,
                flowthroughStatus: AbacusContractFlowthroughStatus.ACTIVE,
            },
        };
        render(props);
        const statusSelector = screen.getByText('Active');
        fireEvent.click(statusSelector);
        const pausedOption = screen.getByText('Paused');
        fireEvent.click(pausedOption);
        expect(defaultProps.setAbacusContractFlowthrough).toHaveBeenCalledWith(
            expect.objectContaining({
                flowthroughStatus: 'PAUSED',
            })
        );
    });
});
