import * as React from 'react';
import { mocked } from 'jest-mock';
import { act, render } from '@testing-library/react';
import type { ExtensionDescriptor } from '@atlassian/clientside-extensions-registry';
import registry from '@atlassian/clientside-extensions-registry';
import { ErrorLevels } from '@atlassian/clientside-extensions-schema';
import type { Validator } from '@atlassian/clientside-extensions-schema';

import * as debug from '@atlassian/clientside-extensions-debug';

import ExtensionPoint from './ExtensionPoint';

jest.mock('@atlassian/clientside-extensions-registry');

debug.setLoggingEnabled(true);
debug.setDebugEnabled(true);
// avoid spamming the console with error/warn messages
// eslint-disable-next-line no-underscore-dangle
debug._deregisterDefaultLogger();

type MockSubjectPayload = {
    descriptors: ExtensionDescriptor[];
    loadingState: boolean;
};

type MockLocationObserver = (payload: MockSubjectPayload) => void;

const supportedExtension: ExtensionDescriptor = {
    key: 'fake-1',
    location: 'fake-location',
    weight: 10,
    attributes: { label: 'a fake label', type: 'button' },
};
const unsupportedExtension: ExtensionDescriptor = {
    key: 'fake-2',
    location: 'fake-location',
    weight: 10,
    attributes: { label: 'a fake label' },
};

const validate: Validator = (input) => {
    const { type } = input as { type: string };

    return type
        ? {
              errors: [],
              warnings: [],
          }
        : {
              errors: [
                  {
                      error: 'error',
                      severity: ErrorLevels.ERROR,
                  },
              ],
              warnings: [],
          };
};

/**
 * Only testing the runtime error of a wrong child provided and that the component is using the right hook.
 * The rest of the tests are in useExtensions.tests.
 */
describe('ExtensionPoint component', () => {
    it('should not render if the child provided is not a callback', async () => {
        const onDebugSpy = jest.fn();
        debug.observeLogger(onDebugSpy);

        mocked(registry.getLocation).mockImplementation(() => ({
            subscribe: () => {
                return {
                    unsubscribe: () => {},
                };
            },
        }));

        const TestComponent = () => {
            return (
                // @ts-expect-error - forcing ts to ignore assigning a wrong child type to execute JS runtime validation
                <ExtensionPoint name="fake-extension" context={null} options={{ attributeValidator: validate }}>
                    <p data-testid="test-text">Wrong child</p>;
                </ExtensionPoint>
            );
        };

        const { container } = render(<TestComponent />);

        expect(container.firstChild).toBe(null); // nothing rendered by the component
        expect(onDebugSpy).toHaveBeenCalledTimes(1);
    });

    it('should provide all the extensions information to the child callback', () => {
        let locationObserver: MockLocationObserver;
        mocked(registry.getLocation).mockImplementation(() => ({
            subscribe: (observer) => {
                locationObserver = observer;
                return {
                    unsubscribe: () => {},
                };
            },
        }));

        const TestComponent = () => {
            return (
                <ExtensionPoint name="fake-extension" context={null} options={{ attributeValidator: validate }}>
                    {(supportedExtensions, unsupportedExtensions, isLoading) => (
                        <>
                            <p data-testid="isLoading">{JSON.stringify(isLoading)}</p>
                            <p data-testid="supportedExtensions">{JSON.stringify(supportedExtensions)}</p>
                            <p data-testid="unsupportedExtensions">{JSON.stringify(unsupportedExtensions)}</p>
                        </>
                    )}
                </ExtensionPoint>
            );
        };

        const { asFragment } = render(<TestComponent />);

        expect(asFragment()).toMatchSnapshot('loading state');

        act(() => {
            locationObserver({ descriptors: [supportedExtension, unsupportedExtension], loadingState: false });
        });

        expect(asFragment()).toMatchSnapshot('with extensions state');
    });
});
