// @flow

import Logger from 'jitsi-meet-logger';

import { openConnection } from '../../../connection';
import {
    openAuthDialog,
    openLoginDialog } from '../../../react/features/authentication/actions.web';
import { WaitForOwnerDialog } from '../../../react/features/authentication/components';
import {
    isTokenAuthEnabled,
    getTokenAuthUrl
} from '../../../react/features/authentication/functions';
import { getReplaceParticipant } from '../../../react/features/base/config/functions';
import { isDialogOpen } from '../../../react/features/base/dialog';
import { setJWT } from '../../../react/features/base/jwt';
import UIUtil from '../util/UIUtil';

import LoginDialog from './LoginDialog';


let externalAuthWindow;
declare var APP: Object;

const logger = Logger.getLogger(__filename);


/**
 * Authenticate using external service or just focus
 * external auth window if there is one already.
 *
 * @param {JitsiConference} room
 * @param {string} [lockPassword] password to use if the conference is locked
 */
function doExternalAuth(room, lockPassword) {
    const config = APP.store.getState()['features/base/config'];

    if (externalAuthWindow) {
        externalAuthWindow.focus();

        return;
    }

    if (room.isJoined()) {
        let getUrl;

        if (isTokenAuthEnabled(config)) {
            getUrl = Promise.resolve(getTokenAuthUrl(config)(room.getName(), true));
            initJWTTokenListener(room);
        } else {
            getUrl = room.getExternalAuthUrl(true);
        }
        getUrl.then(url => {
            externalAuthWindow = LoginDialog.showExternalAuthDialog(
                url,
                () => {
                    externalAuthWindow = null;
                    if (!isTokenAuthEnabled(config)) {
                        room.join(lockPassword);
                    }
                }
            );
        });
    } else if (isTokenAuthEnabled(config)) {
        redirectToTokenAuthService(room.getName());
    } else {
        room.getExternalAuthUrl().then(UIUtil.redirect);
    }
}

/**
 * Redirect the user to the token authentication service for the login to be
 * performed. Once complete it is expected that the service will bring the user
 * back with "?jwt={the JWT token}" query parameter added.
 * @param {string} [roomName] the name of the conference room.
 */
export function redirectToTokenAuthService(roomName: string) {
    const config = APP.store.getState()['features/base/config'];

    // FIXME: This method will not preserve the other URL params that were
    // originally passed.
    UIUtil.redirect(getTokenAuthUrl(config)(roomName, false));
}

/**
 * Initializes 'message' listener that will wait for a JWT token to be received
 * from the token authentication service opened in a popup window.
 * @param room the name of the conference room.
 */
function initJWTTokenListener(room) {
    /**
     *
     */
    function listener({ data, source }) {
        if (externalAuthWindow !== source) {
            logger.warn('Ignored message not coming '
                + 'from external authnetication window');

            return;
        }

        let jwt;

        if (data && (jwt = data.jwtToken)) {
            logger.info('Received JSON Web Token (JWT):', jwt);

            APP.store.dispatch(setJWT(jwt));

            const roomName = room.getName();

            openConnection({
                retry: false,
                roomName
            }).then(connection => {
                // Start new connection
                const newRoom = connection.initJitsiConference(
                    roomName, APP.conference._getConferenceOptions());

                // Authenticate from the new connection to get
                // the session-ID from the focus, which will then be used
                // to upgrade current connection's user role

                newRoom.room.moderator.authenticate()
                    .then(() => {
                        connection.disconnect();

                        // At this point we'll have session-ID stored in
                        // the settings. It will be used in the call below
                        // to upgrade user's role
                        room.room.moderator.authenticate()
                            .then(() => {
                                logger.info('User role upgrade done !');
                                // eslint-disable-line no-use-before-define
                                unregister();
                            })
                            .catch((err, errCode) => {
                                logger.error('Authentication failed: ',
                                    err, errCode);
                                unregister();
                            });
                    })
                    .catch((error, code) => {
                        unregister();
                        connection.disconnect();
                        logger.error(
                            'Authentication failed on the new connection',
                            error, code);
                    });
            }, err => {
                unregister();
                logger.error('Failed to open new connection', err);
            });
        }
    }

    /**
     *
     */
    function unregister() {
        window.removeEventListener('message', listener);
    }

    if (window.addEventListener) {
        window.addEventListener('message', listener, false);
    }
}

/**
 * Authenticate for the conference.
 * Uses external service for auth if conference supports that.
 * @param {JitsiConference} room
 * @param {string} [lockPassword] password to use if the conference is locked
 */
function authenticate(room: Object, lockPassword: string) {
    const config = APP.store.getState()['features/base/config'];

    if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
        doExternalAuth(room, lockPassword);
    } else {
        APP.store.dispatch(openLoginDialog());
    }
}

/**
 * Notify user that authentication is required to create the conference.
 * @param {JitsiConference} room
 * @param {string} [lockPassword] password to use if the conference is locked
 */
function requireAuth(room: Object, lockPassword: string) {
    if (!isDialogOpen(APP.store, WaitForOwnerDialog)) {
        return;
    }

    APP.store.dispatch(
        openAuthDialog(
        room.getName(), authenticate.bind(null, room, lockPassword))
    );
}

/**
 * De-authenticate local user.
 *
 * @param {JitsiConference} room
 * @param {string} [lockPassword] password to use if the conference is locked
 * @returns {Promise}
 */
function logout(room: Object) {
    return new Promise(resolve => {
        room.room.moderator.logout(resolve);
    }).then(url => {
        // de-authenticate conference on the fly
        if (room.isJoined()) {
            const replaceParticipant = getReplaceParticipant(APP.store.getState());

            room.join(null, replaceParticipant);
        }

        return url;
    });
}

export default {
    authenticate,
    logout,
    requireAuth
};
