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

import NevisMobileAuthentication

@objc(NevisMobileAuthenticationSdkReact)
public final class NevisMobileAuthenticationSdkReact: NSObject, Sendable {
	private(set) nonisolated(unsafe) var client: MobileAuthenticationClient?
	private let queue = DispatchQueue(withLabel: "ch.nevis.mobile.authentication.sdk.react")

	private let methodMessageMap: [ReactMethodName: 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,
		.localDeleteAllData: LocalDeleteAllDataMessage.self,
		.localDeviceInformation: LocalDeviceInformationMessage.self,
		.fidoUafAttestationInformation: FidoUafAttestationInformationMessage.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: MetaDataMessage.self,
	]

	override init() { /* . */  }

	// MARK: - Basic SDK invocations

	@objc
	func initClient(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .initClient), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func payloadDecode(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .payloadDecode), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func oobOperation(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .oobOperation), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func oobRegister(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .oobRegister), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func oobAuthenticate(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .oobAuthenticate), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func inBandRegister(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .register), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func authCloudApiRegister(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .authCloudApiRegister), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func authenticate(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .authenticate), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func deregister(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .deregister), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func deviceInformationChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .deviceInformationChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func deviceInformationCheck(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .deviceInformationCheck), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func deviceInformationSync(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .deviceInformationSync), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pendingOutOfBandOperations(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pendingOutOfBandOperations), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pinChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pinChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func passwordChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .passwordChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func localAccounts(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .localAccounts), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func localAuthenticators(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .localAuthenticators), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func localDeleteAuthenticator(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .localDeleteAuthenticator), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func localDeleteAllData(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .localDeleteAllData), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func localDeviceInformation(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .localDeviceInformation), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func fidoUafAttestationInformation(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .fidoUafAttestationInformation), message: message, resolve: resolve, reject: reject)
	}

	// MARK: - Auxiliary SDK invocations

	@objc
	func authenticatorAaid(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .authenticatorAaid), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pinEnroll(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pinEnroll), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pinsChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pinsChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pinValidatedForEnrollment(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pinValidatedForEnrollment), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pinValidatedForPinChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pinValidatedForPinChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func pinVerify(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .pinVerify), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func passwordEnroll(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .passwordEnroll), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func passwordsChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .passwordsChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func passwordValidatedForEnrollment(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .passwordValidatedForEnrollment), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func passwordValidatedForPasswordChange(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .passwordValidatedForPasswordChange), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func passwordVerify(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .passwordVerify), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func accountUsername(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .accountUsername), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func cancel(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .cancel), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func isPolicyCompliant(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .isPolicyCompliant), message: message, resolve: resolve, reject: reject)
	}

	@objc
	func listenForOsCredentials(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .listenForOsCredentials), message: message, resolve: resolve, reject: reject)
	}

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

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

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

	@objc
	func metaData(
		_ message: [String: Any],
		resolve: @escaping RCTPromiseResolveBlock,
		reject: @escaping RCTPromiseRejectBlock
	) {
		handle(ReactMethod(name: .metaData), message: message, resolve: resolve, reject: reject)
	}

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

		let message = try! JsonMapper.decode(messageType, from: message)
		MethodChannelHandler.shared.add(method: method, resolve: resolve, reject: reject)

		Task {
			switch method.name {
				case .initClient:
					await InitClientMethodHandler().execute(method: method, message: message) { client in
						self.queue.sync {
							self.client = client
						}
					}
				case .metaData:
					await MetaDataMethodHandler().execute(method: method, message: message)
				case .localDeleteAllData:
					await LocalDeleteAllDataMethodHandler().execute(method: method)
				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)!")
					}

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