import { AfterViewInit, Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { AuthProvider } from '@app/core/common/enum/auth-provider';
import { ConfigurableProvider } from '@app/core/model/configurable-provider/configurableProvider';
import { AuthService } from '@app/core/services/auth/auth.service';
import { ConfigurableProvidersService } from '@app/ui/auth/login/utilities/configurableProviders.service';
import { LoginService } from '@app/ui/auth/login/utilities/login.service';
import { BaseComponent } from '@common/base/base.component';
import { environment } from 'environments/environment';
import { takeUntil } from 'rxjs/operators';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { MergeLoginService } from './utilities/merge-login.service';
import { Oauth2DialogService } from '@app/ui/misc/oauth2-dialog/service/oauth2-dialog.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { FormControl } from '@angular/forms';
import { OrcidUser } from '@app/core/model/orcid/orcidUser';
import { ZenodoToken } from '@app/core/model/zenodo/zenodo-token.model';
import { LoginInfo } from '@app/core/model/auth/login-info';
import { MatomoService } from '@app/core/services/matomo/matomo-service';

/// <reference types="gapi" />
/// <reference types="facebook-js-sdk" />

declare const gapi: any;
declare const FB: any;

@Component({
	selector: 'app-login',
	templateUrl: './login.component.html',
	styleUrls: ['./login.component.scss']
})
export class LoginComponent extends BaseComponent implements OnInit, AfterViewInit {

	@Input() redirect: boolean = true;
	@Input() mergeUsers: boolean;

	public auth2: any;
	private returnUrl: string;
	private twitterUrl = new BehaviorSubject(undefined);
	private emailFormControl = new FormControl('');
	private orcidUser: OrcidUser;
	private zenodoToken: ZenodoToken;
	private accessToken: string;
	private oauthLock: boolean;
	//public cofigurableProviders: ConfigurableProvider[];

	constructor(
		private router: Router,
		private route: ActivatedRoute,
		private loginService: LoginService,
		private authService: AuthService,
		public configurableProviderService: ConfigurableProvidersService,
		private configurationService: ConfigurationService,
		private mergeLoginService: MergeLoginService,
		private oauth2DialogService: Oauth2DialogService,
		private httpClient: HttpClient,
		private matomoService: MatomoService,
	) { super(); }

	ngOnInit(): void {
		if(this.authService.current() && (this.redirect == true)){
			this.router.navigate(['home']);
		}
		this.matomoService.trackPageView('loginPage');
		this.route.queryParams
			.pipe(takeUntil(this._destroyed))
			.subscribe((params: Params) => {
				const returnUrl = params['returnUrl'];
				if (returnUrl) { this.returnUrl = returnUrl; }
			});
		this.authService.getConfigurableProviders()
			.pipe(takeUntil(this._destroyed))
			.subscribe((data: any) => {
				this.configurableProviderService.providers = data;
			})
	}

	ngAfterViewInit() {
		this.initProviders();
	}

	public linkedInLogin() {
		//this.router.navigate(['/login/linkedin']);
		this.oauth2DialogService.login(this.getLinkedInUrl()).pipe(takeUntil(this._destroyed))
		.subscribe(result => {
			if (result !== undefined) {
				if (!this.oauthLock) {
					this.linkedInLoginUser(result.oauthCode, result.oauthState);
					this.oauthLock = true;
				}
			} else {
				this.oauthLock = false;
			}
		});
	}

	public twitterLogin() {
		// this.router.navigate(['/login/twitter']);
		this.twitterUrl.asObservable().pipe(takeUntil(this._destroyed)).subscribe(url => {
			if (url !== undefined) {
				this.oauth2DialogService.login(url).pipe(takeUntil(this._destroyed))
				.subscribe(result => {
					if (result !== undefined) {
						if (!this.oauthLock) {
							this.twitterLoginUser(result.oauthToken, result.oauthVerifier);
							this.oauthLock = true;
						}
					} else {
						this.oauthLock = false;
					}
				});
			}
		});
		this.twitterAuthorize();
	}

	public b2AccessLogin() {
		//this.router.navigate(['/login/external/b2access'], {queryParams: {returnUrl: this.returnUrl, mergeUsers: this.mergeUsers}});
		this.oauth2DialogService.login(this.getB2AccessUrl()).pipe(takeUntil(this._destroyed))
		.subscribe(result => {
			if (result !== undefined) {
				if (!this.oauthLock) {
					this.b2AccessLoginUser(result.oauthCode);
					this.oauthLock = true;
				}
			} else {
				this.oauthLock = false;
			}
		});
	}

	public orcidLogin() {
		//this.router.navigate(['/login/external/orcid']);
		this.oauth2DialogService.login(this.getORCIDUrl()).pipe(takeUntil(this._destroyed))
		.subscribe(result => {
			if (result !== undefined) {
				if (!this.oauthLock) {
					this.orcidLoginUser(result.oauthCode);
					this.oauthLock = true;
				}
			} else {
				this.oauthLock = false;
			}
		});
	}

	public openaireLogin() {
		//this.router.navigate(['/login/openaire']);
		this.oauth2DialogService.login(this.getOpenAireUrl()).pipe(takeUntil(this._destroyed))
		.subscribe(result => {
			if (result !== undefined) {
				if (!this.oauthLock) {
					this.openaireLoginUser(result.oauthCode, result.oauthState);
					this.oauthLock = true;
				}
			} else {
				this.oauthLock = false;
			}
		});
	}

	public configurableLogin(provider: ConfigurableProvider) {
		this.router.navigate(['/login/configurable/' + provider.configurableLoginId])
	}

	public zenodoLogin() {
		//this.router.navigate(['/login/external/zenodo']);
		this.oauth2DialogService.login(this.getZenodoUrl()).pipe(takeUntil(this._destroyed))
		.subscribe(result => {
			if (result !== undefined) {
				if (!this.oauthLock) {
					this.zenodoLoginUser(result.oauthCode);
					this.oauthLock = true;
				}
			} else {
				this.oauthLock = false;
			}
		});
	}

	public hasFacebookOauth(): boolean {
		return this.hasProvider(AuthProvider.Facebook);
	}

	public hasLinkedInOauth(): boolean {
		return this.hasProvider(AuthProvider.LinkedIn);
	}

	public hasTwitterOauth(): boolean {
		return this.hasProvider(AuthProvider.Twitter);
	}

	public hasGoogleOauth(): boolean {
		return this.hasProvider(AuthProvider.Google);
	}

	public hasB2AccessOauth(): boolean {
		return this.hasProvider(AuthProvider.B2Access);
	}

	public hasOrcidOauth(): boolean {
		return this.hasProvider(AuthProvider.ORCID);
	}

	public hasOpenAireOauth(): boolean {
		return this.hasProvider(AuthProvider.OpenAire);
	}

	public hasZenodoOauth(): boolean {
		return this.hasProvider(AuthProvider.Zenodo);
	}

	public initProviders() {
		if (this.hasProvider(AuthProvider.Google)) { this.initializeGoogleOauth(); }
		if (this.hasProvider(AuthProvider.Facebook)) { this.initializeFacebookOauth(); }
	}

	public hasProvider(provider: AuthProvider) {
		for (let i = 0; i < this.configurationService.loginProviders.enabled.length; i++) {
			if (provider === this.configurationService.loginProviders.enabled[i]) { return this.isProviderProperlyConfigured(provider); }
		}
		return false;
	}

	private isProviderProperlyConfigured(provider: AuthProvider) {
		switch (provider) {
			case AuthProvider.Facebook: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.facebookConfiguration);
			case AuthProvider.Google: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.googleConfiguration);
			case AuthProvider.LinkedIn: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.linkedInConfiguration);
			case AuthProvider.Twitter: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.twitterConfiguration);
			case AuthProvider.B2Access: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.b2accessConfiguration);
			case AuthProvider.ORCID: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.orcidConfiguration);
			case AuthProvider.OpenAire: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.openAireConfiguration);
			case AuthProvider.Zenodo: return this.hasAllRequiredFieldsConfigured(this.configurationService.loginProviders.zenodoConfiguration);
			default: throw new Error('Unsupported Provider Type');
		}
	}

	private hasAllRequiredFieldsConfigured(configuration: any) {
		if (configuration != null && configuration.clientId != null) { return true; }
		return false;
	}

	/*
	* GOOGLE SIGN IN
	*/
	private initializeGoogleOauth(): void {
		gapi.load('auth2', () => {
			this.auth2 = gapi.auth2.init({
				client_id: this.configurationService.loginProviders.googleConfiguration.clientId,
				scope: 'profile email'
			});
			this.attachGoogleSignΙn(document.getElementById('googleSignInButton'));
		});
	}

	public attachGoogleSignΙn(element) {
		if (!element) { return; }
		this.auth2.attachClickHandler(element, {},
			(googleUser) => {
				const id_token = googleUser.getAuthResponse().id_token;
				if (id_token) {
					this.authLogin({ ticket: id_token, provider: AuthProvider.Google });
				}
			}, (error) => {
			});
	}

	/*
	* FACEBOOK SIGN IN
	*/
	private initializeFacebookOauth(): void {
		FB.init({
			appId: this.configurationService.loginProviders.facebookConfiguration.clientId,
			cookie: false,
			xfbml: true,
			version: 'v2.8'
		});
	}

	public facebookLogin() {
		FB.login((response: any) => {
			if (response.status === 'connected' || 'not_authorized') {
				this.authLogin({ ticket: response.authResponse.accessToken, provider: AuthProvider.Facebook });
			}
		}, { scope: 'email' });
	}

	public hasConfigurableProviders(): boolean {
		return !(this.configurableProviderService.providers == undefined) && this.configurableProviderService.providers.length > 0
	}

	private getLinkedInUrl() {
		return this.configurationService.loginProviders.linkedInConfiguration.oauthUrl
			+ '?response_type=code&client_id=' + this.configurationService.loginProviders.linkedInConfiguration.clientId
			+ '&redirect_uri=' + this.configurationService.loginProviders.linkedInConfiguration.redirectUri
			+ '&state=' + this.configurationService.loginProviders.linkedInConfiguration.state
			+ '&scope=r_emailaddress';
	}

	public linkedInLoginUser(code: string, state: string) {
		if (state !== this.configurationService.loginProviders.linkedInConfiguration.state) {
			this.router.navigate(['/login']);
		}
		this.httpClient.post(this.configurationService.server + 'auth/linkedInRequestToken', { code: code, provider: AuthProvider.LinkedIn })
			.pipe(takeUntil(this._destroyed))
			.subscribe((data: any) => {
				this.authLogin({ ticket: data.payload.accessToken, provider: AuthProvider.LinkedIn, data: null });
			});
	}

	public twitterAuthorize() {
		let headers = new HttpHeaders();
		headers = headers.set('Content-Type', 'application/json');
		headers = headers.set('Accept', 'application/json');
		this.httpClient.get(this.configurationService.server + 'auth/twitterRequestToken', { headers: headers })
			.pipe(takeUntil(this._destroyed))
			.subscribe((data: any) => {
				this.twitterUrl.next(this.configurationService.loginProviders.twitterConfiguration.oauthUrl + '?oauth_token=' + data.payload.value);
			});
	}

	public twitterLoginUser(token: string, verifier: string) {
		const data = {
			email: this.emailFormControl.value, verifier: verifier
		};
		this.authLogin({ ticket: token, provider: AuthProvider.Twitter, data: data });
	}

	private getB2AccessUrl() {
		return this.configurationService.loginProviders.b2accessConfiguration.oauthUrl
		+ '?response_type=code&client_id=' + this.configurationService.loginProviders.b2accessConfiguration.clientId
		+ '&redirect_uri=' + this.configurationService.loginProviders.b2accessConfiguration.redirectUri
		+ '&state=' + this.configurationService.loginProviders.b2accessConfiguration.state
		+ '&scope=USER_PROFILE';
	}

	public b2AccessLoginUser(code: String) {
		let headers = new HttpHeaders();
		headers = headers.set('Content-Type', 'application/json');
		headers = headers.set('Accept', 'application/json');
		this.httpClient.post(this.configurationService.server + 'auth/b2AccessRequestToken', { code: code }, { headers: headers })
			.pipe(takeUntil(this._destroyed))
			.subscribe((data: any) => {
				this.authLogin({ ticket: data.payload.accessToken, provider: AuthProvider.B2Access, data: null });
			});
	}

	private getORCIDUrl() {
		return this.configurationService.loginProviders.orcidConfiguration.oauthUrl
		+ '?client_id='
		+ this.configurationService.loginProviders.orcidConfiguration.clientId
		+ '&response_type=code&scope=/authenticate&redirect_uri='
		+ this.configurationService.loginProviders.orcidConfiguration.redirectUri;
	}

	public orcidLoginUser(code: string) {
		let headers = new HttpHeaders();
		headers = headers.set('Content-Type', 'application/json');
		headers = headers.set('Accept', 'application/json');
		this.httpClient.post(this.configurationService.server + 'auth/orcidRequestToken', { code: code }, { headers: headers })
			.pipe(takeUntil(this._destroyed))
			.subscribe((responseData: any) => {
				this.orcidUser = new OrcidUser();
				this.orcidUser.orcidId = responseData.payload.orcidId
				this.orcidUser.name = responseData.payload.name
				this.accessToken = responseData.payload.accessToken;
				this.authLogin({ ticket: this.accessToken, provider: AuthProvider.ORCID, data: this.orcidUser });
			});
	}

	private getOpenAireUrl() {
		return this.configurationService.loginProviders.openAireConfiguration.oauthUrl
			+ '?response_type=code&client_id=' + this.configurationService.loginProviders.openAireConfiguration.clientId
			+ '&redirect_uri=' + this.configurationService.loginProviders.openAireConfiguration.redirectUri
			+ '&state=' + this.configurationService.loginProviders.openAireConfiguration.state
			+ '&scope=openid profile email';
	}

	public openaireLoginUser(code: string, state: string) {
		if (state !== this.configurationService.loginProviders.openAireConfiguration.state) {
			this.router.navigate(['/login'])
		}
		this.httpClient.post(this.configurationService.server + 'auth/openAireRequestToken', { code: code, provider: AuthProvider.OpenAire })
			.pipe(takeUntil(this._destroyed))
			.subscribe((data: any) => {
				this.authLogin({ ticket: data.payload.accessToken, provider: AuthProvider.OpenAire, data: null });
			});
	}

	private getZenodoUrl() {
		return this.configurationService.loginProviders.zenodoConfiguration.oauthUrl
		+ '?client_id='
		+ this.configurationService.loginProviders.zenodoConfiguration.clientId
		+ '&response_type=code&scope=deposit:write+deposit:actions+user:email&state=astate&redirect_uri='
		+ this.configurationService.loginProviders.zenodoConfiguration.redirectUri;
	}

	public zenodoLoginUser(code: string) {
		let headers = new HttpHeaders();
		headers = headers.set('Content-Type', 'application/json');
		headers = headers.set('Accept', 'application/json');
		this.httpClient.post(this.configurationService.server + 'auth/zenodoRequestToken', { code: code }, { headers: headers })
			.pipe(takeUntil(this._destroyed))
			.subscribe((responseData: any) => {
				this.zenodoToken = new ZenodoToken();
				this.zenodoToken.userId = responseData.payload.userId;
				this.zenodoToken.expiresIn = responseData.payload.expiresIn;
				this.accessToken = this.zenodoToken.accessToken = responseData.payload.accessToken;
				this.zenodoToken.email = responseData.payload.email;
				this.zenodoToken.refreshToken = responseData.payload.refreshToken;
				this.authLogin({ ticket: this.accessToken, provider: AuthProvider.Zenodo, data: this.zenodoToken });
			});
	}

	private authLogin(loginInfo: LoginInfo) {
		if (this.mergeUsers) {
			this.authService.mergeLogin(loginInfo)
					.pipe(takeUntil(this._destroyed))
					.subscribe(
						res => this.mergeLoginService.setRequest({email: res.payload.email, userId: res.payload.id, provider: loginInfo.provider})
					);
		} else {
			this.authService.login(loginInfo)
						.pipe(takeUntil(this._destroyed))
						.subscribe(
							res => this.loginService.onLogInSuccess(res, this.returnUrl),
							error => this.loginService.onLogInError(error)
						);
		}
	}
}
