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

import NevisMobileAuthentication

enum ErrorCodingKeys: CodingKey {
	case description
	case errorCode
	case sessionProvider
	case server
	case cause
}

struct ChannelError: Encodable {
	// MARK: Properties

	let type: String
	let description: String
	// used only when either one of the following error occurs:
	// - OperationError.FidoError
	// - AuthCloudApiError.FidoError
	// - AuthenticationError.FidoError
	let innerError: FidoErrorCode?
	// used only when either one of the following error occurs:
	// - AuthenticationError.FidoError
	// - AuthenticationError.NetworkError
	let sessionProvider: TypedSessionProvider?
	// used only when either one of the following error occurs:
	// - DeviceInformationCheckError
	// - DeviceInformationSyncError
	// - PendingOutOfBandOperationsError
	let server: (any Server)?
	let cause: String?

	// MARK: Encodable

	func encode(to encoder: Encoder) throws {
		var container = encoder.container(keyedBy: TypedCodingKeys.self)
		try container.encode(type, forKey: .type)

		var dataContainer = container.nestedContainer(keyedBy: ErrorCodingKeys.self, forKey: .data)
		try dataContainer.encode(description, forKey: .description)
		try dataContainer.encodeIfPresent(innerError, forKey: .errorCode)
		try dataContainer.encodeIfPresent(sessionProvider, forKey: .sessionProvider)
		if let server {
			try dataContainer.encode(server, forKey: .server)
		}
		try dataContainer.encodeIfPresent(cause, forKey: .cause)
	}
}

// MARK: - FidoErrorCode - Encodable

extension FidoErrorCode: Encodable {
	enum CodingKeys: CodingKey {
		case type
		case description
	}

	public func encode(to encoder: Encoder) throws {
		var container = encoder.container(keyedBy: CodingKeys.self)
		let type = String(describing: self)
		try container.encode(type.snakeUpperCase(), forKey: .type)
		try container.encodeIfPresent(errorDescription, forKey: .description)
	}
}

// MARK: - FidoErrorCode - CustomStringConvertible

extension FidoErrorCode: CustomStringConvertible {
	// mirror is not working in case of @objc annotated enums
	public var description: String {
		switch self {
			case .noError:
				"noError"
			case .waitUserAction:
				"waitUserAction"
			case .insecureTransport:
				"insecureTransport"
			case .userCanceled:
				"userCanceled"
			case .unsupportedVersion:
				"unsupportedVersion"
			case .noSuitableAuthenticator:
				"noSuitableAuthenticator"
			case .protocolError:
				"protocolError"
			case .untrustedFacetID:
				"untrustedFacetID"
			case .keyDisappearedPermanently:
				"keyDisappearedPermanently"
			case .authenticatorAccessDenied:
				"authenticatorAccessDenied"
			case .invalidTransactionContent:
				"invalidTransactionContent"
			case .userNotResponsive:
				"userNotResponsive"
			case .insufficientAuthenticatorResources:
				"insufficientAuthenticatorResources"
			case .userLockout:
				"userLockout"
			case .userNotEnrolled:
				"userNotEnrolled"
			case .unknown:
				"unknown"
			@unknown default:
				"unknown"
		}
	}
}
