import React from 'react';
import { render } from '@testing-library/react';
import { FieldValidator } from '@theorchard/field-validator';
import { get } from 'lodash-es';
import { useAppConfig } from '../appConfigContext';
import { AppConfigProvider, AppConfigProviderProps } from '../appConfigProvider';
import { SuiteAppConfig } from '../types';
import { createConfig } from './mocks';

interface ErrorBoundaryProps {
    children: React.ReactNode;
}

class ErrorBoundary extends React.Component<ErrorBoundaryProps, { error?: Error }> {
    constructor(props: ErrorBoundaryProps) {
        super(props);
        this.state = {};
    }

    componentDidCatch(error: Error) {
        this.setState({ error });
    }

    render() {
        const { error } = this.state;
        const { children } = this.props;

        if (error) return <span>{error.message}</span>;
        return children;
    }
}

describe('renders AppConfigProvider', () => {
    const appConfig = createConfig();

    const ChildComponent: React.FC = () => {
        const config = useAppConfig();
        return <span data-testid="spanChild">{JSON.stringify(config)}</span>;
    };

    const renderComponent = (props: AppConfigProviderProps = { config: appConfig }) =>
        render(
            <AppConfigProvider {...props}>
                <ChildComponent />
            </AppConfigProvider>,
            {
                wrapper: ({ children }) => <ErrorBoundary>{children}</ErrorBoundary>,
            }
        );

    test('renders correctly with child', () => {
        const extraOptions = { extraKey: 'included' };
        const { container, getByTestId } = renderComponent({
            config: {
                ...appConfig,
                ...extraOptions,
            },
        });

        // eslint-disable-next-line testing-library/no-node-access
        expect(container.firstChild).toBeInTheDocument();

        const element = getByTestId('spanChild');
        expect(element).toBeInTheDocument();

        expect(JSON.parse(element.innerHTML)).toEqual({
            ...appConfig,
            ...extraOptions,
        });
    });

    describe('on missing options', () => {
        let globalConsoleError: typeof global.console.error;

        beforeEach(() => {
            globalConsoleError = global.console.error;
            global.console.error = jest.fn();
        });

        afterEach(() => {
            global.console.error = globalConsoleError;
        });

        test.each([
            'brand',
            'appName',
            'auth0Audience',
            'auth0Audience',
            'auth0ClientId',
            'auth0Domain',
        ])('throws error message on invalid config option for "%s"', (option) => {
            const { getByText } = renderComponent({
                config: {
                    ...appConfig,
                    [option]: undefined,
                },
            });

            expect(getByText(`Missing required string field: "${option}"`)).toBeVisible();
        });
    });

    test('converts empty strings to undefined', () => {
        const { getByTestId } = renderComponent({
            config: {
                ...appConfig,
                gitCommit: '',
            },
        });
        const element = getByTestId('spanChild');
        expect(get(JSON.parse(element.innerHTML), 'gitCommit')).toBeUndefined();
    });

    test('validates "userFeatureFlags"', () => {
        const { getByTestId } = renderComponent({
            config: {
                ...appConfig,
                userFeatureFlags: {
                    myCleverFeature: 'true',
                    enabledFeature: 'enabled',
                    disabledFeature: 'disabled',
                    controlFeature: 'control',
                    booleanFeature: true,
                },
            } as unknown as SuiteAppConfig,
        });

        const element = getByTestId('spanChild');
        expect(get(JSON.parse(element.innerHTML), 'userFeatureFlags')).toEqual({
            myCleverFeature: false,
            enabledFeature: true,
            disabledFeature: false,
            controlFeature: false,
            booleanFeature: true,
        });
    });

    test('invokes the "validate" callback', () => {
        const customConfig = { override: 'true' };
        const validate = jest.fn().mockReturnValue(customConfig);
        const { getByTestId } = renderComponent({
            config: appConfig,
            validate,
        });

        expect(validate).toHaveBeenCalledWith(appConfig, expect.any(FieldValidator));

        const element = getByTestId('spanChild');
        expect(JSON.parse(element.innerHTML)).toEqual(customConfig);
    });
});
