import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { Identity } from '@theorchard/suite-frontend';
import { renderInAppContext } from '@theorchard/suite-testing';
import {
    contractDetails,
    contractDetailsKNR,
} from 'src/__fixtures__/graphql/contract-details';
import {
    AbacusContractLifecycleStatus,
    AbacusContractLifecycleSchedulePeriodType,
    AbacusContractLifecycleScheduleRenewalType,
} from 'src/apollo/definitions/globalTypes';
import * as contractMutations from 'src/apollo/mutations/contract';
import {
    ContractLifecycleEditRenewal,
    ContractLifecycleEditRenewalPropTypes,
} from 'src/components/contract-lifecycle-edit/contract-lifecycle-edit-renewal';
import * as formatting from 'src/utils/contract-lifecycle-schedule';
import * as validation from 'src/utils/form-validations/contract-form-validation';
import { ABACUS_PROFILE, USER_FEATURES } from '../../../constants';
import {
    ContractLifecycleContext,
    ContractLifecycleContextProps,
} from '../../../contexts/contract-lifecycle-context';
import { AbacusContract } from '../../../types/abacus-contract';

type Contract = AbacusContract;
type LifecycleSchedule = Contract['lifecycleSchedules'][0];

describe('<ContractLifecycleEditRenewal />', () => {
    const defaultProps = {
        isSidecarOpen: true,
        setIsSidecarOpen: jest.fn(),
    };
    const mockIdentityFFEnabledCurrentPeriodEdit = {
        id: 'Jane User',
        profileType: ABACUS_PROFILE,
        features: {
            [USER_FEATURES.ABACUS_CONTRACT_EDIT_CURRENT_PERIOD_START_DATE]: true,
        },
    };

    const render = (
        props: ContractLifecycleEditRenewalPropTypes = defaultProps,
        contextValues?: Partial<ContractLifecycleContextProps>,
        identity?: Identity
    ) => {
        return renderInAppContext(
            <ContractLifecycleContext.Provider
                value={{
                    contract: contractDetails.abacusContract as Contract,
                    isTerminatingContract: false,
                    setIsTerminatingContract: jest.fn(),
                    isEditingContractRenewalRules: false,
                    setIsEditingContractRenewalRules: jest.fn(),
                    setContractLifecycle: jest.fn(),
                    ...contextValues,
                }}
            >
                <ContractLifecycleEditRenewal {...props} />
            </ContractLifecycleContext.Provider>,
            {
                identity,
            }
        );
    };

    afterEach(jest.restoreAllMocks);

    it('renders the edit contract lifecycle form in a sidecar', () => {
        render();
        expect(
            screen.getByTestId('ContractLifecycleEditRenewalFormSidecar')
        ).toBeInTheDocument();
    });

    describe('Current Period section', () => {
        it('renders with distribution contract type header text', () => {
            render();

            const distroHeader = screen.getByText(
                'Current Period & Renewal Rules'
            );
            const nrHeader = screen.queryByText('Renewal & Collection Rules');

            expect(distroHeader).toBeInTheDocument();
            expect(nrHeader).not.toBeInTheDocument();
        });

        it('renders with neighbouring rights contract type header text', () => {
            render(defaultProps, {
                contract:
                    contractDetailsKNR.abacusContract as unknown as Contract,
            });

            const nrHeader = screen.getByText('Renewal & Collection Rules');
            const distroHeader = screen.queryByText(
                'Current Period & Renewal Rules'
            );

            expect(nrHeader).toBeInTheDocument();
            expect(distroHeader).not.toBeInTheDocument();
        });

        it('renders a disabled form when terminating a contract', () => {
            render(defaultProps, { isTerminatingContract: true });

            const renewalTypeSelect = screen.getAllByTestId('SuiteSelectInput');

            expect(renewalTypeSelect.length).toEqual(5);
            expect(renewalTypeSelect[0].className).toContain('disabled');
        });

        it('does not allow editing of the current period start when the contract is not continuously renewable', () => {
            render();

            expect(
                screen.queryByLabelText('Current Period Start Date')
            ).toBeNull();
        });

        describe('FF enabled: abacus_contract_edit_current_period_start_date', () => {
            const mockRenewedLifecycle = {
                ...contractDetails.abacusContract.lifecycle,
                lastRenewed: new Date(),
            };
            const mockContinuousLifecycleSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
            };

            it('allows editing of the current period start for contracts that have a null lastRenewed attribute', async () => {
                const mockNewContract = {
                    ...contractDetails.abacusContract,
                    lifecycle: {
                        ...mockRenewedLifecycle,
                        lastRenewed: null,
                    },
                } as Contract;

                render(
                    defaultProps,
                    { contract: mockNewContract },
                    mockIdentityFFEnabledCurrentPeriodEdit
                );

                expect(
                    await screen.findByTestId('start-date-tooltip')
                ).toBeInTheDocument();
            });

            it('allows editing of the current period start for contracts that are continuously renewable', async () => {
                const mockNewContract = {
                    ...contractDetails.abacusContract,
                    lifecycleSchedules: [mockContinuousLifecycleSchedule],
                } as Contract;

                render(
                    defaultProps,
                    { contract: mockNewContract },
                    mockIdentityFFEnabledCurrentPeriodEdit
                );

                expect(
                    await screen.findByTestId('start-date-tooltip')
                ).toBeInTheDocument();
            });

            it('allows editing of the current period start for contracts that are continuously renewable even when they have been renewed', async () => {
                const mockNewContract = {
                    ...contractDetails.abacusContract,
                    lifecycle: mockRenewedLifecycle,
                    lifecycleSchedules: [mockContinuousLifecycleSchedule],
                } as Contract;

                render(
                    defaultProps,
                    { contract: mockNewContract },
                    mockIdentityFFEnabledCurrentPeriodEdit
                );

                expect(
                    await screen.findByTestId('start-date-tooltip')
                ).toBeInTheDocument();
            });
        });
    });

    describe('Subsequent Period section', () => {
        it(`renders only a disabled Renewal Rules dropdown when the current period is ${AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE}`, () => {
            const continuouslyActiveSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
            } as LifecycleSchedule;
            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [continuouslyActiveSchedule],
            } as Contract;

            render({ ...defaultProps }, { contract: mockContract });

            const renewalTypeSelect = screen.getAllByTestId('SuiteSelectInput');

            expect(renewalTypeSelect.length).toEqual(2);
            expect(renewalTypeSelect[1].className).toContain('disabled');
        });

        it(`renders a Renewal Rules dropdown with placeholder text when the current period is ${AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE}`, () => {
            const continuouslyActiveSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
            } as LifecycleSchedule;
            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [continuouslyActiveSchedule],
            } as Contract;

            render({ ...defaultProps }, { contract: mockContract });

            const renewalTypeSelect = screen.getAllByTestId(
                'renewal-type-select'
            );

            expect(renewalTypeSelect.length).toEqual(2);
            expect(renewalTypeSelect[1].textContent).toEqual(
                'Select Renewal Rules'
            );
        });

        it(`renders an enabled form when the current period is NOT ${AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE}`, () => {
            render();

            const renewalTypeSelect = screen.getAllByTestId('SuiteSelectInput');
            const terminationNoticeInput = screen
                .getAllByTestId('termination-notice-interval')[1]
                .querySelector('input')!;

            expect(renewalTypeSelect[1].className).not.toContain('disabled');
            expect(terminationNoticeInput.className).not.toContain('disabled');
        });

        it('renders a disabled form when terminating a contract', () => {
            render(defaultProps, { isTerminatingContract: true });

            const renewalTypeSelect = screen.getAllByTestId('SuiteSelectInput');

            expect(renewalTypeSelect.length).toEqual(5);
            expect(renewalTypeSelect[1].className).toContain('disabled');
        });
    });

    describe('Terminate section', () => {
        it('renders Termination section if contract is ACTIVE', () => {
            render();
            expect(screen.getByText('Termination')).toBeInTheDocument();
            expect(screen.getByText('Add Termination')).toBeInTheDocument();
            expect(screen.getByTestId('PlusGlyphIcon')).toBeInTheDocument();
        });

        it('hides Termination section if contract is not ACTIVE', () => {
            render(defaultProps, {
                contract: {
                    ...contractDetails.abacusContract,
                    lifecycle: {
                        ...contractDetails.abacusContract.lifecycle,
                        lifecycleStatus: AbacusContractLifecycleStatus.INIT,
                    },
                } as Contract,
            });
            expect(screen.queryByText('Termination')).not.toBeInTheDocument();
        });

        it('renders Termination form on click of TERMINATE Button', () => {
            render(defaultProps, { isTerminatingContract: true });
            expect(screen.getByText('Termination')).toBeInTheDocument();
            expect(screen.getByText('Cancel Termination')).toBeInTheDocument();
            expect(screen.getByTestId('MinusGlyphIcon')).toBeInTheDocument();

            expect(screen.getByText('Termination Date')).toBeInTheDocument();
            expect(screen.getByText('Client Notice Date')).toBeInTheDocument();
        });

        it('saves termination effective date on click of save button and closes sidecar', async () => {
            const terminateContract = jest.fn().mockResolvedValue({});
            jest.spyOn(
                contractMutations,
                'useTerminateContract'
            ).mockReturnValue(terminateContract);
            jest.spyOn(
                validation,
                'contractTerminationFormValidation'
            ).mockReturnValue({});

            const contractLifecycle = {
                ...contractDetails.abacusContract.lifecycle,
                terminationEffective: '2055-01-01',
            };

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycle: contractLifecycle,
            } as Contract;

            render(defaultProps, {
                contract: mockContract,
                isTerminatingContract: true,
            });

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

            expect(terminateContract).toHaveBeenCalled();
        });

        it('shows validation error if present and the termination effective date is not saved', async () => {
            const terminateContract = jest.fn().mockResolvedValue({});
            jest.spyOn(
                contractMutations,
                'useTerminateContract'
            ).mockReturnValue(terminateContract);
            jest.spyOn(
                validation,
                'contractTerminationFormValidation'
            ).mockReturnValue({
                terminationEffective: 'validation error',
            });

            const contractLifecycle = {
                ...contractDetails.abacusContract.lifecycle,
                terminationEffective: '2055-01-01',
            };

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycle: contractLifecycle,
            } as Contract;

            render(defaultProps, {
                contract: mockContract,
                isTerminatingContract: true,
            });
            const saveButton = screen.getByRole('button', { name: 'save' });
            fireEvent.click(saveButton);

            expect(terminateContract).not.toHaveBeenCalled();

            const errorMsg = screen.getByTestId(
                'ContractLifecycleTerminationError'
            );
            expect(errorMsg.textContent).toEqual('validation error');
        });
    });

    describe('Save Button', () => {
        it('renders enabled by default', () => {
            render();
            const saveButton = screen.getByRole('button', { name: 'save' });
            expect(saveButton).not.toBeDisabled();
        });

        it('is disabled when current schedule has no renewal type selected', () => {
            const continuouslyActiveSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
            } as LifecycleSchedule;

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [continuouslyActiveSchedule],
            } as Contract;

            render(defaultProps, { contract: mockContract });

            const clearRenewalTypeIcon = screen.getByTestId('ClearGlyphIcon');
            fireEvent.click(clearRenewalTypeIcon);

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

        it('is disabled when current schedule termination notice is invalid', () => {
            const continuouslyActiveSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
                terminationNoticeDetail: {
                    contractLifecycleScheduleDetailId: '102',
                    periodInterval: 0,
                    periodType: AbacusContractLifecycleSchedulePeriodType.DAY,
                },
            } as LifecycleSchedule;

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [continuouslyActiveSchedule],
            } as Contract;

            render(defaultProps, { contract: mockContract });

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

        it('is disabled when current schedule end date is invalid', () => {
            const renewAfterCertainDateSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                scheduleEnd: null,
            } as LifecycleSchedule;

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [renewAfterCertainDateSchedule],
            } as Contract;

            render(defaultProps, { contract: mockContract });

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

        it('is disabled when current schedule renewal offset is invalid', () => {
            const renewPeriodicallySchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[0],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.RENEW_PERIODICALLY,
                renewalOffsetDetail: {
                    contractLifecycleScheduleDetailId: '101',
                    periodInterval: 0,
                    periodType: AbacusContractLifecycleSchedulePeriodType.YEAR,
                },
            } as LifecycleSchedule;

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [renewPeriodicallySchedule],
            } as Contract;

            render(defaultProps, { contract: mockContract });

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

        it('is disabled when subsequent schedule has no renewal type selected', () => {
            render();

            const clearRenewalTypeIcon =
                screen.getAllByTestId('ClearGlyphIcon')[1];
            fireEvent.click(clearRenewalTypeIcon);

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

        it('is disabled when subsequent schedule termination notice is invalid', () => {
            const continuouslyActiveSchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[1],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.CONTINUOUSLY_ACTIVE,
                terminationNoticeDetail: {
                    contractLifecycleScheduleDetailId: '102',
                    periodInterval: 0,
                    periodType: AbacusContractLifecycleSchedulePeriodType.DAY,
                },
            } as LifecycleSchedule;

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [
                    contractDetails.abacusContract.lifecycleSchedules[0],
                    continuouslyActiveSchedule,
                ],
            } as Contract;

            render(defaultProps, { contract: mockContract });

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

        it('is disabled when subsequent schedule renewal offset is invalid', () => {
            const renewPeriodicallySchedule = {
                ...contractDetails.abacusContract.lifecycleSchedules[1],
                renewalType:
                    AbacusContractLifecycleScheduleRenewalType.RENEW_PERIODICALLY,
                renewalOffsetDetail: {
                    contractLifecycleScheduleDetailId: '101',
                    periodInterval: 0,
                    periodType: AbacusContractLifecycleSchedulePeriodType.YEAR,
                },
            } as LifecycleSchedule;

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycleSchedules: [
                    contractDetails.abacusContract.lifecycleSchedules[0],
                    renewPeriodicallySchedule,
                ],
            } as Contract;

            render(defaultProps, { contract: mockContract });

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

        it('is disabled when termination effective is invalid', () => {
            const contractLifecycle = {
                ...contractDetails.abacusContract.lifecycle,
                terminationEffective: null,
            };

            const mockContract = {
                ...contractDetails.abacusContract,
                lifecycle: contractLifecycle,
            } as Contract;

            render(defaultProps, {
                contract: mockContract,
                isTerminatingContract: true,
            });

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

        it('validates the contract lifecycle schedules when clicked', () => {
            const updateContractLifecycleSchedules = jest
                .fn()
                .mockResolvedValue({});
            jest.spyOn(
                contractMutations,
                'useUpdateContractLifecycleSchedules'
            ).mockReturnValue(updateContractLifecycleSchedules);

            const contractLifecycleScheduleValidation = jest
                .spyOn(validation, 'contractLifecycleScheduleFormValidation')
                .mockReturnValue({ renewalType: 'fake error' });

            render();

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

            expect(contractLifecycleScheduleValidation).toHaveBeenCalledTimes(
                2
            );
            expect(updateContractLifecycleSchedules).not.toHaveBeenCalled();
            expect(screen.getAllByText('fake error').length).toEqual(2);
        });

        it('saves updated contract lifecycle schedules and closes sidecar on click', () => {
            const updateContractLifecycleSchedules = jest
                .fn()
                .mockResolvedValue({});
            jest.spyOn(
                contractMutations,
                'useUpdateContractLifecycleSchedules'
            ).mockReturnValue(updateContractLifecycleSchedules);

            const formatSchedules = jest.spyOn(
                formatting,
                'formatContractLifecycleSchedules'
            );

            jest.spyOn(
                validation,
                'contractLifecycleScheduleFormValidation'
            ).mockReturnValue({});

            render();

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

            expect(updateContractLifecycleSchedules).toHaveBeenCalled();
            expect(formatSchedules).toHaveBeenCalled();
            expect(defaultProps.setIsSidecarOpen).toHaveBeenCalledWith(false);
        });

        it('does not close the sidecar and displays an error when saving fails', async () => {
            const updateContractLifecycleSchedules = jest
                .fn()
                .mockRejectedValue('fake error');
            jest.spyOn(
                contractMutations,
                'useUpdateContractLifecycleSchedules'
            ).mockReturnValue(updateContractLifecycleSchedules);
            jest.spyOn(
                validation,
                'contractLifecycleScheduleFormValidation'
            ).mockReturnValue({});

            render();

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

            expect(updateContractLifecycleSchedules).toHaveBeenCalled();
            expect(saveButton).toBeDisabled();

            await waitFor(() => {
                expect(screen.getByTestId('error-alert')).toBeInTheDocument();
            });
        });
    });
});
