import { all, call, delay, put, select, take, takeLatest } from 'redux-saga/effects';

import * as types from '../actions/actionsTypes';
import { appInit, appStart } from '../actions/app';
import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks';
import { loginRequest } from '../actions/login';
import { selectServerRequest, serverInitAdd } from '../actions/server';
import { RootEnum } from '../definitions';
import { CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
import database from '../lib/database';
import { canOpenRoom, getServerInfo } from '../lib/methods';
import { getUidDirectMessage } from '../lib/methods/helpers';
import EventEmitter from '../lib/methods/helpers/events';
import { goRoom, navigateToRoom } from '../lib/methods/helpers/goRoom';
import { localAuthenticate } from '../lib/methods/helpers/localAuthentication';
import log from '../lib/methods/helpers/log';
import UserPreferences from '../lib/methods/userPreferences';
import { videoConfJoin } from '../lib/methods/videoConf';
import { Services } from '../lib/services';

const roomTypes = {
	channel: 'c',
	direct: 'd',
	group: 'p',
	channels: 'l'
};

const handleInviteLink = function* handleInviteLink({ params, requireLogin = false }) {
	if (params.path && params.path.startsWith('invite/')) {
		const token = params.path.replace('invite/', '');
		if (requireLogin) {
			yield put(inviteLinksSetToken(token));
		} else {
			yield put(inviteLinksRequest(token));
		}
	}
};

const navigate = function* navigate({ params }) {
	yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
	if (params.path || params.rid) {
		let type;
		let name;
		let jumpToThreadId;
		if (params.path) {
			// Following this pattern: {channelType}/{channelName}/thread/{threadId}
			[type, name, , jumpToThreadId] = params.path.split('/');
		}
		if (type !== 'invite' || params.rid) {
			const room = yield canOpenRoom(params);
			if (room) {
				const item = {
					name,
					t: roomTypes[type],
					roomUserId: getUidDirectMessage(room),
					...room
				};

				const isMasterDetail = yield select(state => state.app.isMasterDetail);
				const jumpToMessageId = params.messageId;

				yield goRoom({ item, isMasterDetail, jumpToMessageId, jumpToThreadId, popToRoot: true });
			}
		} else {
			yield handleInviteLink({ params });
		}
	}
};

const fallbackNavigation = function* fallbackNavigation() {
	const currentRoot = yield select(state => state.app.root);
	if (currentRoot) {
		return;
	}
	yield put(appInit());
};

const handleOAuth = function* handleOAuth({ params }) {
	const { credentialToken, credentialSecret } = params;
	try {
		yield Services.loginOAuthOrSso({ oauth: { credentialToken, credentialSecret } }, false);
	} catch (e) {
		log(e);
	}
};

const handleOpen = function* handleOpen({ params }) {
	const serversDB = database.servers;
	const serversCollection = serversDB.get('servers');

	let { host } = params;

	if (params.type === 'oauth') {
		yield handleOAuth({ params });
		return;
	}

	// If there's no host on the deep link params and the app is opened, just call appInit()
	if (!host) {
		yield fallbackNavigation();
		return;
	}

	// If there's host, continue
	if (!/^(http|https)/.test(host)) {
		if (/^localhost(:\d+)?/.test(host)) {
			host = `http://${host}`;
		} else {
			host = `https://${host}`;
		}
	} else {
		// Notification should always come from https
		host = host.replace('http://', 'https://');
	}
	// remove last "/" from host
	if (host.slice(-1) === '/') {
		host = host.slice(0, host.length - 1);
	}

	const [server, user] = yield all([
		UserPreferences.getString(CURRENT_SERVER),
		UserPreferences.getString(`${TOKEN_KEY}-${host}`)
	]);

	// TODO: needs better test
	// if deep link is from same server
	if (server === host && user) {
		const connected = yield select(state => state.server.connected);
		if (!connected) {
			yield localAuthenticate(host);
			yield put(selectServerRequest(host));
			yield take(types.LOGIN.SUCCESS);
		}
		yield navigate({ params });
	} else {
		// search if deep link's server already exists
		try {
			const hostServerRecord = yield serversCollection.find(host);
			if (hostServerRecord && user) {
				yield localAuthenticate(host);
				yield put(selectServerRequest(host, hostServerRecord.version, true, true));
				yield take(types.LOGIN.SUCCESS);
				yield navigate({ params });
				return;
			}
		} catch (e) {
			// do nothing?
		}
		// if deep link is from a different server
		const result = yield getServerInfo(host);
		if (!result.success) {
			// Fallback to prevent the app from being stuck on splash screen
			yield fallbackNavigation();
			return;
		}
		yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
		yield put(serverInitAdd(server));
		yield delay(1000);
		EventEmitter.emit('NewServer', { server: host });

		if (params.token) {
			yield take(types.SERVER.SELECT_SUCCESS);
			yield put(loginRequest({ resume: params.token }, true));
			yield take(types.LOGIN.SUCCESS);
			yield navigate({ params });
		} else {
			yield handleInviteLink({ params, requireLogin: true });
		}
	}
};

const handleNavigateCallRoom = function* handleNavigateCallRoom({ params }) {
	yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
	const db = database.active;
	const subsCollection = db.get('subscriptions');
	const room = yield subsCollection.find(params.rid);
	if (room) {
		const isMasterDetail = yield select(state => state.app.isMasterDetail);
		yield navigateToRoom({ item: room, isMasterDetail, popToRoot: true });
		const uid = params.caller._id;
		const { rid, callId, event } = params;
		if (event === 'accept') {
			yield call(Services.notifyUser, `${uid}/video-conference`, {
				action: 'accepted',
				params: { uid, rid, callId }
			});
			yield videoConfJoin(callId, true, false, true);
		} else if (event === 'decline') {
			yield call(Services.notifyUser, `${uid}/video-conference`, {
				action: 'rejected',
				params: { uid, rid, callId }
			});
		}
	}
};

const handleClickCallPush = function* handleOpen({ params }) {
	const serversDB = database.servers;
	const serversCollection = serversDB.get('servers');

	let { host } = params;

	if (host.slice(-1) === '/') {
		host = host.slice(0, host.length - 1);
	}

	const [server, user] = yield all([
		UserPreferences.getString(CURRENT_SERVER),
		UserPreferences.getString(`${TOKEN_KEY}-${host}`)
	]);

	if (server === host && user) {
		const connected = yield select(state => state.server.connected);
		if (!connected) {
			yield localAuthenticate(host);
			yield put(selectServerRequest(host));
			yield take(types.LOGIN.SUCCESS);
		}
		yield handleNavigateCallRoom({ params });
	} else {
		// search if deep link's server already exists
		try {
			const hostServerRecord = yield serversCollection.find(host);
			if (hostServerRecord && user) {
				yield localAuthenticate(host);
				yield put(selectServerRequest(host, hostServerRecord.version, true, true));
				yield take(types.LOGIN.SUCCESS);
				yield handleNavigateCallRoom({ params });
				return;
			}
		} catch (e) {
			// do nothing?
		}
		// if deep link is from a different server
		const result = yield Services.getServerInfo(host);
		if (!result.success) {
			// Fallback to prevent the app from being stuck on splash screen
			yield fallbackNavigation();
			return;
		}
		yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
		yield put(serverInitAdd(server));
		yield delay(1000);
		EventEmitter.emit('NewServer', { server: host });
		if (params.token) {
			yield take(types.SERVER.SELECT_SUCCESS);
			yield put(loginRequest({ resume: params.token }, true));
			yield take(types.LOGIN.SUCCESS);
			yield handleNavigateCallRoom({ params });
		}
	}
};

const root = function* root() {
	yield takeLatest(types.DEEP_LINKING.OPEN, handleOpen);
	yield takeLatest(types.DEEP_LINKING.OPEN_VIDEO_CONF, handleClickCallPush);
};
export default root;
