import React from 'react';
import {
    withRouter,
    Switch,
    Route
} from 'react-router-dom';
import Loadable from "react-loadable";
import {withContextConsumer} from "@formanta/react";
import {withNamespaces} from 'react-i18next';
import {MdBlock} from 'react-icons/md';
import {isNumber, isUndefined} from "@formanta/js";
import {withAuth} from "../feature/Auth";
import {ApiRequest, POST} from "../lib/ApiRequest";
import Loading from "../component/Loading";
import {withBranding} from "../component/Branding";
import {withTheme} from "../lib/theme";
import {withBackend} from "../feature/BackendConnect";
import {withApiControl} from "../component/ApiControl";
import {i18n} from "../lib/i18n";
import Header from "../layout/Header";
import Footer from '../layout/Footer';
import {withFeatureManager} from "../component/FeatureManager";

const LoginForm = Loadable({
    loader: () => import('../component/User/LoginForm'),
    loading: (props) => <Loading {...props} name='LoginForm'/>,
});

/**
 * Defines the basic requesting source, the backend could thus deliver ACL knowledge directly after logging in and not only when checking permissions
 * @type {string}
 */
const authSource = 'flood-admin';

const AuthPermissionContext = React.createContext({});

class AuthPermissionProviderBase extends React.Component {

    constructor(props) {
        super(props);
        const {isEnabled} = this.props;

        this.isAllowed = (request) => {
            if(!isEnabled('authPermission')) {
                return true;
            }

            let hook = false;
            if(this.props.hook_active) {
                hook = this.props.hook_active.id;
            }

            if(this.state.rules) {
                if('admin' === request && this.state.rules.admin) {
                    return true;
                } else if(hook) {
                    if(this.state.rules.hooks && this.state.rules.hooks[hook] && this.state.rules.hooks[hook].view && this.state.rules.hooks[hook].view.includes(request)) {
                        return true;
                    }
                } else if(this.state.rules.view && this.state.rules.view.includes(request)) {
                    return true;
                }
            }
            return false;
        };

        this.isAllowedHook = (hook_id) => {
            if(!isEnabled('authPermission')) {
                return true;
            }
            if(this.state.rules) {
                if(this.state.rules.hooks && this.state.rules.hooks[hook_id] && this.state.rules.hooks[hook_id].view) {
                    return true;
                }
            }
            return false;
        };

        this.state = {
            // check here to suppress multiple loads starting when loading a page and already logged in
            permissionsLoaded: this.props.loggedIn ? 'loading' : false,
            rules: {
                admin: false,
                view: false,
                hooks: {}
            },
            loginTimedOut: false,
            isAllowed: this.isAllowed,
            isAllowedHook: this.isAllowedHook,
            // performance optimising i18next impact
            t: this.props.t
        };
    }

    clearPermissions() {
        this.setState({
            permissionsLoaded: false,
            rules: {
                admin: false,
                view: false,
                hooks: {}
            },
            loginTimedOut: false,
            isAllowed: this.isAllowed,
            // performance optimising i18next impact
            t: this.props.t
        });
    }

    loadPermissions(requests = {},) {
        const {isEnabled} = this.props;
        if(!isEnabled('authPermission')) {
            return;
        }

        this.setState({permissionsLoaded: 'loading'});

        return (new ApiRequest('auth', POST, 'check-allowed', {
            authSource,
            requests
        }))
            .debug(true)
            .header({})
            .exec()
            .then((res) => {
                if(res && res.data) {
                    let rules = {};
                    if(res.data.rules) {
                        rules = res.data.rules;
                    }

                    this.setState({
                        rules,
                        permissionsLoaded: true,
                    });

                    return rules;
                } else {
                    this.setState({permissionsLoaded: 'error'});
                }
            }).catch((err) => {
                if(isNumber(err.status)) {
                    if(440 === err.status) {
                        this.setState({loginTimedOut: true});

                        // when on auth page, also logout
                        if(this.props.history.location.pathname === '/' + i18n.languages[0] + '/auth') {
                            if(this.props.logout) {
                                this.props.logout();
                            }
                        }
                    }
                }
            });
    }

    componentDidMount() {
        if(this.props.loggedIn) {
            this.loadPermissions();
        }
    }

    componentDidUpdate(prevProps, prevState) {
        if(prevProps.loggedIn !== this.props.loggedIn) {
            if(this.props.loggedIn) {
                // has just logged in
                this.loadPermissions();
            } else {
                // has just logged out
                this.clearPermissions();
            }
        }

        if(prevProps.loggedIn === this.props.loggedIn && this.props.loggedIn &&
            prevState.permissionsLoaded !== this.state.permissionsLoaded && true === this.state.permissionsLoaded) {
            const {isEnabled} = this.props;
            if(!isEnabled('authPermission') || !isEnabled('backendConnect')) {
                return;
            }
            // is logged in and permissions have been just loaded

            // check if active hook is allowed, and when not switch to an allowed hook
            let switchedHook = false;
            for(let hook in this.props.hooks) {
                if(this.props.hooks.hasOwnProperty(hook) && this.state.isAllowedHook(hook)) {
                    this.props.switchHook(hook);
                    switchedHook = true;
                    break;
                }
            }

            if(!switchedHook) {
                // todo: add display for failure
                console.error('misconfigured permissions, not one hook submitted is allowed for this user');
                this.props.switchHook(false);
            }
        }
    }

    render() {
        const {
            t, theme,
            branding,
            loggedIn,
            except,
            isEnabled
        } = this.props;

        let exception = false;
        if(!isUndefined(except)) {
            exception = except.join('|');
        }

        const enabled = isEnabled('authPermission');

        return (
            <AuthPermissionContext.Provider value={this.state}>
                {enabled ?
                    <Switch>
                        <Route exact path="/:lng" render={(props) => (
                            null
                        )}/>

                        {exception ?
                            <Route exact path={'/:lng/(' + exception + ')'} render={(props) => (
                                this.props.children
                            )}/>
                            : null}

                        <Route path="/:lng" render={(props) => (
                            this.state.loginTimedOut || !loggedIn ?

                                <React.Fragment>
                                    <Header/>
                                    <p style={{margin: '6px auto'}}>
                                        {this.state.loginTimedOut ? t('auth-permission.login-expired') : null}
                                        {!loggedIn ? t('auth-permission.login-needed') : null}
                                    </p>
                                    <LoginForm onLoggedIn={() => {
                                        this.setState({loginTimedOut: false});
                                        this.loadPermissions();
                                    }}/>
                                    <Footer/>
                                </React.Fragment> :

                                ('loading' === this.state.permissionsLoaded || 'error' === this.state.permissionsLoaded ?

                                    <Loading name='PermissionProvider' errorWarning={'error' === this.state.permissionsLoaded}>
                                        <div style={{textAlign: 'center'}}>
                                            {
                                                'dark' === theme.name && branding.logo.white ?
                                                    <branding.logo.white
                                                        style={{width: '64px', height: '64px',}}/> :
                                                    <branding.logo.normal
                                                        style={{width: '64px', height: '64px',}}/>
                                            }
                                            {'error' === this.state.connecting ?
                                                <p>{t('auth-permission.error')}</p> :
                                                <p>{t('auth-permission.loading')} ...</p>}
                                        </div>
                                    </Loading> :
                                    this.props.children)
                        )}/>
                    </Switch> :
                    this.props.children}
            </AuthPermissionContext.Provider>
        );
    }
}

const withAuthPermission = withContextConsumer(AuthPermissionContext.Consumer);

const AuthPermissionProvider = withNamespaces('common')(withFeatureManager(withRouter(withAuth(withBackend(withApiControl(withBranding(withTheme(AuthPermissionProviderBase))))))));

export const FAIL_DISPLAY = true;
export const FAIL_DISPLAY_SMALL = 'xs';
export const FAIL_HIDE = false;

const needsPermission = (requests, failDisplay = FAIL_HIDE) =>
    (Component) =>
        withFeatureManager((props) =>
            <AuthPermissionContext.Consumer>
                {(context) => {
                    const {
                        t,
                        isAllowed, permissionsLoaded,
                    } = context;
                    const {isEnabled} = props;

                    /*
                     * using .some to be able to "break" the foreach with returning `true`
                     */
                    let allowed = false;
                    if(true === permissionsLoaded) {
                        requests.some(req => {
                            if(isAllowed(req)) {
                                allowed = true;
                            } else {
                                allowed = false;
                                return true;
                            }
                            return false;
                        });
                    }

                    return (
                        true === permissionsLoaded || !isEnabled('authPermission') ?
                            (allowed || !isEnabled('authPermission') ?
                                <Component {...props}/> :
                                (failDisplay ?
                                    (FAIL_DISPLAY_SMALL === failDisplay ?
                                        <MdBlock/> :
                                        <p>{t('auth-permission.no-permission')}</p>) :
                                    null)) :
                            <div style={{textAlign: 'center'}}>
                                {'error' === permissionsLoaded ?
                                    <p>{t('auth-permission.error')}</p> :
                                    'loading' === permissionsLoaded ?
                                        <p>{t('auth-permission.loading')} ...</p> :
                                        <p>{t('auth-permission.login-needed')}</p>}
                            </div>
                    );
                }}
            </AuthPermissionContext.Consumer>);

export {AuthPermissionProvider, withAuthPermission, needsPermission};