//
// 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"
		}
	}
}
