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

import NevisMobileAuthentication

protocol UserInteractionState: OperationState {
	func isPolicyCompliant(aaid: String, for: String?) async -> Bool

	func findAuthenticator(aaid: AuthenticatorAaid) async -> any Authenticator
}

extension UserInteractionState {
	// MARK: UserInteractionState Overrides

	func isPolicyCompliant(aaid _: String, for _: String?) -> Bool {
		fatalError("isPolicyCompliant cannot be called in the current state!")
	}

	func findAuthenticator(aaid _: AuthenticatorAaid) -> any Authenticator {
		fatalError("findAuthenticator cannot be called in the current state!")
	}
}

// MARK: -

actor SelectAccountState: UserInteractionState {
	// MARK: Properties

	let context: AccountSelectionContext
	let handler: AccountSelectionHandler

	// MARK: Initialization

	init(context: AccountSelectionContext, handler: AccountSelectionHandler) {
		self.context = context
		self.handler = handler
	}

	// MARK: Public Interface

	func selectAccount(username: String) {
		handler.username(username)
	}

	// MARK: OperationState

	func cancel() {
		handler.cancel()
	}

	// MARK: UserInteractionState

	func isPolicyCompliant(aaid: String, for username: String?) -> Bool {
		guard let username else { return false }
		return context.isPolicyCompliant(username: username, aaid: aaid)
	}
}

// MARK: -

actor SelectAuthenticatorState: UserInteractionState {
	// MARK: Properties

	let context: AuthenticatorSelectionContext
	let handler: AuthenticatorSelectionHandler

	// MARK: Initialization

	init(context: AuthenticatorSelectionContext, handler: AuthenticatorSelectionHandler) {
		self.context = context
		self.handler = handler
	}

	// MARK: Public Interface

	func selectAuthenticator(aaid: String) {
		handler.aaid(aaid)
	}

	// MARK: OperationState

	func cancel() {
		handler.cancel()
	}

	// MARK: UserInteractionState

	func isPolicyCompliant(aaid: String, for _: String?) -> Bool {
		context.isPolicyCompliant(authenticatorAaid: aaid)
	}

	func findAuthenticator(aaid: AuthenticatorAaid) -> any Authenticator {
		guard let authenticator = context.authenticators.first(where: { $0.aaid == aaid.rawValue }) else {
			fatalError("Authenticator with aaid \(aaid) is missing!")
		}

		return authenticator
	}
}

// MARK: -

actor PinEnrollState: UserInteractionState {
	// MARK: Properties

	let context: PinEnrollmentContext
	let handler: PinEnrollmentHandler
	let authenticator: any Authenticator

	// MARK: Initialization

	init(context: PinEnrollmentContext, handler: PinEnrollmentHandler, authenticator: any Authenticator) {
		self.context = context
		self.handler = handler
		self.authenticator = authenticator
	}

	// MARK: Public Interface

	func enrollPin(pin: String) {
		handler.pin(pin)
	}

	// MARK: OperationState

	func cancel() {
		handler.cancel()
	}

	// MARK: UserInteractionState

	func findAuthenticator(aaid: AuthenticatorAaid) -> any Authenticator {
		guard aaid == AuthenticatorAaid.Pin else {
			fatalError("Trying to find wrong authenticator (\(aaid.rawValue)) during PIN enrollment!")
		}

		return authenticator
	}
}

// MARK: -

actor PasswordEnrollState: UserInteractionState {
	// MARK: Properties

	let context: PasswordEnrollmentContext
	let handler: PasswordEnrollmentHandler
	let authenticator: any Authenticator

	// MARK: Initialization

	init(context: PasswordEnrollmentContext, handler: PasswordEnrollmentHandler, authenticator: any Authenticator) {
		self.context = context
		self.handler = handler
		self.authenticator = authenticator
	}

	// MARK: Public Interface

	func enroll(password: String) {
		handler.password(password)
	}

	// MARK: OperationState

	func cancel() {
		handler.cancel()
	}

	// MARK: UserInteractionState

	func findAuthenticator(aaid: AuthenticatorAaid) -> any Authenticator {
		guard aaid == AuthenticatorAaid.Password else {
			fatalError("Trying to find wrong authenticator (\(aaid.rawValue)) during password enrollment!")
		}

		return authenticator
	}
}

// MARK: -

enum VerificationMode {
	case pin, password, biometric, devicePasscode
}

// MARK: -

actor VerifyUserState: UserInteractionState {
	// MARK: Properties

	let verificationMode: VerificationMode
	let context: UserVerificationContext?
	let pinUserVerificationHandler: PinUserVerificationHandler?
	let passwordUserVerificationHandler: PasswordUserVerificationHandler?
	let biometricUserVerificationHandler: BiometricUserVerificationHandler?
	let devicePasscodeUserVerificationHandler: DevicePasscodeUserVerificationHandler?

	// MARK: Initialization

	init(verificationMode: VerificationMode,
	     context: UserVerificationContext?,
	     pinUserVerificationHandler: PinUserVerificationHandler? = nil,
	     passwordUserVerificationHandler: PasswordUserVerificationHandler? = nil,
	     biometricUserVerificationHandler: BiometricUserVerificationHandler? = nil,
	     devicePasscodeUserVerificationHandler: DevicePasscodeUserVerificationHandler? = nil)
	{
		self.verificationMode = verificationMode
		self.context = context
		self.pinUserVerificationHandler = pinUserVerificationHandler
		self.passwordUserVerificationHandler = passwordUserVerificationHandler
		self.biometricUserVerificationHandler = biometricUserVerificationHandler
		self.devicePasscodeUserVerificationHandler = devicePasscodeUserVerificationHandler
	}

	// MARK: Public Interface

	func verify(pin: String) {
		switch verificationMode {
		case .pin:
			pinUserVerificationHandler?.verify(pin)
		case .password, .biometric, .devicePasscode:
			fatalError("Cannot use pin verification during biometric verification!")
		}
	}

	func verify(password: String) {
		switch verificationMode {
		case .password:
			passwordUserVerificationHandler?.verify(password)
		case .pin, .biometric, .devicePasscode:
			fatalError("Cannot use password verification during other verification process!")
		}
	}

	func listenForOsCredentials(using options: PromptOptions?) {
		switch verificationMode {
		case .pin:
			fatalError("Cannot listen to OS credentials during PIN verification!")
		case .password:
			fatalError("Cannot listen to OS credentials during password verification!")
		case .biometric:
			guard let biometricPromptOptions = options as? BiometricPromptOptions else {
				biometricUserVerificationHandler?.verify()
				return
			}
			biometricUserVerificationHandler?.verify(using: biometricPromptOptions)
		case .devicePasscode:
			guard let devicePasscodePromptOptions = options as? DevicePasscodePromptOptions else {
				devicePasscodeUserVerificationHandler?.verify()
				return
			}
			devicePasscodeUserVerificationHandler?.verify(using: devicePasscodePromptOptions)
		}
	}

	// MARK: OperationState

	func cancel() {
		switch verificationMode {
		case .pin:
			pinUserVerificationHandler?.cancel()
		case .password:
			passwordUserVerificationHandler?.cancel()
		case .biometric:
			biometricUserVerificationHandler?.cancel()
		case .devicePasscode:
			devicePasscodeUserVerificationHandler?.cancel()
		}
	}
}

// MARK: -

final class OnValidCredentialsProvidedState: UserInteractionState {
	// MARK: Properties

	let authenticator: any Authenticator

	// MARK: Initialization

	init(authenticator: any Authenticator) {
		self.authenticator = authenticator
	}
}

// MARK: -

actor PinValidateForEnrollmentState: UserInteractionState {
	// MARK: Properties

	let authenticator: any Authenticator
	let onSuccess: @Sendable () -> ()
	let onValidationError: @Sendable (PinEnrollmentValidationError) -> ()

	// MARK: Initialization

	init(authenticator: any Authenticator,
	     onSuccess: @escaping @Sendable () -> (),
	     onValidationError: @escaping @Sendable (PinEnrollmentValidationError) -> ())
	{
		self.authenticator = authenticator
		self.onSuccess = onSuccess
		self.onValidationError = onValidationError
	}

	// MARK: Public Interface

	func validatePin(errorMessage: String?, cause: String?) {
		guard let errorMessage else {
			return onSuccess()
		}

		var error: Error? {
			guard let cause else {
				return nil
			}
			return PinValidationError.InvalidPin(cause: cause)
		}
		onValidationError(.InvalidPin(message: errorMessage, cause: error))
	}

	// MARK: UserInteractionState

	func findAuthenticator(aaid: AuthenticatorAaid) -> any Authenticator {
		guard aaid == AuthenticatorAaid.Pin else {
			fatalError("Trying to find wrong authenticator (\(aaid.rawValue)) during PIN validation!")
		}

		return authenticator
	}
}

// MARK: -

actor PasswordValidateForEnrollmentState: UserInteractionState {
	// MARK: Properties

	let authenticator: any Authenticator
	let onSuccess: @Sendable () -> ()
	let onValidationError: @Sendable (PasswordEnrollmentValidationError) -> ()

	// MARK: Initialization

	init(authenticator: any Authenticator,
	     onSuccess: @escaping @Sendable () -> (),
	     onValidationError: @escaping @Sendable (PasswordEnrollmentValidationError) -> ())
	{
		self.authenticator = authenticator
		self.onSuccess = onSuccess
		self.onValidationError = onValidationError
	}

	// MARK: Public Interface

	func validatePassword(errorMessage: String?, cause: String?) {
		guard let errorMessage else {
			return onSuccess()
		}

		var error: Error? {
			guard let cause else {
				return nil
			}
			return PasswordValidationError.InvalidPassword(cause: cause)
		}
		onValidationError(.InvalidPassword(message: errorMessage, cause: error))
	}

	// MARK: UserInteractionState

	func findAuthenticator(aaid: AuthenticatorAaid) -> any Authenticator {
		guard aaid == AuthenticatorAaid.Password else {
			fatalError("Trying to find wrong authenticator (\(aaid.rawValue)) during password validation!")
		}

		return authenticator
	}
}
