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

import uuid from 'react-native-uuid';

import type { PendingOutOfBandOperationsResult } from './PendingOutOfBandOperationsResult';
import { PendingOutOfBandPlatformOperation } from '../../cache/operation/PendingOutOfBandPlatformOperation';
import { PlatformOperationCache } from '../../cache/PlatformOperationCache';
import NevisMobileAuthenticationSdkReact from '../../MobileAuthenticationSdk';
import { PendingOutOfBandOperationsResultMessage } from '../../model/messages/in/PendingOutOfBandOperationsResultMessage';
import { PendingOutOfBandOperationsMessage } from '../../model/messages/out/PendingOutOfBandOperationsMessage';
import { HttpOperation, HttpOperationImpl } from '../HttpOperation';

/**
 * The operation retrieving the out-of-band operations that have been started in
 * the server, and must be handled by the application running the Mobile Authentication
 * SDK on the device.
 *
 * The retrieved operations have been neither timed-out, nor have been processed
 * by the SDK, that is, they have not been processed using an {@link OutOfBandOperation}
 * operation.
 *
 * **IMPORTANT** \
 * It is recommended to use this operation as sparsely as possible, because of
 * the performance impact that it can have in the server. Do not use it for example
 * with a polling mechanism.
 *
 * This is supported only when the backend uses nevisFIDO 7.2402.** or later.
 *
 * Usage example:
 * ```ts
 *    [...]
 *    async retrievePendingOutOfBandOperations(
 *        operations: Operations
 *    ): Promise<void> {
 *        await operations.pendingOutOfBandOperations
 *            .onResult((result) => {
 *                // handle the retrieved out-of-band operations. For example
 *                // they can be processed using an OutOfBandOperation.
 *            })
 *            .execute();
 *    }
 *    [...]
 * ```
 *
 * @see
 * - {@link PendingOutOfBandOperationsResult}
 * - {@link PendingOutOfBandOperation.payload}
 * - {@link OutOfBandOperation}
 * - {@link Operations#pendingOutOfBandOperations}
 */
export abstract class PendingOutOfBandOperations extends HttpOperation<PendingOutOfBandOperations> {
	/**
	 * Specifies the object that will be invoked with the {@link PendingOutOfBandOperationsResult}
	 * containing the pending out-of-band operations for this application and the
	 * errors (if any).
	 *
	 * **IMPORTANT** \
	 * Providing the {@link onResult} callback is required.
	 *
	 * @param onResult the callback that will be invoked when the operation finishes.
	 * @returns a {@link PendingOutOfBandOperations} object.
	 */
	abstract onResult(
		onResult: (result: PendingOutOfBandOperationsResult) => void
	): PendingOutOfBandOperations;
}

export class PendingOutOfBandOperationsImpl
	extends HttpOperationImpl<PendingOutOfBandOperations>
	implements PendingOutOfBandOperations
{
	private _onResult?: (result: PendingOutOfBandOperationsResult) => void;

	constructor() {
		super();
	}

	onResult(
		onResult: (result: PendingOutOfBandOperationsResult) => void
	): PendingOutOfBandOperations {
		this._onResult = onResult;
		return this;
	}

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

		PlatformOperationCache.getInstance().put(operation);

		const message = new PendingOutOfBandOperationsMessage(
			operationId,
			this._onResult !== undefined,
			this.httpRequestHeaders
		);

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

		return NevisMobileAuthenticationSdkReact.pendingOutOfBandOperations(message).then(
			(result: PendingOutOfBandOperationsResultMessage) => {
				const resultMessage = PendingOutOfBandOperationsResultMessage.fromJson(result);
				operation.handleResult(resultMessage.result);
				finish();
			}
		);
	}
}
