import config from '@automattic/calypso-config';
import { get as webauthn_auth } from '@github/webauthn-json';
import debugFactory from 'debug';
import { bumpStat } from 'calypso/lib/analytics/mc';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import emitter from 'calypso/lib/mixins/emitter';
import { reduxDispatch } from 'calypso/lib/redux-bridge';
import wp from 'calypso/lib/wp';
import { accountRecoverySettingsFetch } from 'calypso/state/account-recovery/settings/actions';
import { requestConnectedApplications } from 'calypso/state/connected-applications/actions';
import { requestUserProfileLinks } from 'calypso/state/profile-links/actions';
import { fetchUserSettings } from 'calypso/state/user-settings/actions';

const debug = debugFactory( 'calypso:two-step-authorization' );

export function bumpTwoStepAuthMCStat( eventAction ) {
	bumpStat( '2fa', eventAction );
	recordTracksEvent( 'calypso_login_twostep_authorize', {
		event_action: eventAction,
	} );
}

/*
 * Initialize TwoStepAuthorization with defaults
 */
function TwoStepAuthorization() {
	if ( ! ( this instanceof TwoStepAuthorization ) ) {
		return new TwoStepAuthorization();
	}

	this.data = {};
	this.initialized = false;
	this.smsResendThrottled = false;

	this.fetch();
}

/*
 * fetch data about users two step configuration from /me/two-step
 */
TwoStepAuthorization.prototype.fetch = function ( callback ) {
	wp.req.get( '/me/two-step/', ( error, data ) => {
		if ( ! error ) {
			this.data = data;

			if ( this.isReauthRequired() && ! this.initialized ) {
				bumpTwoStepAuthMCStat( 'reauth-required' );
			}

			this.initialized = true;
			this.emit( 'change' );
		}

		if ( callback ) {
			callback( error, data );
		}
	} );
};

TwoStepAuthorization.prototype.postLoginRequest = function ( endpoint, data ) {
	if ( ! this.getTwoStepWebauthnNonce() ) {
		return Promise.reject( 'Invalid nonce' );
	}

	const url = 'https://wordpress.com/wp-login.php?action=' + endpoint;
	const formData = new window.FormData();
	const requestData = {
		...data,
		client_id: config( 'wpcom_signup_id' ),
		client_secret: config( 'wpcom_signup_key' ),
		auth_type: 'webauthn',
		two_step_nonce: this.getTwoStepWebauthnNonce(),
	};

	for ( const key in requestData ) {
		formData.set( key, requestData[ key ] );
	}

	return window
		.fetch( url, {
			method: 'POST',
			body: formData,
			credentials: 'include',
		} )
		.then( ( response ) => response.json() );
};

TwoStepAuthorization.prototype.refreshDataOnSuccessfulAuth = function () {
	// If the validation was successful AND re-auth was required, fetch
	// data from the following modules.
	if ( this.isReauthRequired() ) {
		reduxDispatch( accountRecoverySettingsFetch() );
		reduxDispatch( fetchUserSettings() );
		reduxDispatch( requestConnectedApplications() );
		reduxDispatch( requestUserProfileLinks() );
	}
	this.data.two_step_reauthorization_required = false;
	this.invalidCode = false;

	this.emit( 'change' );
};

TwoStepAuthorization.prototype.loginUserWithSecurityKey = function ( args ) {
	return this.postLoginRequest( 'webauthn-challenge-endpoint', {
		user_id: args.user_id,
	} )
		.then( ( response ) => {
			const parameters = response.data || [];
			this.data.two_step_webauthn_nonce = parameters.two_step_nonce;
			if ( typeof this.data.two_step_webauthn_nonce === 'undefined' ) {
				return Promise.reject( response );
			}
			return webauthn_auth( { publicKey: parameters } );
		} )
		.then( ( assertion ) => {
			return this.postLoginRequest( 'webauthn-authentication-endpoint', {
				user_id: args.user_id,
				client_data: JSON.stringify( assertion ),
				create_2fa_cookies_only: 1,
			} );
		} )
		.then( ( response ) => {
			if ( typeof response.success === 'undefined' || ! response.success ) {
				return Promise.reject( response );
			}
			this.refreshDataOnSuccessfulAuth();
			return response;
		} );
};

/*
 * Given a code, validate the code which will update a user's twostep_auth cookie
 */
TwoStepAuthorization.prototype.validateCode = function ( args, callback ) {
	wp.req.post(
		'/me/two-step/validate',
		{
			...args,
			code: args.code.replace( /\s/g, '' ),
		},
		( error, data ) => {
			if ( ! error && data.success ) {
				if ( args.action ) {
					bumpTwoStepAuthMCStat(
						'enable-two-step' === args.action ? 'enable-2fa-successful' : 'disable-2fa-successful'
					);
				} else {
					bumpTwoStepAuthMCStat( 'reauth-successful' );
				}

				this.refreshDataOnSuccessfulAuth();
			} else if ( ! error ) {
				// If code was invalid but API did not error
				this.invalidCode = true;

				if ( args.action ) {
					bumpTwoStepAuthMCStat(
						'enable-two-step' === args.action
							? 'enable-2fa-failed-invalid-code'
							: 'disable-2fa-failed-invalid-code'
					);
				} else {
					bumpTwoStepAuthMCStat( 'reauth-failed-invalid-code' );
				}
			}

			if ( callback ) {
				callback( error, data );
			}
		}
	);
};

/*
 * Send an SMS authentication code to a user's SMS phone number by calling
 * /me/two-step/sms/new
 */
TwoStepAuthorization.prototype.sendSMSCode = function ( callback ) {
	wp.req.post( '/me/two-step/sms/new', ( error, data ) => {
		if ( error ) {
			debug( 'Sending SMS code failed: ' + JSON.stringify( error ) );

			if ( error.error && 'rate_limited' === error.error ) {
				debug( 'SMS resend throttled.' );
				bumpTwoStepAuthMCStat( 'sms-code-send-throttled' );
				this.smsResendThrottled = true;
			}
		} else {
			this.smsResendThrottled = false;
			bumpTwoStepAuthMCStat( 'sms-code-send-success' );
		}

		this.emit( 'change' );

		if ( callback ) {
			callback( error, data );
		}
	} );
};

TwoStepAuthorization.prototype.codeValidationFailed = function () {
	return this.invalidCode;
};

TwoStepAuthorization.prototype.isSMSResendThrottled = function () {
	return this.smsResendThrottled;
};

TwoStepAuthorization.prototype.isReauthRequired = function () {
	return this.data.two_step_reauthorization_required ?? false;
};

TwoStepAuthorization.prototype.isTwoStepSMSEnabled = function () {
	return this.data.two_step_sms_enabled ?? false;
};

TwoStepAuthorization.prototype.isSecurityKeyEnabled = function () {
	return this.data.two_step_webauthn_enabled ?? false;
};

TwoStepAuthorization.prototype.getTwoStepWebauthnNonce = function () {
	return this.data.two_step_webauthn_nonce ?? false;
};

TwoStepAuthorization.prototype.getSMSLastFour = function () {
	return this.data.two_step_sms_last_four ?? null;
};

emitter( TwoStepAuthorization.prototype );

/**
 * Expose TwoStepAuthorization
 */
export default new TwoStepAuthorization();
