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

import {
	type EmitterSubscription,
	NativeEventEmitter,
	NativeModules,
	Platform,
} from 'react-native';

import { UserInteractionPlatformOperation } from '../cache/operation/UserInteractionPlatformOperation';
import { PlatformOperationCache } from '../cache/PlatformOperationCache';
import { AuthenticatorExtension } from '../extensions/AuthenticatorExtension';
import { OnValidCredentialsProvidedMessage } from '../model/messages/in/OnValidCredentialsProvidedMessage';
import { PasswordChangerMessage } from '../model/messages/in/PasswordChangerMessage';
import { PasswordEnrollerMessage } from '../model/messages/in/PasswordEnrollerMessage';
import { PasswordValidationMessage } from '../model/messages/in/PasswordValidationMessage';
import { PinChangerMessage } from '../model/messages/in/PinChangerMessage';
import { PinEnrollerMessage } from '../model/messages/in/PinEnrollerMessage';
import { PinValidationMessage } from '../model/messages/in/PinValidationMessage';
import { SelectAccountMessage } from '../model/messages/in/SelectAccountMessage';
import { SelectAuthenticatorMessage } from '../model/messages/in/SelectAuthenticatorMessage';
import { VerifyUserMessage } from '../model/messages/in/VerifyUserMessage';

enum EventType {
	SelectAccount = 'selectAccount',
	SelectAuthenticator = 'selectAuthenticator',
	PinEnroll = 'pinEnroll',
	PasswordEnroll = 'passwordEnroll',
	PinChange = 'pinChange',
	PasswordChange = 'passwordChange',
	PinValidateForEnrollment = 'pinValidateForEnrollment',
	PasswordValidateForEnrollment = 'passwordValidateForEnrollment',
	PinValidateForPinChange = 'pinValidateForPinChange',
	PasswordValidateForPasswordChange = 'passwordValidateForPasswordChange',
	VerifyUser = 'verifyUser',
	OnValidCredentialsProvided = 'onValidCredentialsProvided',
}

export class NativeEventListener {
	private static instance: NativeEventListener;

	private _ongoingOperations = new Set<string>();

	private _eventEmitter: NativeEventEmitter;
	private _selectAccount?: EmitterSubscription;
	private _selectAuthenticator?: EmitterSubscription;
	private _pinEnroll?: EmitterSubscription;
	private _passwordEnroll?: EmitterSubscription;
	private _pinChange?: EmitterSubscription;
	private _passwordChange?: EmitterSubscription;
	private _pinValidateForEnrollment?: EmitterSubscription;
	private _passwordValidateForEnrollment?: EmitterSubscription;
	private _pinValidateForPinChange?: EmitterSubscription;
	private _passwordValidateForPasswordChange?: EmitterSubscription;
	private _verifyUser?: EmitterSubscription;
	private _onValidCredentialsProvided?: EmitterSubscription;

	private constructor() {
		this._eventEmitter = new NativeEventEmitter(NativeModules.RNEventEmitter);
	}

	static getInstance(): NativeEventListener {
		if (!NativeEventListener.instance) {
			NativeEventListener.instance = new NativeEventListener();
		}

		return NativeEventListener.instance;
	}

	start(operationId: string): void {
		this._ongoingOperations.add(operationId);
		this.startListeningForEvents();
	}

	stop(operationId: string): void {
		this._ongoingOperations.delete(operationId);
		if (this._ongoingOperations.size == 0) {
			this.stopListeningForEvents();
		}
	}

	private startListeningForEvents() {
		this.listenToSelectAccount();
		this.listenToSelectAuthenticator();
		this.listenToPinEnroll();
		this.listenToPasswordEnroll();
		this.listenToPinChange();
		this.listenToPasswordChange();
		this.listenToPinValidateForEnrollment();
		this.listenToPasswordValidateForEnrollment();
		this.listenToPinValidateForPinChange();
		this.listenToPasswordValidateForPasswordChange();
		this.listenToVerifyUser();
		this.listenToOnValidCredentialsProvided();
	}

	private stopListeningForEvents() {
		this._selectAccount?.remove();
		this._selectAccount = undefined;
		this._selectAuthenticator?.remove();
		this._selectAuthenticator = undefined;
		this._pinEnroll?.remove();
		this._pinEnroll = undefined;
		this._passwordEnroll?.remove();
		this._passwordEnroll = undefined;
		this._pinChange?.remove();
		this._pinChange = undefined;
		this._passwordChange?.remove();
		this._passwordChange = undefined;
		this._pinValidateForEnrollment?.remove();
		this._pinValidateForEnrollment = undefined;
		this._passwordValidateForEnrollment?.remove();
		this._passwordValidateForEnrollment = undefined;
		this._pinValidateForPinChange?.remove();
		this._pinValidateForPinChange = undefined;
		this._passwordValidateForPasswordChange?.remove();
		this._passwordValidateForPasswordChange = undefined;
		this._verifyUser?.remove();
		this._verifyUser = undefined;
		this._onValidCredentialsProvided?.remove();
		this._onValidCredentialsProvided = undefined;
	}

	private listenToSelectAccount(): void {
		if (this._selectAccount) {
			return; // Already listening
		}

		this._selectAccount = this._eventEmitter.addListener(EventType.SelectAccount, (data) => {
			const message = SelectAccountMessage.fromJson(data);
			const operation = this.getUserInteractionPlatformOperation(
				message.operationId,
				'Operation not found during account selection.'
			);
			operation.selectAccount(message.context);
		});
	}

	private listenToSelectAuthenticator(): void {
		if (this._selectAuthenticator) {
			return; // Already listening
		}

		this._selectAuthenticator = this._eventEmitter.addListener(
			EventType.SelectAuthenticator,
			(data) => {
				const message = SelectAuthenticatorMessage.fromJson(data);
				const operation = this.getUserInteractionPlatformOperation(
					message.operationId,
					'Operation not found during authenticator selection.'
				);
				operation.selectAuthenticator(message.context);
			}
		);
	}

	private listenToPinEnroll(): void {
		if (this._pinEnroll) {
			return; // Already listening
		}

		this._pinEnroll = this._eventEmitter.addListener(EventType.PinEnroll, (data) => {
			const message = PinEnrollerMessage.fromJson(data);
			const operation = this.getUserInteractionPlatformOperation(
				message.operationId,
				'Operation not found during PIN enrollment.'
			);
			operation.enrollPin(message.context);
		});
	}

	private listenToPasswordEnroll(): void {
		if (this._passwordEnroll) {
			return; // Already listening
		}

		this._passwordEnroll = this._eventEmitter.addListener(EventType.PasswordEnroll, (data) => {
			const message = PasswordEnrollerMessage.fromJson(data);
			const operation = this.getUserInteractionPlatformOperation(
				message.operationId,
				'Operation not found during password enrollment.'
			);
			operation.enrollPassword(message.context);
		});
	}

	private listenToPinChange(): void {
		if (this._pinChange) {
			return; // Already listening
		}

		this._pinChange = this._eventEmitter.addListener(EventType.PinChange, (data) => {
			const message = PinChangerMessage.fromJson(data);
			const operation = this.getUserInteractionPlatformOperation(
				message.operationId,
				'Operation not found during PIN change.'
			);
			operation.changePin(message.context);
		});
	}

	private listenToPasswordChange(): void {
		if (this._passwordChange) {
			return; // Already listening
		}

		this._passwordChange = this._eventEmitter.addListener(EventType.PasswordChange, (data) => {
			const message = PasswordChangerMessage.fromJson(data);
			const operation = this.getUserInteractionPlatformOperation(
				message.operationId,
				'Operation not found during password change.'
			);
			operation.changePassword(message.context);
		});
	}

	private listenToPinValidateForEnrollment(): void {
		if (this._pinValidateForEnrollment) {
			return; // Already listening
		}

		this._pinValidateForEnrollment = this._eventEmitter.addListener(
			EventType.PinValidateForEnrollment,
			(data) => {
				const message = PinValidationMessage.fromJson(data);
				const operation = this.getUserInteractionPlatformOperation(
					message.operationId,
					'Operation not found during PIN validation for enrollment.'
				);
				operation.validatePinForEnrollment(message.pin);
			}
		);
	}

	private listenToPasswordValidateForEnrollment(): void {
		if (this._passwordValidateForEnrollment) {
			return; // Already listening
		}

		this._passwordValidateForEnrollment = this._eventEmitter.addListener(
			EventType.PasswordValidateForEnrollment,
			(data) => {
				const message = PasswordValidationMessage.fromJson(data);
				const operation = this.getUserInteractionPlatformOperation(
					message.operationId,
					'Operation not found during password validation for enrollment.'
				);
				operation.validatePasswordForEnrollment(message.password);
			}
		);
	}

	private listenToPinValidateForPinChange(): void {
		if (this._pinValidateForPinChange) {
			return; // Already listening
		}

		this._pinValidateForPinChange = this._eventEmitter.addListener(
			EventType.PinValidateForPinChange,
			(data) => {
				const message = PinValidationMessage.fromJson(data);
				const operation = this.getUserInteractionPlatformOperation(
					message.operationId,
					'Operation not found during PIN validation for PIN change.'
				);
				operation.validatePinForPinChange(message.pin);
			}
		);
	}

	private listenToPasswordValidateForPasswordChange(): void {
		if (this._passwordValidateForPasswordChange) {
			return; // Already listening
		}

		this._passwordValidateForPasswordChange = this._eventEmitter.addListener(
			EventType.PasswordValidateForPasswordChange,
			(data) => {
				const message = PasswordValidationMessage.fromJson(data);
				const operation = this.getUserInteractionPlatformOperation(
					message.operationId,
					'Operation not found during password validation for password change.'
				);
				operation.validatePasswordForPasswordChange(message.password);
			}
		);
	}

	private listenToVerifyUser(): void {
		if (this._verifyUser) {
			return; // Already listening
		}

		this._verifyUser = this._eventEmitter.addListener(EventType.VerifyUser, (data) => {
			const message = VerifyUserMessage.fromJson(data);
			const operation = this.getUserInteractionPlatformOperation(
				message.operationId,
				'Operation not found during user verification.'
			);
			const handler = AuthenticatorExtension.handlerByAuthenticator(
				message.context.authenticator.aaid,
				message.operationId
			);

			operation.userVerificationHandler = handler;
			PlatformOperationCache.getInstance().update(operation);

			(async () => {
				await operation.verifyUser(message.context, handler);
			})();
		});
	}

	private listenToOnValidCredentialsProvided(): void {
		// listenToOnValidCredentialsProvided is only supported on an Android platform
		if (Platform.OS !== 'android') {
			return;
		}

		if (this._onValidCredentialsProvided) {
			return; // Already listening
		}

		this._onValidCredentialsProvided = this._eventEmitter.addListener(
			EventType.OnValidCredentialsProvided,
			(data) => {
				const message = OnValidCredentialsProvidedMessage.fromJson(data);
				const operation = this.getUserInteractionPlatformOperation(
					message.operationId,
					'Operation not found during providing valid credentials.'
				);

				operation.userVerificationHandler = undefined;
				PlatformOperationCache.getInstance().update(operation);

				operation.onValidCredentialsProvided(message.authenticator);
			}
		);
	}

	private getUserInteractionPlatformOperation(
		operationId: string,
		errorMessage: string
	): UserInteractionPlatformOperation {
		const operation = PlatformOperationCache.getInstance().read(operationId);
		if (operation instanceof UserInteractionPlatformOperation) {
			return operation;
		}

		throw new Error(errorMessage);
	}
}
