//
// Copyright © 2023 Nevis Security AG. All rights reserved.
//

import NevisMobileAuthentication

@objc(NevisMobileAuthenticationSdkReact)
public class NevisMobileAuthenticationSdkReact: NSObject {
	private var client: MobileAuthenticationClient?

	private let methodMessageMap: [ReactMethod: ChannelInMessage.Type] = [
		.initClient: InitClientMessage.self,
		.payloadDecode: OutOfBandPayloadDecodeMessage.self,
		.oobOperation: OutOfBandOperationMessage.self,
		.oobRegister: OutOfBandRegistrationMessage.self,
		.oobAuthenticate: OutOfBandAuthenticationMessage.self,
		.register: RegistrationMessage.self,
		.authCloudApiRegister: AuthCloudApiRegistrationMessage.self,
		.authenticate: AuthenticationMessage.self,
		.deregister: DeregistrationMessage.self,
		.deviceInformationChange: DeviceInformationChangeMessage.self,
		.deviceInformationCheck: DeviceInformationCheckMessage.self,
		.deviceInformationSync: DeviceInformationSyncMessage.self,
		.pendingOutOfBandOperations: PendingOobOperationsMessage.self,
		.pinChange: PinChangeMessage.self,
		.passwordChange: PasswordChangeMessage.self,
		.localAccounts: LocalAccountsMessage.self,
		.localAuthenticators: LocalAuthenticatorsMessage.self,
		.localDeleteAuthenticator: LocalDeleteAuthenticatorMessage.self,
		.localDeviceInformation: LocalDeviceInformationMessage.self,
		.authenticatorAaid: AuthenticatorAaidMessage.self,
		.accountUsername: AccountUsernameMessage.self,
		.pinEnroll: PinEnrollMessage.self,
		.pinsChange: PinsChangeMessage.self,
		.pinValidatedForEnrollment: PinValidatedForEnrollmentMessage.self,
		.pinValidatedForPinChange: PinValidatedForPinChangeMessage.self,
		.pinVerify: PinVerifyMessage.self,
		.passwordEnroll: PasswordEnrollMessage.self,
		.passwordsChange: PasswordsChangeMessage.self,
		.passwordValidatedForEnrollment: PasswordValidatedForEnrollmentMessage.self,
		.passwordValidatedForPasswordChange: PasswordValidatedForPasswordChangeMessage.self,
		.passwordVerify: PasswordVerifyMessage.self,
		.cancel: CancelMessage.self,
		.isPolicyCompliant: IsPolicyCompliantMessage.self,
		.listenForOsCredentials: ListenForOsCredentialsMessage.self,
		.metaData: ChannelInMessage.self,
	]

	override private init() { /* . */ }

	// MARK: - Basic SDK invocations

	@objc
	func initClient(_ message: [String: Any],
	                resolver: @escaping RCTPromiseResolveBlock,
	                rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.initClient, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func payloadDecode(_ message: [String: Any],
	                   resolver: @escaping RCTPromiseResolveBlock,
	                   rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.payloadDecode, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func oobOperation(_ message: [String: Any],
	                  resolver: @escaping RCTPromiseResolveBlock,
	                  rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.oobOperation, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func oobRegister(_ message: [String: Any],
	                 resolver: @escaping RCTPromiseResolveBlock,
	                 rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.oobRegister, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func oobAuthenticate(_ message: [String: Any],
	                     resolver: @escaping RCTPromiseResolveBlock,
	                     rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.oobAuthenticate, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func inBandRegister(_ message: [String: Any],
	                    resolver: @escaping RCTPromiseResolveBlock,
	                    rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.register, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func authCloudApiRegister(_ message: [String: Any],
	                          resolver: @escaping RCTPromiseResolveBlock,
	                          rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.authCloudApiRegister, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func authenticate(_ message: [String: Any],
	                  resolver: @escaping RCTPromiseResolveBlock,
	                  rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.authenticate, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func deregister(_ message: [String: Any],
	                resolver: @escaping RCTPromiseResolveBlock,
	                rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.deregister, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func deviceInformationChange(_ message: [String: Any],
	                             resolver: @escaping RCTPromiseResolveBlock,
	                             rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.deviceInformationChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func deviceInformationCheck(_ message: [String: Any],
	                            resolver: @escaping RCTPromiseResolveBlock,
	                            rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.deviceInformationCheck, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func deviceInformationSync(_ message: [String: Any],
	                           resolver: @escaping RCTPromiseResolveBlock,
	                           rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.deviceInformationSync, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pendingOutOfBandOperations(_ message: [String: Any],
	                                resolver: @escaping RCTPromiseResolveBlock,
	                                rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pendingOutOfBandOperations, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pinChange(_ message: [String: Any],
	               resolver: @escaping RCTPromiseResolveBlock,
	               rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pinChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func passwordChange(_ message: [String: Any],
	                    resolver: @escaping RCTPromiseResolveBlock,
	                    rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.passwordChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func localAccounts(_ message: [String: Any],
	                   resolver: @escaping RCTPromiseResolveBlock,
	                   rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.localAccounts, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func localAuthenticators(_ message: [String: Any],
	                         resolver: @escaping RCTPromiseResolveBlock,
	                         rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.localAuthenticators, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func localDeleteAuthenticator(_ message: [String: Any],
	                              resolver: @escaping RCTPromiseResolveBlock,
	                              rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.localDeleteAuthenticator, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func localDeviceInformation(_ message: [String: Any],
	                            resolver: @escaping RCTPromiseResolveBlock,
	                            rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.localDeviceInformation, message: message, resolver: resolver, rejecter: rejecter)
	}

	// MARK: - Auxiliary SDK invocations

	@objc
	func authenticatorAaid(_ message: [String: Any],
	                       resolver: @escaping RCTPromiseResolveBlock,
	                       rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.authenticatorAaid, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pinEnroll(_ message: [String: Any],
	               resolver: @escaping RCTPromiseResolveBlock,
	               rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pinEnroll, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pinsChange(_ message: [String: Any],
	                resolver: @escaping RCTPromiseResolveBlock,
	                rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pinsChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pinValidatedForEnrollment(_ message: [String: Any],
	                               resolver: @escaping RCTPromiseResolveBlock,
	                               rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pinValidatedForEnrollment, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pinValidatedForPinChange(_ message: [String: Any],
	                              resolver: @escaping RCTPromiseResolveBlock,
	                              rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pinValidatedForPinChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func pinVerify(_ message: [String: Any],
	               resolver: @escaping RCTPromiseResolveBlock,
	               rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.pinVerify, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func passwordEnroll(_ message: [String: Any],
	                    resolver: @escaping RCTPromiseResolveBlock,
	                    rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.passwordEnroll, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func passwordsChange(_ message: [String: Any],
	                     resolver: @escaping RCTPromiseResolveBlock,
	                     rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.passwordsChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func passwordValidatedForEnrollment(_ message: [String: Any],
	                                    resolver: @escaping RCTPromiseResolveBlock,
	                                    rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.passwordValidatedForEnrollment, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func passwordValidatedForPasswordChange(_ message: [String: Any],
	                                        resolver: @escaping RCTPromiseResolveBlock,
	                                        rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.passwordValidatedForPasswordChange, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func passwordVerify(_ message: [String: Any],
	                    resolver: @escaping RCTPromiseResolveBlock,
	                    rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.passwordVerify, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func accountUsername(_ message: [String: Any],
	                     resolver: @escaping RCTPromiseResolveBlock,
	                     rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.accountUsername, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func cancel(_ message: [String: Any],
	            resolver: @escaping RCTPromiseResolveBlock,
	            rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.cancel, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func isPolicyCompliant(_ message: [String: Any],
	                       resolver: @escaping RCTPromiseResolveBlock,
	                       rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.isPolicyCompliant, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func listenForOsCredentials(_ message: [String: Any],
	                            resolver: @escaping RCTPromiseResolveBlock,
	                            rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.listenForOsCredentials, message: message, resolver: resolver, rejecter: rejecter)
	}

	@objc
	func cancelAuthentication(_: [String: Any],
	                          resolver: @escaping RCTPromiseResolveBlock,
	                          rejecter _: @escaping RCTPromiseRejectBlock)
	{
		// not supported in iOS
		resolver(nil)
	}

	@objc
	func pauseListening(_: [String: Any],
	                    resolver: @escaping RCTPromiseResolveBlock,
	                    rejecter _: @escaping RCTPromiseRejectBlock)
	{
		// not supported in iOS
		resolver(nil)
	}

	@objc
	func resumeListening(_: [String: Any],
	                     resolver: @escaping RCTPromiseResolveBlock,
	                     rejecter _: @escaping RCTPromiseRejectBlock)
	{
		// not supported in iOS
		resolver(nil)
	}

	@objc
	func metaData(_ message: [String: Any],
	              resolver: @escaping RCTPromiseResolveBlock,
	              rejecter: @escaping RCTPromiseRejectBlock)
	{
		handle(.metaData, message: message, resolver: resolver, rejecter: rejecter)
	}

	func handle(_ method: ReactMethod, message: [String: Any], resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
		guard let messageType = methodMessageMap[method] else {
			fatalError("The \(method) method is not implemented!")
		}

		MethodChannelHandler.shared.channelData[method] = MethodChannelData(resolver: resolver, rejecter: rejecter)
		let message = try! JsonMapper.decode(messageType, from: message)

		switch method {
		case .initClient:
			InitClientMethodHandler().execute(with: message) { self.client = $0 }
		case .metaData:
			MetaDataMethodHandler().execute(with: message)
		default:
			guard let client else {
				fatalError("Mobile Authentication Client was not initialized!")
			}

			guard let handler = MethodHandlerFactory.create(by: messageType) else {
				fatalError("Method handler not found for message type \(messageType)!")
			}

			handler.execute(using: client, with: message)
		}
	}
}
