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

import NevisMobileAuthentication

class UserInteractionState: OperationState {
	// MARK: OperationState

	func cancel() {
		fatalError("Must override.")
	}

	// MARK: Public Interface

	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: -

class 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: UserInteractionState Overrides

	override func cancel() {
		handler?.cancel()
	}

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

// MARK: -

class 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: UserInteractionState Overrides

	override func cancel() {
		handler?.cancel()
	}

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

	override 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: -

class PinEnrollState: UserInteractionState {
	// MARK: Properties

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

	// MARK: Initialization

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

	// MARK: Public Interface

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

	// MARK: UserInteractionState Overrides

	override func cancel() {
		handler?.cancel()
	}

	override 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: -

class PasswordEnrollState: UserInteractionState {
	// MARK: Properties

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

	// MARK: Initialization

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

	// MARK: Public Interface

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

	// MARK: UserInteractionState Overrides

	override func cancel() {
		handler?.cancel()
	}

	override 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: -

class 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() {
		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:
			biometricUserVerificationHandler?.verify()
		case .devicePasscode:
			devicePasscodeUserVerificationHandler?.verify()
		}
	}

	// MARK: UserInteractionState Overrides

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

// MARK: -

class OnValidCredentialsProvidedState: UserInteractionState {
	// MARK: Properties

	let authenticator: any Authenticator

	// MARK: Initialization

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

// MARK: -

class PinValidateForEnrollmentState: UserInteractionState {
	// MARK: Properties

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

	// MARK: Initialization

	init(authenticator: any Authenticator,
	     onSuccess: @escaping () -> (),
	     onValidationError: @escaping (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 Overrides

	override 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: -

class PasswordValidateForEnrollmentState: UserInteractionState {
	// MARK: Properties

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

	// MARK: Initialization

	init(authenticator: any Authenticator,
	     onSuccess: @escaping () -> (),
	     onValidationError: @escaping (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 Overrides

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

		return authenticator
	}
}
