import React from 'react';
import { render, fireEvent, act, screen } from '@testing-library/react';
import {
    APP_AUDIENCE,
    APP_INSIGHTS,
    APP_SETTINGS,
    BRAND_DOMAINS,
    BRAND_TITLES,
    ENV_PROD,
} from '@theorchard/constants';
import { AUTH_ERROR_CODES } from '@theorchard/suite-auth';
import { SuiteAppConfig } from '@theorchard/suite-config';
import { formatMessage, getMessage } from '@theorchard/suite-i18n';
import Segment from '../../../segment';
import { SuiteError } from '../../../types';
import { ERROR_CODES } from '../../../utils';
import * as storageUtils from '../../../utils/storage';
import { FatalErrorPage } from '../fatalErrorPage';

vi.mock('../../../utils/storage', () => ({
    clearAppStorage: vi.fn(),
}));

describe('<FatalErrorPage>', () => {
    const error = {
        name: 'test',
        message: 'test message',
        code: ERROR_CODES.FAILED_TO_START,
    };

    const defaultConfig: SuiteAppConfig = {
        appTitle: 'test app',
        appName: 'frontend-test',
        environment: 'prod',
        brand: 'orchard',
        cdnUrl: '',
        auth0Audience: '',
        auth0ClientId: '',
        auth0Domain: '',
        graphqlUrl: '',
    };

    const renderComponent = (initError: SuiteError, config?: Partial<SuiteAppConfig>) =>
        render(<FatalErrorPage error={initError} config={{ ...defaultConfig, ...config }} />);

    beforeEach(() => {
        vi.spyOn(Segment, 'trackEvent').mockImplementation();
        vi.clearAllMocks();
    });

    test('does not render error message', () => {
        const { getByText, queryByText } = renderComponent(error);
        expect(queryByText(error.message)).toBeNull();
        expect(getByText('Retry')).toBeVisible();
    });

    describe.each(['dev', 'qa'])('in "%s" environment', (environment) => {
        const config = { environment };

        test('renders error message', () => {
            const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
            const { getByText } = renderComponent(error, config);
            expect(consoleSpy).toHaveBeenCalledWith(error);
            expect(getByText('Retry')).toBeVisible();
            consoleSpy.mockRestore();
        });
    });

    test('does not render retry button if error.retry == false', () => {
        const { queryByText } = renderComponent({ ...error, retry: false });
        expect(queryByText('Retry')).toBeNull();
    });

    describe('clicking on "Retry"', () => {
        test('clears the application storage', () => {
            const { getByText } = renderComponent(error);
            const btn = getByText('Retry');

            fireEvent.click(btn);

            expect(storageUtils.clearAppStorage).toHaveBeenCalledTimes(1);
        });

        test('reloads app', () => {
            const { getByText } = renderComponent(error);
            const btn = getByText('Retry');

            fireEvent.click(btn);

            expect(window.location.replace).toHaveBeenCalledWith('/');
        });
    });

    test('renders `fatalError` illustration', () => {
        const { getByTestId } = renderComponent(error);
        const element = getByTestId('FatalErrorIllustration');
        expect(element).toBeVisible();
    });

    describe('impersonation', () => {
        test('shows banner when active', () => {
            const { getByTestId, getByText } = renderComponent({
                ...error,
                identity: {
                    impersonatedBy: {
                        id: '123',
                        name: 'Test User',
                    },
                    features: {},
                    id: 'abc',
                },
            });

            const element = getByText('Impersonation Mode');
            expect(element).toBeVisible();
            expect(getByTestId('ImpersonationBanner')).toBeInTheDocument();
            expect(getByText('Test User', { exact: false })).toBeInTheDocument();
        });

        test('banner is not shown when not active', () => {
            const { queryByTestId } = renderComponent({
                ...error,
                identity: {
                    impersonatedBy: undefined,
                    features: {},
                    id: 'abc',
                },
            });

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

    describe.each([['orchard'], ['sme'], ['awal'], ['knr']])('brand is "%s"', (brand) => {
        const brandTitle = BRAND_TITLES[brand] ?? 'unknown';
        const config = { brand, appTitle: 'The APP' };
        const message = 'lalalala';

        describe('with "wrong organization" message', () => {
            test('renders "wrong org" title', () => {
                const wrongOrgMessage = 'The user blablabla is not part of the Shield Organization';
                const { getByText, queryByText } = renderComponent(
                    {
                        name: 'error',
                        message: wrongOrgMessage,
                        code: AUTH_ERROR_CODES.WRONG_ORG,
                    },
                    config
                );
                expect(getByText(`You don't have access to ${brandTitle}`)).toBeVisible();
                expect(queryByText(wrongOrgMessage)).toBeNull();
                expect(queryByText('Resend verify email')).toBeNull();
            });
        });

        describe('with "wrongOrg" code', () => {
            test('renders "wrong org" title', () => {
                const { getByText, queryByText } = renderComponent(
                    {
                        name: 'test',
                        code: AUTH_ERROR_CODES.WRONG_ORG,
                        message,
                    },
                    config
                );
                expect(getByText(`You don't have access to ${brandTitle}`)).toBeVisible();
                expect(queryByText(message)).toBeNull();
                expect(queryByText('Resend verify email')).toBeNull();
            });
        });

        describe('with "invitationExpired" code', () => {
            test('renders "invitation expired" title', () => {
                const { getByText, queryByText } = renderComponent(
                    {
                        name: 'test',
                        code: AUTH_ERROR_CODES.INVITATION_EXPIRED,
                        message,
                    },
                    config
                );
                expect(getByText('This link has expired')).toBeVisible();
                expect(queryByText(message)).toBeNull();
                expect(queryByText('Resend verify email')).toBeNull();
            });
        });

        describe('with "noAccess" code', () => {
            test('renders "no access" message', () => {
                const { getByText, queryByText } = renderComponent(
                    {
                        name: 'test',
                        code: AUTH_ERROR_CODES.NO_ACCESS,
                        message,
                    },
                    config
                );

                const brandedMessage =
                    getMessage(`error_noAccess_message_${brand}`)?.toString() ??
                    formatMessage('error_noAccess_message', {
                        brand: brandTitle,
                    });

                expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();
                expect(getByText(brandedMessage)).toBeVisible();
                expect(queryByText(message)).toBeNull();
                expect(queryByText('Resend verify email')).toBeNull();
            });
        });

        describe('with "emailNotVerified" code', () => {
            test('renders "unverified email" message', () => {
                const { getByText } = renderComponent(
                    {
                        name: 'test',
                        code: AUTH_ERROR_CODES.UNVERIFIED_EMAIL,
                        message: 'email_not_verified|auth0|123',
                    },
                    config
                );

                expect(getByText('Please verify your email before logging in.')).toBeVisible();
                expect(getByText('Resend verify email')).toBeVisible();
            });
        });
    });

    describe.each([['orchard'], ['awal'], ['knr']])('brand is "%s"', (brand) => {
        const config = {
            brand,
            appTitle: 'The APP',
            environment: ENV_PROD,
        };

        describe('with "noAccess" code', () => {
            const error = {
                name: 'test',
                code: AUTH_ERROR_CODES.NO_ACCESS,
                message: 'No profiles for app',
            };

            const settingsApp = {
                id: APP_SETTINGS,
                name: 'Settings',
                url: 'settings.com',
            };

            const applications = [
                settingsApp,
                {
                    id: APP_INSIGHTS,
                    name: 'Insights',
                    url: 'insights.com',
                },
            ];

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

            afterEach(() => {
                vi.clearAllTimers();
                vi.useRealTimers();
            });

            describe('with no user applications', () => {
                test('renders "no access" message', () => {
                    const { getByText } = renderComponent(error, config);

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();
                    expect(getByText('Back to login')).toBeVisible();
                });
            });

            describe('with only allowed to access settings app and somehow this is settings app', () => {
                test('renders "no access" message', () => {
                    const { getByText } = renderComponent(
                        {
                            ...error,
                            identity: {
                                id: '1',
                                features: {},
                                applications: [settingsApp],
                            },
                        },
                        config
                    );

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();
                    expect(getByText('Back to login')).toBeVisible();
                });
            });

            describe('with one user application', () => {
                test('renders automatic redirect message to that application', () => {
                    const { getByText } = renderComponent(
                        {
                            ...error,
                            identity: {
                                id: '1',
                                features: {},
                                applications,
                            },
                        },
                        config
                    );

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();

                    expect(
                        getByText('You will be redirected to the Insights app in 5 seconds...')
                    ).toBeInTheDocument();
                });

                test('redirects after 5 seconds', async () => {
                    renderComponent(
                        {
                            ...error,
                            identity: {
                                id: '1',
                                features: {},
                                applications,
                            },
                        },
                        config
                    );

                    for (let index = 5; index > 0; index--) {
                        await act(async () => {
                            await vi.runOnlyPendingTimersAsync();
                        });

                        expect(
                            screen.getByText(
                                `You will be redirected to the Insights app in ${index - 1} seconds...`
                            )
                        ).toBeInTheDocument();
                    }

                    expect(window.location.assign).toHaveBeenCalledTimes(1);
                    expect(window.location.assign).toHaveBeenCalledWith('insights.com');
                });
            });

            describe('with multiple user applications', () => {
                const identity = {
                    id: '1',
                    features: {},
                    applications: [
                        ...applications,
                        {
                            id: APP_AUDIENCE,
                            name: 'Audience',
                            url: 'audience.com',
                        },
                    ],
                };

                test('renders automatic redirect message to Settings', () => {
                    const { getByText } = renderComponent({ ...error, identity }, config);

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();

                    expect(
                        getByText('You will be redirected to the Settings app in 5 seconds...')
                    ).toBeInTheDocument();
                });

                test('redirects after 5 seconds', async () => {
                    renderComponent({ ...error, identity }, config);

                    for (let index = 5; index > 0; index--) {
                        await act(async () => {
                            await vi.runOnlyPendingTimersAsync();
                        });

                        expect(
                            screen.getByText(
                                `You will be redirected to the Settings app in ${index - 1} seconds...`
                            )
                        ).toBeInTheDocument();
                    }

                    expect(window.location.assign).toHaveBeenCalledTimes(1);
                    expect(window.location.assign).toHaveBeenCalledWith(
                        `https://settings.${BRAND_DOMAINS[brand][ENV_PROD]}/overview`
                    );
                });
            });
        });
    });

    describe.each([['sme']])('brand is "%s"', (brand) => {
        const config = { brand, appTitle: 'The APP' };

        describe('with "no access" code', () => {
            const error = {
                name: 'test',
                code: AUTH_ERROR_CODES.NO_ACCESS,
                message: 'No profiles for app',
            };

            const applications = [
                {
                    id: APP_SETTINGS,
                    name: 'Settings',
                    url: 'settings.com',
                },
                {
                    id: APP_INSIGHTS,
                    name: 'Insights',
                    url: 'insights.com',
                },
            ];

            describe('with no user applications', () => {
                test('renders "no access" message', () => {
                    const { getByText } = renderComponent(error, config);

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();
                    expect(getByText('Back to login')).toBeVisible();
                });
            });

            describe('with one user application', () => {
                test('renders automatic redirect message to that application', () => {
                    const { getByText } = renderComponent(
                        {
                            ...error,
                            identity: {
                                id: '1',
                                features: {},
                                applications,
                            },
                        },
                        config
                    );

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();

                    expect(
                        getByText('You will be redirected to the Insights app in 5 seconds...')
                    ).toBeInTheDocument();
                });
            });

            describe('with multiple user applications', () => {
                test('renders "no access" message', () => {
                    const { getByText } = renderComponent(
                        {
                            ...error,
                            identity: {
                                id: '1',
                                features: {},
                                applications: [
                                    ...applications,
                                    {
                                        id: APP_AUDIENCE,
                                        name: 'Audience',
                                        url: 'audience.com',
                                    },
                                ],
                            },
                        },
                        config
                    );

                    expect(getByText(`You don't have access to ${config.appTitle}`)).toBeVisible();
                    expect(getByText('Back to login')).toBeVisible();
                });
            });
        });
    });
});
