// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

/* eslint-disable global-require*/
import {
    Alert,
    AppState,
    Dimensions,
    InteractionManager,
    NativeModules,
    Platform,
    YellowBox,
} from 'react-native';
const {StatusBarManager, MattermostShare, Initialization} = NativeModules;

import DeviceInfo from 'react-native-device-info';
import {Navigation, NativeEventsReceiver} from 'react-native-navigation';
import {Provider} from 'react-redux';
import semver from 'semver';

import {Client4} from 'mattermost-redux/client';
import {setAppState, setServerVersion} from 'mattermost-redux/actions/general';
import {loadMe, logout} from 'mattermost-redux/actions/users';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {General} from 'mattermost-redux/constants';
import EventEmitter from 'mattermost-redux/utils/event_emitter';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';

import {selectDefaultChannel} from 'app/actions/views/channel';
import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device';
import {handleLoginIdChanged} from 'app/actions/views/login';
import {handleServerUrlChanged} from 'app/actions/views/select_server';
import {loadConfigAndLicense, startDataCleanup} from 'app/actions/views/root';

import initialState from 'app/initial_state';
import configureStore from 'app/store';
import {NavigationTypes} from 'app/constants';
import mattermostBucket from 'app/mattermost_bucket';
import mattermostManaged from 'app/mattermost_managed';
import {configurePushNotifications} from 'app/utils/push_notifications';
import PushNotifications from 'app/push_notifications';
import {registerScreens} from 'app/screens';
import {deleteFileCache} from 'app/utils/file';
import avoidNativeBridge from 'app/utils/avoid_native_bridge';
import {t} from 'app/utils/i18n';
import LocalConfig from 'assets/config';
import telemetry from 'app/telemetry';

import App from './app';
import './fetch_preconfig';

const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 60 * 1000;

// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841)
YellowBox.ignoreWarnings(['Require cycle: node_modules/react-native/Libraries/Network/fetch.js']);

export const app = new App();
export const store = configureStore(initialState);
registerScreens(store, Provider);

const lazyLoadExternalModules = () => {
    const StatusBarSizeIOS = require('react-native-status-bar-size');
    const initializeErrorHandling = require('app/utils/error_handling').initializeErrorHandling;
    return {
        StatusBarSizeIOS,
        initializeErrorHandling,
    };
};

const lazyLoadAnalytics = () => {
    const initAnalytics = require('app/utils/segment').init;

    return {
        initAnalytics,
    };
};

const initializeModules = () => {
    const {
        StatusBarSizeIOS,
        initializeErrorHandling,
    } = lazyLoadExternalModules();
    const {
        config,
    } = store.getState().entities.general;
    const window = Dimensions.get('window');

    initializeErrorHandling();

    EventEmitter.on(NavigationTypes.NAVIGATION_RESET, handleLogout);
    EventEmitter.on(NavigationTypes.RESTART_APP, restartApp);
    EventEmitter.on(General.SERVER_VERSION_CHANGED, handleServerVersionChanged);
    EventEmitter.on(General.CONFIG_CHANGED, handleConfigChanged);
    EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, handleSwitchToDefaultChannel);
    Dimensions.addEventListener('change', handleOrientationChange);
    mattermostManaged.addEventListener('managedConfigDidChange', () => {
        handleManagedConfig(true);
    });

    if (config) {
        configureAnalytics(config);
    }

    handleOrientationChange({window});

    if (Platform.OS === 'ios') {
        StatusBarSizeIOS.addEventListener('willChange', handleStatusBarHeightChange);

        StatusBarManager.getHeight(
            (data) => {
                handleStatusBarHeightChange(data.height);
            }
        );
    }
};

const configureAnalytics = (config) => {
    const {
        initAnalytics,
    } = lazyLoadAnalytics();
    if (!__DEV__ && config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) {
        initAnalytics(config);
    } else {
        global.analytics = null;
    }
};

const resetBadgeAndVersion = () => {
    Client4.serverVersion = '';
    Client4.setUserId('');
    PushNotifications.setApplicationIconBadgeNumber(0);
    PushNotifications.cancelAllLocalNotifications();
    store.dispatch(setServerVersion(''));
};

const handleLogout = () => {
    Client4.setCSRF(null);
    store.dispatch(closeWebSocket(false));

    app.setAppStarted(true);
    app.clearNativeCache();
    deleteFileCache();
    resetBadgeAndVersion();
    launchSelectServer();
};

const restartApp = async () => {
    Navigation.dismissModal({animationType: 'none'});

    try {
        const window = Dimensions.get('window');

        handleOrientationChange({window});
        await store.dispatch(loadConfigAndLicense());
        await store.dispatch(loadMe());

        if (Platform.OS === 'ios') {
            StatusBarManager.getHeight(
                (data) => {
                    handleStatusBarHeightChange(data.height);
                }
            );
        }
    } catch (e) {
        console.warn('Failed to load initial data while restarting', e); // eslint-disable-line no-console
    }

    launchChannel();
};

const handleServerVersionChanged = async (serverVersion) => {
    const {dispatch, getState} = store;
    const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
    const translations = app.getTranslations();
    const state = getState();

    if (serverVersion) {
        if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) {
            Alert.alert(
                translations[t('mobile.server_upgrade.title')],
                translations[t('mobile.server_upgrade.description')],
                [{
                    text: translations[t('mobile.server_upgrade.button')],
                    onPress: handleServerVersionUpgradeNeeded,
                }],
                {cancelable: false}
            );
        } else if (state.entities.users && state.entities.users.currentUserId) {
            dispatch(setServerVersion(serverVersion));
            const data = await dispatch(loadConfigAndLicense());
            configureAnalytics(data.config);
        }
    }
};

const handleConfigChanged = (config) => {
    configureAnalytics(config);
};

const handleServerVersionUpgradeNeeded = async () => {
    const {dispatch, getState} = store;

    resetBadgeAndVersion();

    if (getState().entities.general.credentials.token) {
        InteractionManager.runAfterInteractions(() => {
            dispatch(logout());
        });
    }
};

const handleStatusBarHeightChange = (nextStatusBarHeight) => {
    store.dispatch(setStatusBarHeight(nextStatusBarHeight));
};

const handleOrientationChange = (dimensions) => {
    const {dispatch} = store;
    if (DeviceInfo.isTablet()) {
        dispatch(setDeviceAsTablet());
    }

    const {height, width} = dimensions.window;
    const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE';

    dispatch(setDeviceOrientation(orientation));
    dispatch(setDeviceDimensions(height, width));
};

export const handleManagedConfig = async (eventFromEmmServer = false) => {
    if (app.performingEMMAuthentication) {
        return true;
    }

    const {dispatch, getState} = store;
    const state = getState();

    let authNeeded = false;
    let blurApplicationScreen = false;
    let jailbreakProtection = false;
    let vendor = null;
    let serverUrl = null;
    let username = null;

    if (LocalConfig.AutoSelectServerUrl) {
        dispatch(handleServerUrlChanged(LocalConfig.DefaultServerUrl));
        app.setAllowOtherServers(false);
    }

    try {
        const config = await avoidNativeBridge(
            () => {
                return true;
            },
            () => {
                return Initialization.managedConfig;
            },
            () => {
                return mattermostManaged.getConfig();
            }
        );

        if (config && Object.keys(config).length) {
            app.setEMMEnabled(true);
            authNeeded = config.inAppPinCode === 'true';
            blurApplicationScreen = config.blurApplicationScreen === 'true';
            jailbreakProtection = config.jailbreakProtection === 'true';
            vendor = config.vendor || 'Mattermost';

            if (!state.entities.general.credentials.token) {
                serverUrl = config.serverUrl;
                username = config.username;

                if (config.allowOtherServers && config.allowOtherServers === 'false') {
                    app.setAllowOtherServers(false);
                }
            }

            if (jailbreakProtection) {
                const isTrusted = mattermostManaged.isTrustedDevice();

                if (!isTrusted) {
                    const translations = app.getTranslations();
                    Alert.alert(
                        translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor),
                        translations[t('mobile.managed.jailbreak')].replace('{vendor}', vendor),
                        [{
                            text: translations[t('mobile.managed.exit')],
                            style: 'destructive',
                            onPress: () => {
                                mattermostManaged.quitApp();
                            },
                        }],
                        {cancelable: false}
                    );
                    return false;
                }
            }

            if (authNeeded && !eventFromEmmServer) {
                const authenticated = await handleAuthentication(vendor);
                if (!authenticated) {
                    return false;
                }
            }

            if (blurApplicationScreen) {
                mattermostManaged.blurAppScreen(true);
            }

            if (serverUrl) {
                dispatch(handleServerUrlChanged(serverUrl));
            }

            if (username) {
                dispatch(handleLoginIdChanged(username));
            }
        }
    } catch (error) {
        return true;
    }

    return true;
};

const handleAuthentication = async (vendor, prompt = true) => {
    app.setPerformingEMMAuthentication(true);
    const isSecured = await mattermostManaged.isDeviceSecure();
    const translations = app.getTranslations();

    if (isSecured) {
        try {
            mattermostBucket.setPreference('emm', vendor);
            if (prompt) {
                await mattermostManaged.authenticate({
                    reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor),
                    fallbackToPasscode: true,
                    suppressEnterPassword: true,
                });
            }
        } catch (err) {
            mattermostManaged.quitApp();
            return false;
        }
    } else {
        await showNotSecuredAlert(vendor, translations);

        mattermostManaged.quitApp();
        return false;
    }

    app.setPerformingEMMAuthentication(false);
    return true;
};

function showNotSecuredAlert(vendor, translations) {
    return new Promise((resolve) => {
        const options = [];

        if (Platform.OS === 'android') {
            options.push({
                text: translations[t('mobile.managed.settings')],
                onPress: () => {
                    mattermostManaged.goToSecuritySettings();
                },
            });
        }

        options.push({
            text: translations[t('mobile.managed.exit')],
            onPress: resolve,
            style: 'cancel',
        });

        Alert.alert(
            translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor),
            Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')],
            options,
            {cancelable: false, onDismiss: resolve},
        );
    });
}

const handleSwitchToDefaultChannel = (teamId) => {
    store.dispatch(selectDefaultChannel(teamId));
};

const launchSelectServer = () => {
    Navigation.startSingleScreenApp({
        screen: {
            screen: 'SelectServer',
            navigatorStyle: {
                navBarHidden: true,
                statusBarHidden: false,
                statusBarHideWithNavBar: false,
                screenBackgroundColor: 'transparent',
            },
        },
        passProps: {
            allowOtherServers: app.allowOtherServers,
        },
        appStyle: {
            orientation: 'auto',
        },
        animationType: 'fade',
    });
};

const launchChannel = (skipMetrics = false) => {
    Navigation.startSingleScreenApp({
        screen: {
            screen: 'Channel',
            navigatorStyle: {
                navBarHidden: true,
                statusBarHidden: false,
                statusBarHideWithNavBar: false,
                screenBackgroundColor: 'transparent',
            },
        },
        passProps: {
            skipMetrics,
        },
        appStyle: {
            orientation: 'auto',
        },
        animationType: 'fade',
    });
};

const handleAppStateChange = (appState) => {
    const isActive = appState === 'active';
    const isBackground = appState === 'background';

    store.dispatch(setAppState(isActive));

    if (isActive && app.previousAppState === 'background') {
        handleAppActive();
    } else if (isBackground) {
        handleAppInActive();
    }

    app.previousAppState = appState;
};

const handleAppActive = async () => {
    // This handles when the app was started in the background
    // cause of an iOS push notification reply
    if (Platform.OS === 'ios' && app.shouldRelaunchWhenActive) {
        app.launchApp();
        app.setShouldRelaunchWhenActive(false);
    }

    // if the app is being controlled by an EMM provider
    if (app.emmEnabled) {
        const config = await mattermostManaged.getConfig();
        const authNeeded = config.inAppPinCode === 'true';
        const authExpired = (Date.now() - app.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER;

        // Once the app becomes active we check if the device needs to have a passcode set
        if (authNeeded) {
            const prompt = app.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode
            await handleAuthentication(config.vendor, prompt);
        }
    }

    app.setInBackgroundSince(null);
};

const handleAppInActive = () => {
    const {dispatch, getState} = store;
    const theme = getTheme(getState());

    // When the app is sent to the background we set the time when that happens
    // and perform a data clean up to improve on performance
    app.setInBackgroundSince(Date.now());
    app.setStartupThemes(
        theme.sidebarHeaderBg,
        theme.sidebarHeaderTextColor,
        theme.centerChannelBg,
    );
    dispatch(startDataCleanup());
};

AppState.addEventListener('change', handleAppStateChange);

const launchEntry = () => {
    telemetry.start([
        'start:select_server_screen',
        'start:channel_screen',
    ]);

    Navigation.startSingleScreenApp({
        screen: {
            screen: 'Entry',
            navigatorStyle: {
                navBarHidden: true,
                statusBarHidden: false,
                statusBarHideWithNavBar: false,
            },
        },
        passProps: {
            initializeModules,
        },
        appStyle: {
            orientation: 'auto',
        },
        animationType: 'fade',
    });

    telemetry.startSinceLaunch(['start:splash_screen']);
};

configurePushNotifications();
const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened;

if (startedSharedExtension) {
    // Hold on launching Entry screen
    app.setAppStarted(true);
}

if (!app.appStarted) {
    launchEntry();
}

new NativeEventsReceiver().appLaunched(() => {
    if (startedSharedExtension) {
        app.setAppStarted(false);
        launchEntry();
    } else if (app.token && app.url) {
        launchChannel(true);
    } else {
        launchSelectServer();
    }
});
