import React from 'react';
import { render, screen, waitFor, act, fireEvent } from '@testing-library/react';
import MainContainer from './MainContainer';
import { ResourceBrowserPlugin, ResourceBrowserSourceWithPlugin, PluginLaunchModeType } from '../types';

import { mockSource, mockResource, mockSourceWithPlugin } from '../__mocks__/MockModels';

import SourceList from '../SourceList/SourceList'; // Import Functional Component
jest.mock('../SourceList/SourceList'); // Mock the Functional Component
const ActualSourceList = jest.requireActual('../SourceList/SourceList').default; // Grab the real copy of it as normally we don't want the mock
const MockSourceList = SourceList as jest.MockedFunction<typeof SourceList>; // Cast the mocked function so TS stops complaining
MockSourceList.mockImplementation(ActualSourceList); // Return the actual function unless overridden

import ResourceLauncher from '../ResourceLauncher/ResourceLauncher'; // Import Functional Component
jest.mock('../ResourceLauncher/ResourceLauncher'); // Mock the Functional Component
const ActualResourceLauncher = jest.requireActual('../ResourceLauncher/ResourceLauncher').default; // Grab the real copy of it as normally we don't want the mock
const MockResourceLauncher = ResourceLauncher as jest.MockedFunction<typeof ResourceLauncher>; // Cast the mocked function so TS stops complaining
MockResourceLauncher.mockImplementation(ActualResourceLauncher); // Return the actual function unless overridden

import SourceDropdown from '../SourceDropdown/SourceDropdown'; // Import Functional Component
jest.mock('../SourceDropdown/SourceDropdown'); // Mock the Functional Component
const ActualSourceDropdown = jest.requireActual('../SourceDropdown/SourceDropdown').default; // Grab the real copy of it as normally we don't want the mock
const MockSourceDropdown = SourceDropdown as jest.MockedFunction<typeof SourceDropdown>; // Cast the mocked function so TS stops complaining
MockSourceDropdown.mockImplementation(ActualSourceDropdown); // Return the actual function unless overridden

import SourceDropdownContainer from '../SourceDropdownContainer/SourceDropdownContainer';
jest.mock('../SourceDropdownContainer/SourceDropdownContainer', () => {
    return jest.fn().mockReturnValue(<div />);
});

const mockSourceBrowserComponent = jest.fn();
const defaultProps = {
    title: '',
    titleAriaProps: {},
    allowedTypes: undefined,
    sources: [],
    selectedSource: null,
    onSourceSelect: jest.fn(),
    onChange: jest.fn(),
    onClose: jest.fn(),
    preselectedResource: null,
    plugin: null,
    pluginMode: null,
    searchEnabled: false,
};

describe('MainContainer', () => {
    it('clicking close button should call the onClose callback', async () => {
        const onClose = jest.fn();

        render(<MainContainer {...defaultProps} title="Select Asset" onClose={onClose} />);

        await act(() => fireEvent.click(screen.getByRole('button', { name: 'Close Select Asset dialog' })));
        expect(onClose).toHaveBeenCalled();
    });

    it('should render Environment Selector title', () => {
        render(<MainContainer {...defaultProps} title="Select MyAsset" />);
        expect(screen.getByText('Select myasset')).toBeInTheDocument();
        expect(screen.queryByText('Environment Selector')).not.toBeInTheDocument();
    });

    it('should render title in Sentence case', () => {
        const titles = ['Select Asset', 'Select MyAsset', 'another title'];
        titles.forEach((title) => {
            render(<MainContainer {...defaultProps} title={title} />);
            const expectedTitle = title.charAt(0).toUpperCase() + title.slice(1).toLowerCase();
            expect(screen.getByText(expectedTitle)).toBeInTheDocument();
        });
    });

    describe('no source selected', () => {
        it('should not render source selector dropdown', () => {
            render(<MainContainer {...defaultProps} />);
            expect(MockSourceDropdown).not.toHaveBeenCalled();
        });

        it('should not render plugin UI', () => {
            render(<MainContainer {...defaultProps} />);
            expect(mockSourceBrowserComponent).not.toHaveBeenCalled();
        });

        it('should render resource launcher list when searchEnabled using provided props', () => {
            const sources: ResourceBrowserSourceWithPlugin[] = [];
            const onSourceSelect = jest.fn();
            render(<MainContainer {...defaultProps} searchEnabled={true} sources={sources} onSourceSelect={onSourceSelect} />);
            expect(MockResourceLauncher).toHaveBeenCalledWith({ sources, onSourceSelect }, {});
        });

        it('should render source list using provided props', () => {
            const sources: ResourceBrowserSourceWithPlugin[] = [];
            const onSourceSelect = jest.fn();
            render(<MainContainer {...defaultProps} sources={sources} onSourceSelect={onSourceSelect} />);
            expect(MockSourceList).toHaveBeenCalledWith({ sources, onSourceSelect }, {});
        });
    });

    describe('source selected', () => {
        const source = mockSource({ type: 'dam' });

        it('it should create a default source switcher if header portal not requested and sources > 1', async () => {
            const source1 = mockSourceWithPlugin();
            const source2 = mockSourceWithPlugin();

            const plugin = {
                type: 'dam',
                createHeaderPortal: false,
                sourceBrowserComponent: jest.fn,
                renderSelectedResource: jest.fn(),
                useResolveResource: jest.fn(),
                sourceSearchComponent: jest.fn(),
                renderResourceLauncher: jest.fn(),
            } as ResourceBrowserPlugin;
            render(
                <MainContainer
                    {...defaultProps}
                    sources={[source1, source2]}
                    selectedSource={source1}
                    plugin={plugin}
                    pluginMode={{ type: PluginLaunchModeType.Browse }}
                />,
            );

            expect(SourceDropdownContainer).toHaveBeenCalled();
        });

        it('it should not create a default source switcher if header portal not requested and sources < 2', async () => {
            const source1 = mockSourceWithPlugin();

            const plugin = {
                type: 'dam',
                createHeaderPortal: false,
                sourceBrowserComponent: jest.fn,
                renderSelectedResource: jest.fn(),
                useResolveResource: jest.fn(),
                sourceSearchComponent: jest.fn(),
                renderResourceLauncher: jest.fn(),
            } as ResourceBrowserPlugin;
            render(
                <MainContainer
                    {...defaultProps}
                    sources={[source1]}
                    selectedSource={source1}
                    plugin={plugin}
                    pluginMode={{ type: PluginLaunchModeType.Browse }}
                />,
            );

            expect(SourceDropdownContainer).not.toHaveBeenCalled();
        });

        describe('Browser mode', () => {
            it('should create header portal for plugin', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
                const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: mockSourceBrowserComponent,
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: jest.fn(),
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;
                render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} />);
                expect(mockSourceBrowserComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            headerPortal: expect.any(Element),
                        }),
                        {},
                    );
                });
            });

            it('preselectedResource: should pass expected params to plugin UI generator functional component', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
                const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: mockSourceBrowserComponent,
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: jest.fn(),
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;

                const resource = mockResource();
                const props = {
                    allowedTypes: ['image'],
                    preselectedResource: resource,
                };

                render(<MainContainer {...defaultProps} selectedSource={source} plugin={plugin} {...props} />);
                expect(mockSourceBrowserComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            source: source,
                            allowedTypes: props.allowedTypes,
                            headerPortal: expect.any(Element),
                            preselectedResource: resource,
                        }),
                        {},
                    );
                });
            });

            it('browseTo: should pass expected params to plugin UI generator functional component', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
                const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: mockSourceBrowserComponent,
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: jest.fn(),
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;

                const resource = mockResource();
                const browseTo = { resourceId: resource.id, sourceId: resource.source.id };
                const props = {
                    allowedTypes: ['image'],
                };

                render(
                    <MainContainer
                        {...defaultProps}
                        selectedSource={source}
                        plugin={plugin}
                        pluginMode={{
                            type: PluginLaunchModeType.Browse,
                            args: { browseTo },
                        }}
                        {...props}
                    />,
                );
                expect(mockSourceBrowserComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            source: source,
                            allowedTypes: props.allowedTypes,
                            headerPortal: expect.any(Element),
                            browseTo,
                        }),
                        {},
                    );
                });
            });

            it('should pass onSelected to plugin UI generator FC when invoked calls change and close functions', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source UI has been rendered</div>);
                const mockSourceBrowserComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: mockSourceBrowserComponent,
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: jest.fn(),
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;

                const onChange = jest.fn();
                const onClose = jest.fn();

                render(
                    <MainContainer
                        {...defaultProps}
                        searchEnabled={true}
                        selectedSource={source}
                        plugin={plugin}
                        onChange={onChange}
                        onClose={onClose}
                    />,
                );
                expect(mockSourceBrowserComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            onSelected: expect.any(Function),
                            searchEnabled: true,
                        }),
                        {},
                    );
                });

                const resource = mockResource();
                const { onSelected } = mockFunctionalComponent.mock.calls[0][0];
                onSelected(resource);
                expect(onChange).toHaveBeenCalledWith(resource);
                expect(onClose).toHaveBeenCalled();
            });
        });

        describe('Search mode', () => {
            it('should create header portal for plugin', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source search UI has been rendered</div>);
                const mockSourceSearchComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: jest.fn,
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: mockSourceSearchComponent,
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;
                render(
                    <MainContainer
                        {...defaultProps}
                        selectedSource={source}
                        plugin={plugin}
                        pluginMode={{ type: PluginLaunchModeType.Search }}
                    />,
                );
                expect(mockSourceSearchComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            headerPortal: expect.any(Element),
                        }),
                        {},
                    );
                });
            });

            it('should pass expected params to plugin UI generator functional component', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source search UI has been rendered</div>);
                const mockSourceSearchComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: jest.fn(),
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: mockSourceSearchComponent,
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;

                const props = {
                    allowedTypes: ['image'],
                };

                render(
                    <MainContainer
                        {...defaultProps}
                        selectedSource={source}
                        plugin={plugin}
                        pluginMode={{ type: PluginLaunchModeType.Search, args: { query: 'myQuery' } }}
                        {...props}
                    />,
                );
                expect(mockSourceSearchComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            source: source,
                            allowedTypes: props.allowedTypes,
                            headerPortal: expect.any(Element),
                            query: 'myQuery',
                        }),
                        {},
                    );
                });
            });

            it('should pass onSelected to plugin UI generator FC when invoked calls change and close functions', async () => {
                const mockFunctionalComponent = jest.fn().mockReturnValue(<div>Source search UI has been rendered</div>);
                const mockSourceSearchComponent = jest.fn().mockReturnValue(mockFunctionalComponent);
                const plugin = {
                    type: 'dam',
                    createHeaderPortal: true,
                    sourceBrowserComponent: jest.fn(),
                    renderSelectedResource: jest.fn(),
                    useResolveResource: jest.fn(),
                    sourceSearchComponent: mockSourceSearchComponent,
                    renderResourceLauncher: jest.fn(),
                } as ResourceBrowserPlugin;

                const onChange = jest.fn();
                const onClose = jest.fn();

                render(
                    <MainContainer
                        {...defaultProps}
                        selectedSource={source}
                        searchEnabled={true}
                        plugin={plugin}
                        pluginMode={{ type: PluginLaunchModeType.Search, args: { query: 'myQuery' } }}
                        onChange={onChange}
                        onClose={onClose}
                    />,
                );
                expect(mockSourceSearchComponent).toHaveBeenCalled();

                await waitFor(() => {
                    expect(mockFunctionalComponent).toHaveBeenCalledWith(
                        expect.objectContaining({
                            onSelected: expect.any(Function),
                            searchEnabled: true,
                        }),
                        {},
                    );
                });

                const resource = mockResource();
                const { onSelected } = mockFunctionalComponent.mock.calls[0][0];
                onSelected(resource);
                expect(onChange).toHaveBeenCalledWith(resource);
                expect(onClose).toHaveBeenCalled();
            });
        });
    });
});
