import React from 'react';
import {
	View, StyleSheet, Text, Animated, Easing, Image
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import * as AppleAuthentication from 'expo-apple-authentication';

import { withTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import Button from './Button';
import OrSeparator from './OrSeparator';
import Touch from '../utils/touch';
import I18n from '../i18n';
import random from '../utils/random';
import { logEvent, events } from '../utils/log';
import RocketChat from '../lib/rocketchat';

const BUTTON_HEIGHT = 48;
const SERVICE_HEIGHT = 58;
const BORDER_RADIUS = 2;
const SERVICES_COLLAPSED_HEIGHT = 174;

const styles = StyleSheet.create({
	serviceButton: {
		borderRadius: BORDER_RADIUS,
		marginBottom: 10
	},
	serviceButtonContainer: {
		borderRadius: BORDER_RADIUS,
		width: '100%',
		height: BUTTON_HEIGHT,
		flexDirection: 'row',
		alignItems: 'center',
		justifyContent: 'center',
		paddingHorizontal: 15
	},
	serviceIcon: {
		position: 'absolute',
		left: 15,
		top: 12,
		width: 24,
		height: 24
	},
	serviceText: {
		...sharedStyles.textRegular,
		fontSize: 16
	},
	serviceName: {
		...sharedStyles.textSemibold
	},
	options: {
		marginBottom: 0
	}
});

class LoginServices extends React.PureComponent {
	static propTypes = {
		navigation: PropTypes.object,
		server: PropTypes.string,
		services: PropTypes.object,
		Gitlab_URL: PropTypes.string,
		CAS_enabled: PropTypes.bool,
		CAS_login_url: PropTypes.string,
		separator: PropTypes.bool,
		theme: PropTypes.string
	}

	static defaultProps = {
		separator: true
	}

	state = {
		collapsed: true,
		servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
	}

	onPressFacebook = () => {
		logEvent(events.LOGIN_WITH_FACEBOOK);
		const { services, server } = this.props;
		const { clientId } = services.facebook;
		const endpoint = 'https://m.facebook.com/v2.9/dialog/oauth';
		const redirect_uri = `${ server }/_oauth/facebook?close`;
		const scope = 'email';
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&display=touch`;
		this.openOAuth({ url: `${ endpoint }${ params }` });
	}

	onPressGithub = () => {
		logEvent(events.LOGIN_WITH_GITHUB);
		const { services, server } = this.props;
		const { clientId } = services.github;
		const endpoint = `https://github.com/login?client_id=${ clientId }&return_to=${ encodeURIComponent('/login/oauth/authorize') }`;
		const redirect_uri = `${ server }/_oauth/github?close`;
		const scope = 'user:email';
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }`;
		this.openOAuth({ url: `${ endpoint }${ encodeURIComponent(params) }` });
	}

	onPressGitlab = () => {
		logEvent(events.LOGIN_WITH_GITLAB);
		const { services, server, Gitlab_URL } = this.props;
		const { clientId } = services.gitlab;
		const baseURL = Gitlab_URL ? Gitlab_URL.trim().replace(/\/*$/, '') : 'https://gitlab.com';
		const endpoint = `${ baseURL }/oauth/authorize`;
		const redirect_uri = `${ server }/_oauth/gitlab?close`;
		const scope = 'read_user';
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
		this.openOAuth({ url: `${ endpoint }${ params }` });
	}

	onPressGoogle = () => {
		logEvent(events.LOGIN_WITH_GOOGLE);
		const { services, server } = this.props;
		const { clientId } = services.google;
		const endpoint = 'https://accounts.google.com/o/oauth2/auth';
		const redirect_uri = `${ server }/_oauth/google?close`;
		const scope = 'email';
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
		this.openOAuth({ url: `${ endpoint }${ params }` });
	}

	onPressLinkedin = () => {
		logEvent(events.LOGIN_WITH_LINKEDIN);
		const { services, server } = this.props;
		const { clientId } = services.linkedin;
		const endpoint = 'https://www.linkedin.com/oauth/v2/authorization';
		const redirect_uri = `${ server }/_oauth/linkedin?close`;
		const scope = 'r_liteprofile,r_emailaddress';
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
		this.openOAuth({ url: `${ endpoint }${ params }` });
	}

	onPressMeteor = () => {
		logEvent(events.LOGIN_WITH_METEOR);
		const { services, server } = this.props;
		const { clientId } = services['meteor-developer'];
		const endpoint = 'https://www.meteor.com/oauth2/authorize';
		const redirect_uri = `${ server }/_oauth/meteor-developer`;
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&state=${ state }&response_type=code`;
		this.openOAuth({ url: `${ endpoint }${ params }` });
	}

	onPressTwitter = () => {
		logEvent(events.LOGIN_WITH_TWITTER);
		const { server } = this.props;
		const state = this.getOAuthState();
		const url = `${ server }/_oauth/twitter/?requestTokenAndRedirect=true&state=${ state }`;
		this.openOAuth({ url });
	}

	onPressWordpress = () => {
		logEvent(events.LOGIN_WITH_WORDPRESS);
		const { services, server } = this.props;
		const { clientId, serverURL } = services.wordpress;
		const endpoint = `${ serverURL }/oauth/authorize`;
		const redirect_uri = `${ server }/_oauth/wordpress?close`;
		const scope = 'openid';
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
		this.openOAuth({ url: `${ endpoint }${ params }` });
	}

	onPressCustomOAuth = (loginService) => {
		logEvent(events.LOGIN_WITH_CUSTOM_OAUTH);
		const { server } = this.props;
		const {
			serverURL, authorizePath, clientId, scope, service
		} = loginService;
		const redirectUri = `${ server }/_oauth/${ service }`;
		const state = this.getOAuthState();
		const params = `?client_id=${ clientId }&redirect_uri=${ redirectUri }&response_type=code&state=${ state }&scope=${ scope }`;
		const domain = `${ serverURL }`;
		const absolutePath = `${ authorizePath }${ params }`;
		const url = absolutePath.includes(domain) ? absolutePath : domain + absolutePath;
		this.openOAuth({ url });
	}

	onPressSaml = (loginService) => {
		logEvent(events.LOGIN_WITH_SAML);
		const { server } = this.props;
		const {	clientConfig } = loginService;
		const {	provider } = clientConfig;
		const ssoToken = random(17);
		const url = `${ server }/_saml/authorize/${ provider }/${ ssoToken }`;
		this.openOAuth({ url, ssoToken, authType: 'saml' });
	}

	onPressCas = () => {
		logEvent(events.LOGIN_WITH_CAS);
		const { server, CAS_login_url } = this.props;
		const ssoToken = random(17);
		const url = `${ CAS_login_url }?service=${ server }/_cas/${ ssoToken }`;
		this.openOAuth({ url, ssoToken, authType: 'cas' });
	}

	onPressAppleLogin = async() => {
		try {
			const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
				requestedScopes: [
					AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
					AppleAuthentication.AppleAuthenticationScope.EMAIL
				]
			});

			await RocketChat.loginOAuthOrSso({ fullName, email, identityToken });
		} catch {
			// Do nothing
		}
	}

	getOAuthState = () => {
		const credentialToken = random(43);
		return Base64.encodeURI(JSON.stringify({ loginStyle: 'popup', credentialToken, isCordova: true }));
	}

	openOAuth = ({ url, ssoToken, authType = 'oauth' }) => {
		const { navigation } = this.props;
		navigation.navigate('AuthenticationWebView', { url, authType, ssoToken });
	}

	transitionServicesTo = (height) => {
		const { servicesHeight } = this.state;
		if (this._animation) {
			this._animation.stop();
		}
		this._animation = Animated.timing(servicesHeight, {
			toValue: height,
			duration: 300,
			easing: Easing.easeOutCubic
		}).start();
	}

	toggleServices = () => {
		const { collapsed } = this.state;
		const { services } = this.props;
		const { length } = Object.values(services);
		if (collapsed) {
			this.transitionServicesTo(SERVICE_HEIGHT * length);
		} else {
			this.transitionServicesTo(SERVICES_COLLAPSED_HEIGHT);
		}
		this.setState(prevState => ({ collapsed: !prevState.collapsed }));
	}

	getSocialOauthProvider = (name) => {
		const oauthProviders = {
			facebook: this.onPressFacebook,
			github: this.onPressGithub,
			gitlab: this.onPressGitlab,
			google: this.onPressGoogle,
			linkedin: this.onPressLinkedin,
			'meteor-developer': this.onPressMeteor,
			twitter: this.onPressTwitter,
			wordpress: this.onPressWordpress
		};
		return oauthProviders[name];
	}

	renderServicesSeparator = () => {
		const { collapsed } = this.state;
		const { services, separator, theme } = this.props;
		const { length } = Object.values(services);

		if (length > 3 && separator) {
			return (
				<>
					<Button
						title={collapsed ? I18n.t('Onboarding_more_options') : I18n.t('Onboarding_less_options')}
						type='secondary'
						onPress={this.toggleServices}
						theme={theme}
						style={styles.options}
						color={themes[theme].actionTintColor}
					/>
					<OrSeparator theme={theme} />
				</>
			);
		}
		if (length > 0 && separator) {
			return <OrSeparator theme={theme} />;
		}
		return null;
	}

	renderItem = (service) => {
		const { CAS_enabled, theme } = this.props;
		let { name } = service;
		name = name === 'meteor-developer' ? 'meteor' : name;
		const icon = `icon_${ name }`;
		const isSaml = service.service === 'saml';
		let onPress = () => {};

		switch (service.authType) {
			case 'oauth': {
				onPress = this.getSocialOauthProvider(service.name);
				break;
			}
			case 'oauth_custom': {
				onPress = () => this.onPressCustomOAuth(service);
				break;
			}
			case 'saml': {
				onPress = () => this.onPressSaml(service);
				break;
			}
			case 'cas': {
				onPress = () => this.onPressCas();
				break;
			}
			case 'apple': {
				onPress = () => this.onPressAppleLogin();
				break;
			}
			default:
				break;
		}

		if (name === 'apple') {
			return (
				<AppleAuthentication.AppleAuthenticationButton
					buttonType={AppleAuthentication.AppleAuthenticationButtonType.CONTINUE}
					buttonStyle={theme === 'light' ? AppleAuthentication.AppleAuthenticationButtonStyle.BLACK : AppleAuthentication.AppleAuthenticationButtonStyle.WHITE}
					cornerRadius={BORDER_RADIUS}
					style={[styles.serviceButton, { height: BUTTON_HEIGHT }]}
					onPress={onPress}
				/>
			);
		}

		name = name.charAt(0).toUpperCase() + name.slice(1);
		let buttonText;
		if (isSaml || (service.service === 'cas' && CAS_enabled)) {
			buttonText = <Text style={[styles.serviceName, isSaml && { color: service.buttonLabelColor }]}>{name}</Text>;
		} else {
			buttonText = (
				<>
					{I18n.t('Continue_with')} <Text style={styles.serviceName}>{name}</Text>
				</>
			);
		}

		const backgroundColor = isSaml && service.buttonColor ? service.buttonColor : themes[theme].chatComponentBackground;

		return (
			<Touch
				key={service.name}
				onPress={onPress}
				style={[styles.serviceButton, { backgroundColor }]}
				theme={theme}
				activeOpacity={0.5}
				underlayColor={themes[theme].buttonText}
			>
				<View style={styles.serviceButtonContainer}>
					{service.authType === 'oauth' ? <Image source={{ uri: icon }} style={styles.serviceIcon} /> : null}
					<Text style={[styles.serviceText, { color: themes[theme].titleText }]}>{buttonText}</Text>
				</View>
			</Touch>
		);
	}

	render() {
		const { servicesHeight } = this.state;
		const { services, separator } = this.props;
		const { length } = Object.values(services);
		const style = {
			overflow: 'hidden',
			height: servicesHeight
		};

		if (length > 3 && separator) {
			return (
				<>
					<Animated.View style={style}>
						{Object.values(services).map(service => this.renderItem(service))}
					</Animated.View>
					{this.renderServicesSeparator()}
				</>
			);
		}
		return (
			<>
				{Object.values(services).map(service => this.renderItem(service))}
				{this.renderServicesSeparator()}
			</>
		);
	}
}

const mapStateToProps = state => ({
	server: state.server.server,
	Gitlab_URL: state.settings.API_Gitlab_URL,
	CAS_enabled: state.settings.CAS_enabled,
	CAS_login_url: state.settings.CAS_login_url,
	services: state.login.services
});

export default connect(mapStateToProps)(withTheme(LoginServices));