﻿import React from 'react';
import * as apollo from '@apollo/client';
import { fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderInAppContext } from '@theorchard/suite-testing';
import {
    getMergedProduct,
    getProductMetadata,
    getProductMetadataWithMissingTrackAudioAttributes,
    getProductMetadataWithMissingTrackRightsAttributes,
} from 'lib/mockProductData';
import {
    BLOCKLIST_CATEGORY_ID,
    N_A_AUDIO_ATTRIBUTE_ID,
    N_A_RIGHTS_ATTRIBUTE_ID,
} from 'src/constants';
import * as approveProductReview from 'src/data/mutations/approve/approveProductReview';
import * as upsertTracksAudioAttributes from 'src/data/mutations/upsertTracksAudioAttributes/upsertTracksAudioAttributes';
import * as upsertTracksAudioAttributesEdits from 'src/data/mutations/upsertTracksAudioAttributesEdits/upsertTracksAudioAttributesEdits';
import * as upsertTracksRightsAttributes from 'src/data/mutations/upsertTracksRightsAttributes/upsertTracksRightsAttributes';
import * as upsertTracksRightsAttributesEdits from 'src/data/mutations/upsertTracksRightsAttributesEdits/upsertTracksRightsAttributesEdits';
import * as cannedResponsesNoteQuery from 'src/data/queries/cannedResponseNote/cannedResponseNote';
import * as cannedResponsesQuery from 'src/data/queries/cannedResponses/cannedResponses';
import { CannedResponseNoteResult } from 'src/types';
import * as features from 'src/utils/features';
import ProductModalApprovalSidecar, {
    Props,
} from '../productModalApprovalSidecar';

const upsertAudioAttributesEditsMock = jest.fn();
const upsertRightsAttributesEditsMock = jest.fn();

describe('<ProductModalApprovalSidecar>', () => {
    let defaultProps: Props;

    beforeEach(() => {
        // it should be within beforeEach, in order to have newly generated 'fresh' data for each test
        defaultProps = {
            product: getProductMetadata(),
            mergedProduct: getMergedProduct(),
            isApprovalNoteRequired: false,
            isOpen: true,
            closeModal: jest.fn(),
            productReviewQueueId: 1,
            additionalNotes: '',
            onReviewCompleted: jest.fn(),
            onAdditionalNotesChange: jest.fn(),
            approvalNote: '',
            handleSetApprovalNote: jest.fn(),
            approvalReason: '',
            handleSetApprovalReasons: jest.fn(),
            trackApprovalReasons: {},
            handleSetTrackApprovalReasons: jest.fn(),
            handleTrackApprovalChange: jest.fn(),
        };

        jest.spyOn(
            cannedResponsesQuery,
            'useCannedResponsesQuery'
        ).mockImplementation(() => ({
            loading: false,
            error: undefined,
            data: [
                {
                    cannedResponseCategory: { id: '1', name: 'foo' },
                    cannedResponseId: '1',
                    keyword: 'ab',
                },
                {
                    cannedResponseCategory: { id: '2', name: 'foo' },
                    cannedResponseId: '2',
                    keyword: 'cd',
                },
                {
                    cannedResponseCategory: { id: '69', name: 'foo' },
                    cannedResponseId: '69',
                    keyword: 'ef',
                },
            ],
        }));
        jest.spyOn(
            cannedResponsesNoteQuery,
            'useCannedResponseNoteQuery'
        ).mockReturnValue([jest.fn()] as unknown as CannedResponseNoteResult);

        jest.spyOn(
            upsertTracksAudioAttributes,
            'useUpsertTracksAudioAttributesMutation'
        ).mockReturnValue([
            jest.fn().mockResolvedValue('ok'),
            {} as apollo.MutationResult,
        ]);
        jest.spyOn(
            upsertTracksRightsAttributes,
            'useUpsertTracksRightsAttributesMutation'
        ).mockReturnValue([
            jest.fn().mockResolvedValue('ok'),
            {} as apollo.MutationResult,
        ]);
        jest.spyOn(
            upsertTracksAudioAttributesEdits,
            'useUpsertTracksAudioAttributesEditsMutation'
        ).mockReturnValue([
            upsertAudioAttributesEditsMock,
            {} as apollo.MutationResult,
        ]);
        jest.spyOn(
            upsertTracksRightsAttributesEdits,
            'useUpsertTracksRightsAttributesEditsMutation'
        ).mockReturnValue([
            upsertRightsAttributesEditsMock,
            {} as apollo.MutationResult,
        ]);
    });

    afterEach(() => jest.resetAllMocks());

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

    test('renders with no errors', () => {
        const { getByTestId, queryByTestId } = renderComponent();
        expect(getByTestId('ProductModalApprovalSidecar')).toBeInTheDocument();
        expect(
            getByTestId('ProductModalApprovalSidecar-Form')
        ).toBeInTheDocument();
        expect(queryByTestId('form-notice')).not.toBeInTheDocument();
        expect(
            queryByTestId('form-group-approval-reason')
        ).not.toBeInTheDocument();
        expect(
            getByTestId('form-group-additional-notes-approval')
        ).toBeInTheDocument();
    });

    test('renders approval reason dropdown', async () => {
        const { getByTestId, findByText } = renderComponent({
            isApprovalNoteRequired: true,
        });

        const selectComponent = getByTestId('approval-reason-select');
        const selectComponentAction = selectComponent.querySelector(
            'button.SuiteListView-action-button'
        );
        if (selectComponentAction) fireEvent.click(selectComponentAction);

        const optionOne = await findByText('ab');
        const optionTwo = await findByText('cd');

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

    test('approval note required', async () => {
        const { getByTestId, getByRole } = renderComponent({
            isApprovalNoteRequired: true,
        });
        expect(getByTestId('form-notice')).toBeVisible();

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

    test('on change additional notes', () => {
        const onAdditionalNotesChangeSpy = jest.spyOn(
            defaultProps,
            'onAdditionalNotesChange'
        );
        const { getByTestId, getByRole } = renderComponent();
        const saveButton = getByRole('button', { name: 'Approve' });
        expect(saveButton).toBeEnabled();
        const textarea = getByTestId('form-control-additional-notes-approval');
        if (textarea)
            fireEvent.change(textarea, { target: { value: 'hello world' } });
        expect(onAdditionalNotesChangeSpy).toHaveBeenCalledWith('hello world');
    });

    test('additional notes required', async () => {
        const { getByTestId, findByText, getByRole } = renderComponent({
            isApprovalNoteRequired: true,
        });
        const saveButton = getByRole('button', { name: 'Approve' });
        expect(saveButton).toBeDisabled();

        const selectComponent = getByTestId('approval-reason-select');
        const selectComponentAction = selectComponent.querySelector(
            'button.SuiteListView-action-button'
        );
        if (selectComponentAction) fireEvent.click(selectComponentAction);
        const option = await findByText('ef');
        fireEvent.click(option);
        expect(saveButton).toBeDisabled();
    });

    test('save approval', async () => {
        const mockApproveProductMutation = jest
            .spyOn(approveProductReview, 'useApproveProductMutation')
            .mockImplementation(() => jest.fn());

        const { getByRole } = renderComponent({
            approvalNote: 'abc note',
        });

        const saveButton = getByRole('button', { name: 'Approve' });
        expect(saveButton).toBeEnabled();
        fireEvent.click(saveButton);

        const calls = (mockApproveProductMutation as jest.Mock).mock.calls;
        expect(calls.length).toBeGreaterThan(0);
        expect(calls[calls.length - 1]).toEqual([
            1,
            'abc note',
            '',
            undefined,
            [],
            defaultProps.onReviewCompleted,
        ]);

        await waitFor(() => {
            expect(upsertRightsAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            rightsAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });
        expect(saveButton).toBeDisabled();
    });

    test('when no missing audio or rights attributes are found', async () => {
        jest.spyOn(features, 'useDisplayAudioAttributeModalFF').mockReturnValue(
            true
        );

        const mockApproveProductMutation = jest
            .spyOn(approveProductReview, 'useApproveProductMutation')
            .mockImplementation(() => jest.fn());

        const expectedAudioInput = [
            {
                tuid: '123456',
                audioAttributeIds: ['1'],
            },
        ];
        const expectedRightsInput = [
            {
                tuid: '123456',
                rightsAttributeIds: ['1'],
            },
        ];
        const { getByRole } = renderComponent();
        const saveButton = getByRole('button', { name: 'Approve' });
        fireEvent.click(saveButton);

        await waitFor(() => {
            expect(
                upsertTracksAudioAttributes.useUpsertTracksAudioAttributesMutation()[0]
            ).toHaveBeenCalledWith({
                variables: {
                    input: expectedAudioInput,
                },
            });
        });

        await waitFor(() => {
            expect(
                upsertTracksRightsAttributes.useUpsertTracksRightsAttributesMutation()[0]
            ).toHaveBeenCalledWith({
                variables: {
                    input: expectedRightsInput,
                },
            });
        });

        await waitFor(() => {
            expect(upsertAudioAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            audioAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });
        await waitFor(() => {
            expect(upsertRightsAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            rightsAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });
        expect(mockApproveProductMutation).toHaveBeenCalled();
    });

    test('when track has missing audio attributes', async () => {
        jest.spyOn(features, 'useDisplayAudioAttributeModalFF').mockReturnValue(
            true
        );

        const mockApproveProductMutation = jest
            .spyOn(approveProductReview, 'useApproveProductMutation')
            .mockImplementation(() => jest.fn());

        const { getByTestId, getByRole, getAllByText } = renderComponent({
            product: getProductMetadataWithMissingTrackAudioAttributes(),
            mergedProduct: undefined,
        });
        const saveButton = getByRole('button', { name: 'Approve' });
        const expectedAudioInput = [
            {
                tuid: '123456',
                audioAttributeIds: [N_A_AUDIO_ATTRIBUTE_ID],
            },
        ];
        const expectedRightsInput = [
            {
                tuid: '123456',
                rightsAttributeIds: ['1'],
            },
        ];

        expect(saveButton).toBeEnabled();
        fireEvent.click(saveButton);
        const missingAudioAttributeModal = getByTestId(
            'missing-audio-attributes-modal'
        );
        const missingAudioAttributeModalApproveButtons =
            getAllByText('Approve');
        expect(missingAudioAttributeModal).toBeVisible();
        fireEvent.click(missingAudioAttributeModalApproveButtons[1]);

        await waitFor(() => {
            expect(
                upsertTracksAudioAttributes.useUpsertTracksAudioAttributesMutation()[0]
            ).toHaveBeenCalledWith({
                variables: {
                    input: expectedAudioInput,
                },
            });
        });
        await waitFor(() => {
            expect(
                upsertTracksRightsAttributes.useUpsertTracksRightsAttributesMutation()[0]
            ).toHaveBeenCalledWith({
                variables: {
                    input: expectedRightsInput,
                },
            });
        });

        await waitFor(() => {
            expect(upsertAudioAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            audioAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });

        await waitFor(() => {
            expect(upsertRightsAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            rightsAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });

        await waitFor(() => {
            expect(upsertAudioAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            audioAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });

        await waitFor(() => {
            expect(upsertRightsAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            rightsAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });

        expect(mockApproveProductMutation).toHaveBeenCalled();
    });

    test('when track has missing rights attributes', async () => {
        jest.spyOn(features, 'useDisplayAudioAttributeModalFF').mockReturnValue(
            true
        );

        const mockApproveProductMutation = jest
            .spyOn(approveProductReview, 'useApproveProductMutation')
            .mockImplementation(() => jest.fn());

        const { getByTestId, getByRole, getAllByText } = renderComponent({
            product: getProductMetadataWithMissingTrackRightsAttributes(),
            mergedProduct: undefined,
        });
        const saveButton = getByRole('button', { name: 'Approve' });
        const expectedAudioInput = [
            {
                tuid: '123456',
                audioAttributeIds: ['1'],
            },
        ];
        const expectedRightsInput = [
            {
                tuid: '123456',
                rightsAttributeIds: [N_A_RIGHTS_ATTRIBUTE_ID],
            },
        ];

        expect(saveButton).toBeEnabled();
        fireEvent.click(saveButton);
        const missingAudioAttributeModal = getByTestId(
            'missing-audio-attributes-modal'
        );
        const missingAudioAttributeModalApproveButtons =
            getAllByText('Approve');
        expect(missingAudioAttributeModal).toBeVisible();
        fireEvent.click(missingAudioAttributeModalApproveButtons[1]);

        await waitFor(() => {
            expect(
                upsertTracksAudioAttributes.useUpsertTracksAudioAttributesMutation()[0]
            ).toHaveBeenCalledWith({
                variables: {
                    input: expectedAudioInput,
                },
            });
        });
        await waitFor(() => {
            expect(
                upsertTracksRightsAttributes.useUpsertTracksRightsAttributesMutation()[0]
            ).toHaveBeenCalledWith({
                variables: {
                    input: expectedRightsInput,
                },
            });
        });

        await waitFor(() => {
            expect(upsertAudioAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            audioAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });

        await waitFor(() => {
            expect(upsertRightsAttributesEditsMock).toHaveBeenCalledWith({
                variables: {
                    input: [
                        {
                            rightsAttributeIds: [],
                            tuid: '123456',
                        },
                    ],
                },
            });
        });

        expect(mockApproveProductMutation).toHaveBeenCalled();
    });

    describe('track-level approval with potential audio infringement', () => {
        describe('Blocklist approval reasons are not present in "reason selectors"', () => {
            const mockCannedResponses = [
                {
                    cannedResponseCategory: { id: '1', name: 'foo' },
                    cannedResponseId: '1',
                    keyword: 'bar',
                },
                {
                    cannedResponseCategory: {
                        id: BLOCKLIST_CATEGORY_ID,
                        name: 'foo',
                    },
                    cannedResponseId: '128',
                    keyword: 'blocklist',
                },
            ];

            test('filters blocklist approval reasons from the main approval reason selector', async () => {
                (
                    cannedResponsesQuery.useCannedResponsesQuery as jest.Mock
                ).mockReturnValue({
                    loading: false,
                    error: undefined,
                    data: mockCannedResponses,
                });

                const { getByTestId, findByText, queryByText } =
                    renderComponent({
                        isApprovalNoteRequired: true,
                    });

                const selectComponent = getByTestId('approval-reason-select');
                const selectComponentAction = selectComponent.querySelector(
                    'button.SuiteListView-action-button'
                );
                if (selectComponentAction)
                    fireEvent.click(selectComponentAction);

                expect(await findByText('bar')).toBeVisible();
                expect(queryByText('blocklist')).not.toBeInTheDocument();
            });

            test('filters blocklist approval reasons from the track approval reason selector', async () => {
                (
                    cannedResponsesQuery.useCannedResponsesQuery as jest.Mock
                ).mockReturnValue({
                    loading: false,
                    error: undefined,
                    data: mockCannedResponses,
                });

                const originalMergedProduct = getMergedProduct();
                const customMergedProduct = Object.assign(
                    Object.create(Object.getPrototypeOf(originalMergedProduct)),
                    {
                        ...originalMergedProduct,
                        tracks: {
                            ...originalMergedProduct.tracks,
                            '123456': {
                                ...originalMergedProduct.tracks['123456'],
                                validations: [
                                    {
                                        code: 'POTENTIAL_AUDIO_INFRINGEMENT',
                                        reason: 'some reason',
                                        severity: 'warn',
                                        type: 'track',
                                        target: '123456-field:0',
                                        targetId: 123456,
                                    },
                                ],
                            },
                        },
                    }
                );

                const { getByTestId, findByText, queryByText } =
                    renderComponent({
                        mergedProduct: customMergedProduct,
                        isApprovalNoteRequired: true,
                        trackApprovalReasons: { '123456': '1' },
                    });

                fireEvent.click(await findByText('See Tracks Individually'));

                const trackReasonSelect = getByTestId(
                    'approval-reason-select-123456'
                );
                const trackReasonSelectAction = trackReasonSelect.querySelector(
                    'button.SuiteListView-action-button'
                );
                if (trackReasonSelectAction)
                    fireEvent.click(trackReasonSelectAction);

                expect(await findByText('bar')).toBeVisible();
                expect(queryByText('blocked')).not.toBeInTheDocument();
            });
        });

        test('renders approval dropdown for tracks with POTENTIAL_AUDIO_INFRINGEMENT', async () => {
            Element.prototype.scrollTo = jest.fn();

            const originalMergedProduct = getMergedProduct();
            const customMergedProduct = Object.assign(
                Object.create(Object.getPrototypeOf(originalMergedProduct)),
                {
                    ...originalMergedProduct,
                    tracks: {
                        ...originalMergedProduct.tracks,
                        '123456': {
                            ...originalMergedProduct.tracks['123456'],
                            validations: [
                                {
                                    code: 'POTENTIAL_AUDIO_INFRINGEMENT',
                                    reason: 'some reason',
                                    severity: 'warn',
                                    type: 'track',
                                    target: '123456-field:0',
                                    targetId: 123456,
                                },
                            ],
                        },
                    },
                }
            );

            const { getByTestId, findByText } = renderComponent({
                mergedProduct: customMergedProduct,
                isApprovalNoteRequired: true,
            });
            expect(
                getByTestId('ProductModalApprovalSidecar')
            ).toBeInTheDocument();

            //Test audio infringement notice
            const audioInfringementNotice = await findByText(
                'This product has one or more Potential Audio Infringement notices'
            );
            expect(audioInfringementNotice).toBeVisible();
            const approvalInstruction = await findByText(
                'Using the dropdown below, please select the approval reason explaining why this product is being approved, despite matching a song that has already been released commercially.'
            );
            expect(approvalInstruction).toBeVisible();

            // Test the main approval reason dropdown
            const selectComponent = getByTestId('approval-reason-select');
            const selectComponentAction = selectComponent.querySelector(
                'button.SuiteListView-action-button'
            );
            if (selectComponentAction)
                await userEvent.click(selectComponentAction);

            const optionOne = await findByText('ab');
            const optionTwo = await findByText('cd');

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

            await userEvent.click(optionOne);
            expect(defaultProps.handleSetApprovalReasons).toHaveBeenCalledWith(
                '1'
            );
        });

        test('selecting "See Tracks Individually" shows track-level dropdown', async () => {
            const mockApproveProductMutation = jest
                .spyOn(approveProductReview, 'useApproveProductMutation')
                .mockImplementation(() => jest.fn());
            Element.prototype.scrollTo = jest.fn();

            const originalMergedProduct = getMergedProduct();
            const customMergedProduct = Object.assign(
                Object.create(Object.getPrototypeOf(originalMergedProduct)),
                {
                    ...originalMergedProduct,
                    tracks: {
                        ...originalMergedProduct.tracks,
                        '123456': {
                            ...originalMergedProduct.tracks['123456'],
                            validations: [
                                {
                                    code: 'POTENTIAL_AUDIO_INFRINGEMENT',
                                    reason: 'some reason',
                                    severity: 'warn',
                                    type: 'track',
                                    target: '123456-field:0',
                                    targetId: 123456,
                                },
                            ],
                        },
                    },
                }
            );

            const { getByTestId, findByText, findAllByText, getByRole } =
                renderComponent({
                    mergedProduct: customMergedProduct,
                    isApprovalNoteRequired: true,
                    approvalReason: '1',
                    trackApprovalReasons: { '123456': '1' },
                });
            expect(
                getByTestId('ProductModalApprovalSidecar')
            ).toBeInTheDocument();

            const seeTracksIndividuallyLabel = await findByText(
                'See Tracks Individually'
            );
            fireEvent.click(seeTracksIndividuallyLabel);

            const trackApprovalReason = getByTestId(
                'approval-reason-select-123456'
            );
            expect(trackApprovalReason).toBeVisible();
            const trackApprovalReasonAction = trackApprovalReason.querySelector(
                'SuiteListView-expand-button'
            );
            if (trackApprovalReasonAction)
                fireEvent.click(trackApprovalReasonAction);
            const trackApproveoptions = await findAllByText('ab');
            userEvent.click(trackApproveoptions[1]);

            const saveButton = getByRole('button', { name: 'Approve' });
            expect(saveButton).toBeEnabled();
            fireEvent.click(saveButton);
            expect(mockApproveProductMutation).toHaveBeenCalledWith(
                1,
                '',
                '1',
                undefined,
                [{ cannedResponseId: 1, trackId: 123456 }],
                defaultProps.onReviewCompleted
            );
        });

        test('renders approval dropdown for tracks with CROSS_ACCOUNT_OSR_CONFLICT', async () => {
            Element.prototype.scrollTo = jest.fn();

            const originalMergedProduct = getMergedProduct();
            const customMergedProduct = Object.assign(
                Object.create(Object.getPrototypeOf(originalMergedProduct)),
                {
                    ...originalMergedProduct,
                    tracks: {
                        ...originalMergedProduct.tracks,
                        '123456': {
                            ...originalMergedProduct.tracks['123456'],
                            validations: [
                                {
                                    code: 'CROSS_ACCOUNT_OSR_CONFLICT',
                                    reason: 'some reason',
                                    severity: 'warn',
                                    type: 'track',
                                    target: '123456-field:0',
                                    targetId: 123456,
                                },
                            ],
                        },
                    },
                }
            );

            const { getByTestId, findByText } = renderComponent({
                mergedProduct: customMergedProduct,
                isApprovalNoteRequired: true,
            });
            expect(
                getByTestId('ProductModalApprovalSidecar')
            ).toBeInTheDocument();

            //Test audio infringement notice
            const audioInfringementNotice = await findByText(
                'This product has one or more Potential Audio Infringement notices'
            );
            expect(audioInfringementNotice).toBeVisible();
            const approvalInstruction = await findByText(
                'Using the dropdown below, please select the approval reason explaining why this product is being approved, despite matching a song that has already been released commercially.'
            );
            expect(approvalInstruction).toBeVisible();

            // Test the main approval reason dropdown
            const selectComponent = getByTestId('approval-reason-select');
            const selectComponentAction = selectComponent.querySelector(
                'button.SuiteListView-action-button'
            );
            if (selectComponentAction) fireEvent.click(selectComponentAction);

            const optionOne = await findByText('ab');
            const optionTwo = await findByText('cd');

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