import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderInAppContext } from '@theorchard/suite-testing';
import { Route } from 'react-router-dom';
import {
    contractTerm,
    createContractTermResponse,
    deleteContractTermResponse,
    productContractTerm,
    trackContractTerm,
} from 'src/__fixtures__/graphql/contract-term';
import * as storeList from 'src/__fixtures__/graphql/store-list-response.json';
import {
    transactionTypes,
    transactionTypeGroups,
} from 'src/__fixtures__/graphql/transaction-types';
import * as contractTermMutation from 'src/apollo/mutations/contract-term';
import * as contractTermQuery from 'src/apollo/queries/contract-term';
import * as storesQuery from 'src/apollo/queries/stores';
import * as transactionTypeQueries from 'src/apollo/queries/transaction-types';
import ContractTermsDistro from 'src/components/contract-terms-distro/contract-terms-distro';
import { USER_FEATURES } from 'src/constants';
import { createNewTerms, updateTerms } from 'src/urls/frontend-royalties';
import * as featureFlag from 'src/utils/feature-flag';
import * as contractTermValidation from 'src/utils/form-validations/contract-term-form-validation';
import type { GetStoresQuery } from 'src/apollo/queries/__generated__/stores';

describe('<ContractTermsDistro>', () => {
    const createContractTerm = jest.fn().mockResolvedValue({
        data: createContractTermResponse,
        error: undefined,
        loading: false,
    });

    const updateContractTerm = jest.fn().mockResolvedValue({
        data: createContractTermResponse,
        error: undefined,
        loading: false,
    });

    const deleteContractTerm = jest.fn().mockResolvedValue({
        data: deleteContractTermResponse,
        error: undefined,
        loading: false,
    });

    const render = () =>
        renderInAppContext(
            <Route path={`/contract/:contractId/terms/:termType/new`}>
                <ContractTermsDistro />
            </Route>,
            {
                pathname: createNewTerms(123, 'label'),
            }
        );

    beforeEach(() => {
        jest.spyOn(
            contractTermMutation,
            'useCreateContractTerm'
        ).mockReturnValue(createContractTerm);

        jest.spyOn(
            contractTermMutation,
            'useUpdateContractTerm'
        ).mockReturnValue(updateContractTerm);

        jest.spyOn(
            contractTermMutation,
            'useSoftDeleteContractTerm'
        ).mockReturnValue(deleteContractTerm);

        jest.spyOn(
            transactionTypeQueries,
            'useTransactionTypes'
        ).mockReturnValue({ data: transactionTypes, loading: false });

        jest.spyOn(
            transactionTypeQueries,
            'useTransactionTypeGroups'
        ).mockReturnValue({ data: transactionTypeGroups, loading: false });

        jest.spyOn(storesQuery, 'useStoreList').mockReturnValue({
            data: storeList as GetStoresQuery,
            loading: false,
        });
    });

    afterEach(jest.restoreAllMocks);

    it('renders save and cancel buttons', () => {
        render();
        const saveButton = screen.getByRole('button', { name: 'Save' });
        expect(saveButton).toBeDefined();
        const cancelButton = screen.getByRole('button', { name: 'Cancel' });
        expect(cancelButton).toBeDefined();
    });

    it('disable "Save" button when form is not completed', () => {
        const formValidationSpy = jest
            .spyOn(contractTermValidation, 'contractTermFormValidation')
            .mockReturnValue(['Say please']);

        render();

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

        expect(formValidationSpy).toHaveBeenCalled();
        expect(saveButton).toHaveAttribute('disabled');
    });

    it('displays validation error when present', () => {
        const formValidationSpy = jest
            .spyOn(contractTermValidation, 'contractTermFormValidation')
            .mockImplementation((data, validateName, validatePresence) => {
                if (validatePresence) return [];
                return ['Say please'];
            });

        render();

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

        expect(formValidationSpy).toHaveBeenCalled();
        expect(screen.getByText('Say please')).toBeDefined();
    });

    describe('"Add" term page', () => {
        it('displays "Add" text when rendered as add page', () => {
            render();
            expect(
                screen.getByText('Add Label Term', {
                    ignore: '.breadcrumb-item',
                })
            ).toBeDefined();
        });
        it('does not render a delete term button on the add page', () => {
            render();
            const deleteButton = screen.queryByTestId('deleteTermButton');
            expect(deleteButton).toBeNull();
        });

        it('saves new contract term and contract term conditions', async () => {
            jest.spyOn(
                contractTermValidation,
                'contractTermFormValidation'
            ).mockReturnValue([]);
            render();
            const termNameInput = screen.getByTestId('termName');
            fireEvent.change(termNameInput, { target: { value: 'New Term' } });
            const saveButton = screen.getByRole('button', { name: 'Save' });
            fireEvent.click(saveButton);
            await waitFor(() => {
                expect(createContractTerm).toHaveBeenCalled();
            });
        });
    });

    describe('"Edit" label term page', () => {
        const renderEdit = () =>
            renderInAppContext(
                <Route
                    path={`/contract/:contractId/terms/:termType/:contractTermId/edit`}
                >
                    <ContractTermsDistro />
                </Route>,
                {
                    pathname: updateTerms(123, 1, 'label'),
                }
            );
        beforeEach(() => {
            jest.spyOn(contractTermQuery, 'useContractTerm').mockReturnValue({
                data: contractTerm,
                error: undefined,
                loading: false,
            });
        });

        it('displays term name when rendered as edit page', () => {
            renderEdit();
            const termName = screen.getByTestId('termName');
            expect(termName).toBeDefined();
            expect(termName).toHaveValue('Test Contract Term');
        });

        it('displays  label rendered as edit page', () => {
            renderEdit();
            const labelTermLabel = screen.getByText('Edit Label Term', {
                ignore: '.breadcrumb-item',
            });
            expect(labelTermLabel).toBeDefined();
        });

        it('displays "Edit" text and ID when rendered as edit page', () => {
            renderEdit();
            expect(
                screen.getByText('Edit Label Term', {
                    ignore: '.breadcrumb-item',
                })
            ).toBeDefined();
            expect(screen.getByTestId('termId')).toHaveTextContent('ID: 1');
        });

        it('renders a delete term button on the edit page', () => {
            renderEdit();
            const deleteButton = screen.getByTestId('deleteTermButton');
            expect(deleteButton).toBeDefined();
            expect(deleteButton).toHaveTextContent('Delete Term');
        });

        it('updates the contract term when clicking on the save button and generates the appropriate toast', async () => {
            renderEdit();
            const termNameInput = screen.getByTestId('termName');
            expect(termNameInput).toHaveValue('Test Contract Term');
            fireEvent.change(termNameInput, {
                target: { value: 'New Term 123' },
            });
            expect(termNameInput).toHaveValue('New Term 123');
            const saveButton = screen.getByRole('button', { name: 'Save' });
            await userEvent.setup().click(saveButton);
            expect(updateContractTerm).toHaveBeenCalled();
            const toast = await screen.findByTestId('Toast-0');
            expect(toast).toHaveTextContent(
                'Label Term has been successfully updated.'
            );
        });
    });

    describe('"Edit" product term page', () => {
        const renderEdit = () =>
            renderInAppContext(
                <Route
                    path={`/contract/:contractId/terms/:termType/:contractTermId/edit`}
                >
                    <ContractTermsDistro />
                </Route>,
                {
                    pathname: updateTerms(123, 2, 'product'),
                }
            );
        beforeEach(() => {
            jest.spyOn(contractTermQuery, 'useContractTerm').mockReturnValue({
                data: productContractTerm,
                error: undefined,
                loading: false,
            });
        });

        it('displays term name when rendered as edit page', () => {
            renderEdit();
            const termName = screen.getByTestId('termName');
            expect(termName).toBeDefined();
            expect(termName).toHaveValue('Test Contract Term');
        });

        it('displays product label rendered as edit page', () => {
            renderEdit();
            const productTermLabel = screen.getByText('Edit Product Term', {
                ignore: '.breadcrumb-item',
            });
            expect(productTermLabel).toBeDefined();
        });

        it('displays "Edit" text and ID when rendered as edit page', () => {
            renderEdit();
            expect(
                screen.getByText('Edit Product Term', {
                    ignore: '.breadcrumb-item',
                })
            ).toBeDefined();
            expect(screen.getByTestId('termId')).toHaveTextContent('ID: 2');
        });

        it('renders a delete term button on the edit page', () => {
            renderEdit();
            const deleteButton = screen.getByTestId('deleteTermButton');
            expect(deleteButton).toBeDefined();
            expect(deleteButton).toHaveTextContent('Delete Term');
        });
    });

    describe('"Edit" track term page', () => {
        const renderEdit = () =>
            renderInAppContext(
                <Route
                    path={`/contract/:contractId/terms/:termType/:contractTermId/edit`}
                >
                    <ContractTermsDistro />
                </Route>,
                {
                    pathname: updateTerms(123, 3, 'track'),
                }
            );
        beforeEach(() => {
            jest.spyOn(contractTermQuery, 'useContractTerm').mockReturnValue({
                data: trackContractTerm,
                error: undefined,
                loading: false,
            });
        });

        it('displays term name when rendered as edit page', () => {
            renderEdit();
            const termName = screen.getByTestId('termName');
            expect(termName).toBeDefined();
            expect(termName).toHaveValue('Test Contract Term');
        });

        it('displays track label rendered as edit page', () => {
            renderEdit();
            const trackTermLabel = screen.getByText('Edit Track Term', {
                ignore: '.breadcrumb-item',
            });
            expect(trackTermLabel).toBeDefined();
        });

        it('displays "Edit" text and ID when rendered as edit page', () => {
            renderEdit();
            expect(
                screen.getByText('Edit Track Term', {
                    ignore: '.breadcrumb-item',
                })
            ).toBeDefined();
            expect(screen.getByTestId('termId')).toHaveTextContent('ID: 3');
        });

        it('renders a delete term button on the edit page', () => {
            renderEdit();
            const deleteButton = screen.getByTestId('deleteTermButton');
            expect(deleteButton).toBeDefined();
            expect(deleteButton).toHaveTextContent('Delete Term');
        });

        it('updates the contract term when clicking on the save button and generates the appropriate toast', async () => {
            renderEdit();
            const termNameInput = screen.getByTestId('termName');
            expect(termNameInput).toHaveValue('Test Contract Term');
            fireEvent.change(termNameInput, {
                target: { value: 'New Term 123' },
            });
            expect(termNameInput).toHaveValue('New Term 123');
            const saveButton = screen.getByRole('button', { name: 'Save' });
            await userEvent.setup().click(saveButton);
            expect(updateContractTerm).toHaveBeenCalled();
            const toast = await screen.findByTestId('Toast-0');
            expect(toast).toHaveTextContent(
                'Track Term has been successfully updated.'
            );
        });

        describe('Edit term page delete modal', () => {
            it('renders a modal for deleting the term once the delete button is clicked', async () => {
                renderEdit();
                const deleteButton = screen.getByTestId('deleteTermButton');
                fireEvent.click(deleteButton);
                const deleteModal = screen.getByTestId('deleteModal');
                expect(deleteModal).toBeDefined();
            });

            it('renders the expected text for the delete modal', async () => {
                renderEdit();
                const deleteButton = screen.getByTestId('deleteTermButton');
                fireEvent.click(deleteButton);
                const deleteModal = screen.getByTestId('deleteModal');
                expect(deleteModal).toHaveTextContent('Delete Term');
                expect(deleteModal).toHaveTextContent(
                    'Are you sure you want to delete this term?'
                );
                expect(deleteModal).toHaveTextContent(
                    'This action cannot be undone.'
                );
            });
            it('deletes the contract term', async () => {
                renderEdit();
                const deleteButton = screen.getByTestId('deleteTermButton');
                fireEvent.click(deleteButton);
                const confirmButton = screen.getByRole('button', {
                    name: 'Confirm',
                });
                await userEvent.setup().click(confirmButton);
                expect(deleteContractTerm).toHaveBeenCalledWith({
                    variables: { contractTermId: '3' },
                });
                const toast = screen.getByTestId('Toast-0');
                expect(toast).toHaveTextContent(
                    'The term has been successfully deleted.'
                );
            });
        });
    });

    describe('feature flag ABACUS_CREATE_TERMS_WITH_NO_TRACKS_AND_PRODUCTS', () => {
        it('renders only ContractTermsDistroFormFields when that specific flag is on', () => {
            jest.spyOn(featureFlag, 'isFeatureFlagEnabled').mockImplementation(
                flag =>
                    flag ===
                    USER_FEATURES.ABACUS_CREATE_TERMS_WITH_NO_TRACKS_AND_PRODUCTS
            );
            renderInAppContext(
                <Route
                    path={`/contract/:contractId/terms/:termType/:contractTermId/edit`}
                >
                    <ContractTermsDistro />
                </Route>,
                {
                    pathname: updateTerms(123, 1, 'track'),
                }
            );
            expect(
                screen.getByTestId('contract-term-name-field')
            ).toBeInTheDocument();
            expect(
                screen.getByTestId('contractFormFieldsCancelButton')
            ).toBeDefined();
            expect(
                screen.getByTestId('contractFormFieldsSaveButton')
            ).toBeDefined();
        });

        it('does not render ContractTermsDistroFormFields when that specific flag is off', () => {
            jest.spyOn(featureFlag, 'isFeatureFlagEnabled').mockImplementation(
                flag =>
                    flag !==
                    USER_FEATURES.ABACUS_CREATE_TERMS_WITH_NO_TRACKS_AND_PRODUCTS
            );
            render();
            expect(
                screen.queryByTestId('contract-term-name-field')
            ).not.toBeInTheDocument();
            expect(
                screen.queryByTestId('contractFormFieldsCancelButton')
            ).toBeNull();
            expect(
                screen.queryByTestId('contractFormFieldsSaveButton')
            ).toBeNull();
        });

        it('does not render ContractTermsDistroFormFields when the term type is label and flag is on', () => {
            jest.spyOn(featureFlag, 'isFeatureFlagEnabled').mockImplementation(
                flag =>
                    flag ===
                    USER_FEATURES.ABACUS_CREATE_TERMS_WITH_NO_TRACKS_AND_PRODUCTS
            );
            renderInAppContext(
                <Route
                    path={`/contract/:contractId/terms/:termType/:contractTermId/edit`}
                >
                    <ContractTermsDistro />
                </Route>,
                {
                    pathname: updateTerms(123, 1, 'label'),
                }
            );
            expect(
                screen.queryByTestId('contract-term-name-field')
            ).not.toBeInTheDocument();
        });
    });
});
