import React from 'react';
import { Animated, Easing, Linking, StyleSheet, Text, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import * as AppleAuthentication from 'expo-apple-authentication';
import { StackNavigationProp } from '@react-navigation/stack';

import { TSupportedThemes, withTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../lib/constants';
import Button from './Button';
import OrSeparator from './OrSeparator';
import Touch from '../lib/methods/helpers/touch';
import I18n from '../i18n';
import { random } from '../lib/methods/helpers';
import { events, logEvent } from '../lib/methods/helpers/log';
import { CustomIcon, TIconsName } from './CustomIcon';
import { IServices } from '../selectors/login';
import { OutsideParamList } from '../stacks/types';
import { IApplicationState } from '../definitions';
import { Services } from '../lib/services';

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

const LOGIN_STYPE_POPUP = 'popup';
const LOGIN_STYPE_REDIRECT = 'redirect';

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
	}
});

interface IOpenOAuth {
	url: string;
	ssoToken?: string;
	authType?: string;
}

interface IItemService {
	name: string;
	service: string;
	authType: string;
	buttonColor: string;
	buttonLabelColor: string;
	clientConfig: { provider: string };
	serverURL: string;
	authorizePath: string;
	clientId: string;
	scope: string;
}

interface IOauthProvider {
	[key: string]: () => void;
	facebook: () => void;
	github: () => void;
	gitlab: () => void;
	google: () => void;
	linkedin: () => void;
	'meteor-developer': () => void;
	twitter: () => void;
	wordpress: () => void;
}

interface ILoginServicesProps {
	navigation: StackNavigationProp<OutsideParamList>;
	server: string;
	services: IServices;
	Gitlab_URL: string;
	CAS_enabled: boolean;
	CAS_login_url: string;
	separator: boolean;
	theme: TSupportedThemes;
}

interface ILoginServicesState {
	collapsed: boolean;
	servicesHeight: Animated.Value;
}

class LoginServices extends React.PureComponent<ILoginServicesProps, ILoginServicesState> {
	private _animation?: Animated.CompositeAnimation | void;

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

	onPressFacebook = () => {
		logEvent(events.ENTER_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.ENTER_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.ENTER_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.ENTER_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(LOGIN_STYPE_REDIRECT);
		const params = `?client_id=${clientId}&redirect_uri=${redirect_uri}&scope=${scope}&state=${state}&response_type=code`;
		Linking.openURL(`${endpoint}${params}`);
	};

	onPressLinkedin = () => {
		logEvent(events.ENTER_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.ENTER_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.ENTER_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.ENTER_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: IItemService) => {
		logEvent(events.ENTER_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: IItemService) => {
		logEvent(events.ENTER_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.ENTER_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 () => {
		logEvent(events.ENTER_WITH_APPLE);
		try {
			const { fullName, email, identityToken } = await AppleAuthentication.signInAsync({
				requestedScopes: [
					AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
					AppleAuthentication.AppleAuthenticationScope.EMAIL
				]
			});
			await Services.loginOAuthOrSso({ fullName, email, identityToken });
		} catch {
			logEvent(events.ENTER_WITH_APPLE_F);
		}
	};

	getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
		const credentialToken = random(43);
		let obj: {
			loginStyle: string;
			credentialToken: string;
			isCordova: boolean;
			redirectUrl?: string;
		} = { loginStyle, credentialToken, isCordova: true };
		if (loginStyle === LOGIN_STYPE_REDIRECT) {
			obj = {
				...obj,
				redirectUrl: 'rocketchat://auth'
			};
		}
		return Base64.encodeURI(JSON.stringify(obj));
	};

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

	transitionServicesTo = (height: number) => {
		const { servicesHeight } = this.state;
		if (this._animation) {
			this._animation.stop();
		}
		this._animation = Animated.timing(servicesHeight, {
			toValue: height,
			duration: 300,
			easing: Easing.inOut(Easing.quad),
			useNativeDriver: false
		}).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: ILoginServicesState) => ({ collapsed: !prevState.collapsed }));
	};

	getSocialOauthProvider = (name: string) => {
		const oauthProviders: IOauthProvider = {
			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}
						style={styles.options}
						color={themes[theme].actionTintColor}
					/>
					<OrSeparator theme={theme} />
				</>
			);
		}
		if (length > 0 && separator) {
			return <OrSeparator theme={theme} />;
		}
		return null;
	};

	renderItem = (service: IItemService) => {
		const { CAS_enabled, theme } = this.props;
		let { name } = service;
		name = name === 'meteor-developer' ? 'meteor' : name;
		const icon = `${name}-monochromatic` as TIconsName;
		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;
		}

		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' || service.authType === 'apple' ? (
						<CustomIcon name={icon} size={24} color={themes[theme].titleText} 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: Animated.AnimatedProps<ViewStyle> = {
			overflow: 'hidden',
			height: servicesHeight
		};

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

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

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