/**
 * Copyright © 2024 Nevis Security AG. All rights reserved.
 */

import uuid from 'react-native-uuid';

import type { PasswordChanger } from './PasswordChanger';
import { UserInteractionPlatformOperationImpl } from '../../cache/operation/UserInteractionPlatformOperation';
import { PlatformOperationCache } from '../../cache/PlatformOperationCache';
import type { PasswordChangeError } from '../../error/password/change/PasswordChangeError';
import { PasswordChangeErrorConverter } from '../../error/password/change/PasswordChangeErrorConverter';
import { NativeEventListener } from '../../event/NativeEventListener';
import NevisMobileAuthenticationSdkReact from '../../MobileAuthenticationSdk';
import { PasswordChangeMessage } from '../../model/messages/out/PasswordChangeMessage';
import { Operation } from '../Operation';

/**
 * The object that can be used to change the password.
 *
 * Usage example:
 * ```ts
 * class PasswordChangerImpl implements PasswordChanger {
 *     async changePassword(context: PasswordChangeContext, handler: PasswordChangeHandler) {
 *         handler.passwords(oldPassword, newPassword);
 *     }
 * }
 *
 * [...]
 * async changePassword({
 *     client: MobileAuthenticationClient,
 *     username: string,
 * }): Promise<void> {
 *     await client.operations.passwordChange
 *         .username(username)
 *         .passwordChanger(PasswordChangerImpl(...))
 *         .onSuccess(() {
 *             // handle success
 *         })
 *         .onError((error) {
 *             // handle error
 *         })
 *         .execute();
 * }
 * [...]
 * ```
 */
export abstract class PasswordChange extends Operation {
	/**
	 * The username whose password must be changed.
	 *
	 * **IMPORTANT** \
	 * Providing the {@link username} is required.
	 *
	 * **WARNING** \
	 * The username is the technical user identifier stored in the {@link Account.username} property.
	 * Do not provide the login identifier (for example the users e-mail address) here.
	 * We recommend always using the username provided via {@link LocalData.accounts}.
	 *
	 * @param username the username.
	 * @returns a {@link PasswordChange} object.
	 */
	abstract username(username: string): PasswordChange;

	/**
	 * Specifies the object that will be informed of the potential recoverable
	 * errors and is responsible for obtaining the password from the end-user.
	 *
	 * **IMPORTANT** \
	 * Providing the {@link passwordChanger} is required.
	 *
	 * @param passwordChanger the {@link passwordChanger}
	 * @returns a {@link PasswordChange} object.
	 */
	abstract passwordChanger(passwordChanger: PasswordChanger): PasswordChange;

	/**
	 * Specifies the object that will be invoked if the password was successfully modified.
	 *
	 * **IMPORTANT** \
	 * Providing the {@link onSuccess} is required.
	 *
	 * @param onSuccess the callback which is invoked on successful password modification.
	 * @returns a {@link PasswordChange} object.
	 */
	abstract onSuccess(onSuccess: () => void): PasswordChange;

	/**
	 * Specifies the object that will be invoked when the password could not be changed:
	 * the password was not enrolled, the password is locked or the operation was canceled.
	 *
	 * **IMPORTANT** \
	 * Providing the {@link onError} is required.
	 *
	 * @param onError the callback which receives a {@link PasswordChangeError}.
	 * @returns a {@link PasswordChange} object.
	 */
	abstract onError(onError: (error: PasswordChangeError) => void): PasswordChange;
}

export class PasswordChangeImpl extends PasswordChange {
	private _username?: string;
	private _passwordChanger?: PasswordChanger;
	private _onSuccess?: () => void;
	private _onError?: (error: PasswordChangeError) => void;

	username(username: string): PasswordChange {
		this._username = username;
		return this;
	}

	passwordChanger(passwordChanger: PasswordChanger): PasswordChange {
		this._passwordChanger = passwordChanger;
		return this;
	}

	onSuccess(onSuccess: () => void): PasswordChange {
		this._onSuccess = onSuccess;
		return this;
	}

	onError(onError: (error: PasswordChangeError) => void): PasswordChange {
		this._onError = onError;
		return this;
	}

	async execute(): Promise<void> {
		const operationId = uuid.v4() as string;
		const operation = new UserInteractionPlatformOperationImpl(
			operationId,
			undefined,
			undefined,
			undefined,
			undefined,
			undefined,
			this._passwordChanger,
			undefined
		);

		PlatformOperationCache.getInstance().put(operation);
		NativeEventListener.getInstance().start(operationId);

		const message = new PasswordChangeMessage(
			operationId,
			this._passwordChanger !== undefined,
			this._onSuccess !== undefined,
			this._onError !== undefined,
			this._username
		);

		function finish() {
			NativeEventListener.getInstance().stop(operationId);
			PlatformOperationCache.getInstance().delete(operationId);
		}

		return NevisMobileAuthenticationSdkReact.passwordChange(message)
			.then(() => {
				finish();
				this._onSuccess?.();
			})
			.catch((error: any) => {
				finish();
				const passwordChangeError = new PasswordChangeErrorConverter(error).convert();
				this._onError?.(passwordChangeError);
			});
	}
}
