import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { Segment } from '@theorchard/suite-frontend';
import { renderInAppContext } from '@theorchard/suite-testing';
import { getMergedProduct, getProductMetadata } from 'lib/mockProductData';
import {
    CR_CATEGORY,
    OTHER_CANNED_RESPONSE_ID,
    REJECTION_CLOSING_ID,
    REJECTION_OPENING_ID,
    REJECTION_OPENING_ID_V2,
    WILL_NOT_DELIVER_ID,
} from 'src/constants';
import * as rejectProductReview from 'src/data/mutations/reject/rejectProductReview';
import * as cannedResponseNoteQuery from 'src/data/queries/cannedResponseNote/cannedResponseNote';
import * as cannedResponsesQuery from 'src/data/queries/cannedResponses/cannedResponses';
import * as features from 'src/utils/features';
import ProductModalRejectionSidecar, {
    CLASS_NAME,
    Props,
} from '../productModalRejectionSidecar';

const defaultProps: Props = {
    isOpen: true,
    product: getProductMetadata(),
    mergedProduct: getMergedProduct(),
    closeModal: jest.fn(),
    onReviewCompleted: jest.fn(),
    productReviewQueueId: 1,
    submittedByUserLanguage: null,
};

describe('<ProductModalRejectionSidecar>', () => {
    const mockTriggerRejectProductMutation = jest.fn();

    const mockQueries = () => {
        jest.spyOn(
            rejectProductReview,
            'useRejectProductMutation'
        ).mockImplementation(() => mockTriggerRejectProductMutation);

        jest.spyOn(
            cannedResponsesQuery,
            'useCannedResponsesQuery'
        ).mockReturnValue({
            loading: false,
            error: undefined,
            data: [
                {
                    cannedResponseCategory: { id: '1', name: 'foo' },
                    cannedResponseId: '1',
                    keyword: 'ba',
                },
                {
                    cannedResponseCategory: { id: '1', name: 'foo' },
                    cannedResponseId: '2',
                    keyword: 'dc',
                },
                {
                    cannedResponseCategory: { id: '11', name: 'Other' },
                    cannedResponseId: '72',
                    keyword: 'Other',
                },
                {
                    cannedResponseCategory: { id: '3', name: 'foobar' },
                    cannedResponseId: '3',
                    keyword: 'asdasd',
                },
                {
                    cannedResponseCategory: {
                        id: '13',
                        name: 'Permanent Rejection',
                    },
                    cannedResponseId: '120',
                    keyword: 'Will Not Deliver',
                },
                {
                    cannedResponseCategory: {
                        id: '14',
                        name: 'AI Generated Content',
                    },
                    cannedResponseId: '126',
                    keyword: 'AI Generated Track Audio',
                },
            ],
        });
    };

    jest.spyOn(
        cannedResponseNoteQuery,
        'useFetchCannedNote'
    ).mockImplementation(() => async (id: string) => {
        if (id === REJECTION_OPENING_ID) {
            return {
                cannedResponseId: id,
                noteKeyword: 'opening',
                noteText: 'some opening text',
            };
        }
        if (id === REJECTION_OPENING_ID_V2) {
            return {
                cannedResponseId: id,
                noteKeyword: 'opening',
                noteText: 'some opening text v2',
            };
        }
        if (id === REJECTION_CLOSING_ID) {
            return {
                cannedResponseId: id,
                noteKeyword: 'closing',
                noteText: 'some closing text',
            };
        }
        if (id === OTHER_CANNED_RESPONSE_ID) {
            return {
                cannedResponseId: id,
                noteKeyword: 'Other',
                noteText: 'Other',
            };
        }
        if (id === WILL_NOT_DELIVER_ID) {
            return {
                cannedResponseId: id,
                noteKeyword: 'Will Not Deliver',
                noteText: 'Thank you for submitting {{productTitle}}.',
            };
        }

        return {
            cannedResponseId: '1',
            noteKeyword: 'ba',
            noteText: 'hello world',
        };
    });

    beforeEach(() => {
        mockQueries();
    });
    afterEach(() => jest.clearAllMocks());

    const renderComponent = (customProps?: Partial<Props>) =>
        renderInAppContext(
            <ProductModalRejectionSidecar {...defaultProps} {...customProps} />
        );

    test('renders sidecar and form', () => {
        const { getByTestId } = renderComponent();
        expect(getByTestId('ProductModalRejectionSidecar')).toBeInTheDocument();
        expect(
            getByTestId('ProductModalRejectionSidecar-Form')
        ).toBeInTheDocument();
        expect(getByTestId('form-select-rejection-reason')).toBeInTheDocument();
        expect(
            getByTestId('form-control-additional-rejection-notes')
        ).toBeInTheDocument();
    });

    describe('multiselect', () => {
        test('renders rejection reasons from canned responses query', async () => {
            const { findByText } = renderComponent();

            fireEvent.click(screen.getByRole('combobox', { hidden: true }));
            await screen.findByTestId('SuiteListView');
            const optionOne = await findByText('ba');
            const optionTwo = await findByText('dc');

            expect(optionOne).toBeVisible();
            expect(optionTwo).toBeVisible();
        });

        test('renders other category in canned responses', async () => {
            const { findAllByText } = renderComponent();

            fireEvent.click(screen.getByRole('combobox', { hidden: true }));
            await screen.findByTestId('SuiteListView');
            const categoryAndOption = await findAllByText('Other');

            expect(categoryAndOption[1]).toBeVisible();
        });
    });

    test('renders rejection reason definitions note', async () => {
        const { getByTestId } = renderComponent();

        const formGroup = getByTestId('form-group-rejection-reason');
        const reasonDefinitions = formGroup.querySelector(
            `.${CLASS_NAME}-reason-definitions`
        );

        expect(reasonDefinitions).toBeVisible();
    });

    describe.each([
        ['some opening text v2', 'hello world'],
        ['some opening text', ' '],
    ])(
        'additional notes should be displayed in preview when set',
        (openingText, noteText) => {
            afterEach(() => jest.clearAllMocks());

            test('tracks changes to additional notes field', async () => {
                const { getByTestId, findByText } = renderComponent();

                const reasonInput = getByTestId('SuiteSelectInputValue');
                fireEvent.click(reasonInput);

                const optionOne = await findByText('ba');
                fireEvent.click(optionOne);

                const textarea = getByTestId(
                    'form-control-additional-rejection-notes'
                );
                fireEvent.change(textarea, {
                    target: { value: noteText },
                });

                await waitFor(() => {
                    expect(
                        getByTestId('preview-notes-label')
                    ).toBeInTheDocument();
                });

                const previewLabel = getByTestId('preview-notes-label');
                fireEvent.click(previewLabel);

                const previewBody = getByTestId('preview-notes-body');
                expect(previewBody).toHaveTextContent(openingText);
                expect(previewBody).toHaveTextContent(noteText);
            });
        }
    );

    describe('Update rejection reasons', () => {
        test('Add a rejection reason', async () => {
            const { getByTestId, findByText } = renderComponent();
            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            await waitFor(() => {
                const tagsContainer = getByTestId(
                    'SuiteSelectInputMultiValueTagsContainer'
                );
                expect(tagsContainer).toHaveTextContent('ba');
            });
        });

        test('Remove a rejection reason', async () => {
            const { getByTestId, findByText, findAllByTestId } =
                renderComponent();
            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const option = await findByText('asdasd');
            fireEvent.click(option);

            const tagsContainer = getByTestId(
                'SuiteSelectInputMultiValueTagsContainer'
            );
            expect(tagsContainer).toHaveTextContent('asdasd');

            const badgeButtons = await findAllByTestId('SuiteBadgeCloseButton');
            fireEvent.click(badgeButtons[0]);

            await waitFor(() => {
                expect(getByTestId('SuiteSelectInput')).not.toHaveTextContent(
                    'asdasd'
                );
            });
        });

        test('Track click event - Select rejection reason', async () => {
            const segmentTrack = jest.spyOn(Segment, 'trackEvent');

            const timestamp = Date.now();
            jest.useFakeTimers().setSystemTime(timestamp);

            const { getByTestId, findByText } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            const eventLabel = 'Rejection Reasons - In Revision';
            expect(segmentTrack).toHaveBeenCalledWith(
                'Click',
                {
                    category: CR_CATEGORY,
                    value: timestamp,
                    label: eventLabel,
                    accountId: 123,
                    subaccountId: 333,
                    productId: 33,
                },
                eventLabel
            );
        });
    });

    describe('Preview rejection note', () => {
        beforeEach(() => mockQueries());
        afterEach(() => jest.clearAllMocks());

        test('Preview text disabled', () => {
            const { queryByTestId } = renderComponent();
            expect(
                queryByTestId('preview-notes-label')
            ).not.toBeInTheDocument();
        });

        test('Displays preview text if rejection reasons exist', async () => {
            const { getByTestId, findByText } = renderComponent();
            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            const textarea = getByTestId(
                'form-control-additional-rejection-notes'
            );
            fireEvent.change(textarea, {
                target: { value: 'hello world' },
            });

            await waitFor(() => {
                expect(getByTestId('preview-notes-label')).toBeInTheDocument();
            });

            const previewLabel = getByTestId('preview-notes-label');
            fireEvent.click(previewLabel);

            const previewBody = await screen.findByTestId('preview-notes-body');
            expect(previewBody).toBeInTheDocument();
        });

        test('Track click event - Preview Copy', async () => {
            const segmentTrack = jest.spyOn(Segment, 'trackEvent');
            const { getByTestId, findByText } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            await waitFor(() => {
                expect(getByTestId('preview-notes-label')).toBeInTheDocument();
            });

            // Fake timer after selecting a reason and waiting for preview label to appear
            const timestamp = Date.now();
            jest.useFakeTimers().setSystemTime(timestamp);

            fireEvent.click(getByTestId('preview-notes-label'));

            const eventLabel = 'Preview Copy - Rejection Note - In Revision';

            expect(segmentTrack).toHaveBeenNthCalledWith(
                2,
                'Click',
                {
                    category: CR_CATEGORY,
                    value: timestamp,
                    label: eventLabel,
                    accountId: 123,
                    subaccountId: 333,
                    productId: 33,
                },
                eventLabel
            );
        });
    });

    describe('Save rejection note', () => {
        beforeEach(() => {
            mockQueries();
            jest.spyOn(
                rejectProductReview,
                'useRejectProductMutation'
            ).mockImplementation(() => mockTriggerRejectProductMutation);
        });
        afterEach(() => jest.clearAllMocks());

        test('Reject button disabled', () => {
            const { getByRole } = renderComponent();
            const rejectButton = getByRole('button', { name: 'Reject' });
            expect(rejectButton).toBeDisabled();
        });

        test('Reject button is enabled if rejection reasons exist', async () => {
            const { getByRole, findByText, getByTestId } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            await waitFor(() => {
                expect(getByRole('button', { name: 'Reject' })).toBeEnabled();
            });
        });

        test('Reject button is enabled if canned response category is other and notes provided', async () => {
            const { getByRole, findAllByText, getByTestId } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOther = await findAllByText('Other');
            fireEvent.click(optionOther[1]);

            const textarea = getByTestId(
                'form-control-additional-rejection-notes'
            );
            fireEvent.change(textarea, { target: { value: 'hello world' } });

            const rejectButton = getByRole('button', { name: 'Reject' });
            expect(rejectButton).toBeEnabled();
        });

        test('Reject button is disabled if canned response is other and no notes provided', async () => {
            const { getByRole, findAllByText, getByTestId } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOther = await findAllByText('Other');
            fireEvent.click(optionOther[1]);

            const rejectButton = getByRole('button', { name: 'Reject' });
            expect(rejectButton).toBeDisabled();
        });

        test('Triggers mutation to save rejection note', async () => {
            const { getByRole, findByText, getByTestId } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            const rejectButton = getByRole('button', { name: 'Reject' });

            await waitFor(() => {
                expect(rejectButton).toBeEnabled();
            });

            fireEvent.click(rejectButton);

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

            expect(getByRole('button', { name: 'Reject' })).toBeDisabled();
        });
    });

    describe('User language alert', () => {
        test('Displays for non english language', () => {
            const { getByTestId } = renderComponent({
                submittedByUserLanguage: 'it',
            });
            const languageAlert = getByTestId('UserLanguageAlert');
            expect(languageAlert).toBeInTheDocument();
        });

        test('Does not display for english language', () => {
            const { queryByTestId } = renderComponent({
                submittedByUserLanguage: 'en',
            });
            const languageAlert = queryByTestId('UserLanguageAlert');
            expect(languageAlert).toBeNull();
        });
    });

    describe('Perma reject a product', () => {
        beforeEach(() => {
            jest.spyOn(features, 'usePermaRejectionFF').mockReturnValue(true);
        });

        test('displays option, shows alert, and triggers perma reject mutation', async () => {
            const { getByTestId, findByText } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const permaRejectionOption = await findByText('Will Not Deliver');
            fireEvent.click(permaRejectionOption);

            await waitFor(() => {
                expect(getByTestId('PermaRejectAlert')).toBeInTheDocument();
            });
        });

        test('replaces {{productTitle}} placeholder with product name in note', async () => {
            const { getByTestId, findByText } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const permaRejectionOption = await findByText('Will Not Deliver');
            fireEvent.click(permaRejectionOption);

            await waitFor(() => {
                expect(getByTestId('preview-notes-label')).toBeInTheDocument();
            });

            fireEvent.click(getByTestId('preview-notes-label'));

            const previewBody = getByTestId('preview-notes-body');
            expect(previewBody).toHaveTextContent('Wubba Lubba Dub Dub');
            expect(previewBody).not.toHaveTextContent('{{productTitle}}');
        });
    });

    describe('Skip closing statement list', () => {
        test('displays closing text when option outside of list is chosen', async () => {
            const { getByTestId, findByText } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('ba');
            fireEvent.click(optionOne);

            await waitFor(() => {
                expect(getByTestId('preview-notes-label')).toBeInTheDocument();
            });

            const previewLabel = getByTestId('preview-notes-label');
            fireEvent.click(previewLabel);

            const previewBody = getByTestId('preview-notes-body');
            expect(previewBody).toHaveTextContent('some closing text');
        });

        test('does not display closing text when option inside of list is chosen', async () => {
            const { getByTestId, findByText } = renderComponent();

            const reasonInput = getByTestId('SuiteSelectInputValue');
            fireEvent.click(reasonInput);

            const optionOne = await findByText('AI Generated Track Audio');
            fireEvent.click(optionOne);

            await waitFor(() => {
                expect(getByTestId('preview-notes-label')).toBeInTheDocument();
            });

            const previewLabel = getByTestId('preview-notes-label');
            fireEvent.click(previewLabel);

            const previewBody = getByTestId('preview-notes-body');
            expect(previewBody).not.toHaveTextContent('some closing text');
        });
    });

    describe('Form submission', () => {
        test('prevents default form submission when Enter is pressed', () => {
            const { getByTestId } = renderComponent();
            const form = getByTestId('ProductModalRejectionSidecar-Form');

            const submitEvent = new Event('submit', {
                bubbles: true,
                cancelable: true,
            });
            const preventDefaultSpy = jest.spyOn(submitEvent, 'preventDefault');

            form.dispatchEvent(submitEvent);

            expect(preventDefaultSpy).toHaveBeenCalled();
        });
    });
});
