import React from 'react';
import * as Sentry from '@sentry/browser';
import { render } from '@testing-library/react';
import { AUTH_ERROR_CODES } from '@theorchard/suite-auth';
import { Identity } from '@theorchard/suite-identity';
import Datadog from '../../../datadog';
import Segment from '../../../segment';
import { SuiteErrorCategory, SuiteError, SuiteInitContext } from '../../../types';
import { getErrorCode } from '../../../utils';
import { FatalErrorHandler } from '../fatalErrorHandler';

describe('<FatalErrorHandler>', () => {
    const CHILDREN = 'CHILDREN';
    const data = {
        config: {
            appName: 'test',
            appTitle: 'test',
            brand: 'orchard',
            environment: 'prod',
        },
    } as SuiteInitContext;

    vi.spyOn(Sentry, 'captureException').mockImplementation();
    vi.spyOn(Sentry, 'setUser').mockImplementation();
    vi.spyOn(Datadog, 'addError').mockImplementation();
    vi.spyOn(Datadog, 'setUser').mockImplementation();
    vi.spyOn(Segment, 'setUser').mockImplementation();
    vi.spyOn(Segment, 'trackEvent').mockImplementation();

    beforeEach(() => {
        vi.clearAllMocks();
    });

    const renderComponent = (error?: SuiteError) =>
        render(
            <FatalErrorHandler error={error} data={data} loading={false}>
                {CHILDREN}
            </FatalErrorHandler>
        );

    describe('with normal error', () => {
        const error = new Error('oops');

        test('sends error to Sentry and Datadog', () => {
            renderComponent(error);

            const code = getErrorCode(error);

            expect(Sentry.setUser).not.toHaveBeenCalled();
            expect(Datadog.setUser).not.toHaveBeenCalled();

            expect(Sentry.captureException).toHaveBeenCalledWith(error, {
                extra: { code },
                level: 'error',
            });

            expect(Datadog.addError).toHaveBeenCalledWith(error, { code });
            expect(Segment.trackEvent).not.toHaveBeenCalled();
        });

        test('does not render the fatal error page', () => {
            const { queryByTestId } = renderComponent(error);

            expect(queryByTestId('FatalErrorPage')).toBeNull();
        });

        test('renders children', () => {
            const { getByText } = renderComponent(error);

            expect(getByText(CHILDREN)).toBeInTheDocument();
        });

        describe('with user', () => {
            const identity = { name: 'test' } as Identity;

            test('sets Sentry, Datadog and Segment user', () => {
                renderComponent({ ...error, identity });

                expect(Sentry.setUser).toHaveBeenCalledWith(identity);
                expect(Datadog.setUser).toHaveBeenCalledWith(identity);
                expect(Segment.setUser).toHaveBeenCalledWith(identity);
            });
        });
    });

    describe('with fatal error', () => {
        const error = {
            message: 'fatal',
            name: 'fatal',
            category: SuiteErrorCategory.Fatal,
        };

        test('sends error to Sentry, Datadog and Segment', () => {
            renderComponent(error);

            expect(Sentry.setUser).not.toHaveBeenCalled();
            expect(Datadog.setUser).not.toHaveBeenCalled();
            expect(Segment.setUser).not.toHaveBeenCalled();

            const code = getErrorCode(error);

            expect(Sentry.captureException).toHaveBeenCalledWith(error, {
                extra: { code },
                level: 'fatal',
            });
            expect(Datadog.addError).toHaveBeenCalledWith(error, { code });
            expect(Segment.trackEvent).toHaveBeenCalledWith('Fatal Error', {
                ...error,
                code,
            });
        });

        test('renders the fatal error page', () => {
            const { getByTestId } = renderComponent(error);

            expect(getByTestId('FatalErrorPage')).toBeInTheDocument();
        });

        test('does not render children', () => {
            const { queryByText } = renderComponent(error);

            expect(queryByText(CHILDREN)).toBeNull();
        });

        describe('with user', () => {
            const identity = { name: 'test' } as Identity;

            test('sets Sentry, Datadog and Segment user', () => {
                renderComponent({ ...error, identity });

                expect(Sentry.setUser).toHaveBeenCalledWith(identity);
                expect(Datadog.setUser).toHaveBeenCalledWith(identity);
                expect(Segment.setUser).toHaveBeenCalledWith(identity);
            });
        });

        describe('invitation errors', () => {
            test('tracks with "Invalid Invitation" title for INVITATION_INVALID code', () => {
                const invitationError = {
                    ...error,
                    code: AUTH_ERROR_CODES.INVITATION_INVALID,
                };

                renderComponent(invitationError);

                expect(Segment.trackEvent).toHaveBeenCalledWith(
                    'Invalid Invitation',
                    expect.objectContaining({
                        code: AUTH_ERROR_CODES.INVITATION_INVALID,
                    })
                );
            });

            test('tracks with "Expired Invitation" title for INVITATION_EXPIRED code', () => {
                const invitationError = {
                    ...error,
                    code: AUTH_ERROR_CODES.INVITATION_EXPIRED,
                };

                renderComponent(invitationError);

                expect(Segment.trackEvent).toHaveBeenCalledWith(
                    'Expired Invitation',
                    expect.objectContaining({
                        code: AUTH_ERROR_CODES.INVITATION_EXPIRED,
                    })
                );
            });

            test('tracks with default "Fatal Error" title for other error codes', () => {
                renderComponent(error);

                expect(Segment.trackEvent).toHaveBeenCalledWith(
                    'Fatal Error',
                    expect.objectContaining(error)
                );
            });
        });
    });

    describe('without error', () => {
        test('does not send error to Sentry or Datadog', () => {
            render(<FatalErrorHandler error={undefined} data={data} loading={false} />);

            expect(Sentry.captureException).not.toHaveBeenCalled();
            expect(Datadog.addError).not.toHaveBeenCalled();
        });

        test('renders children', () => {
            const { getByText } = renderComponent();

            expect(getByText(CHILDREN)).toBeInTheDocument();
        });
    });
});
