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

import NevisMobileAuthentication

struct TypedDeviceInformationMismatch: Codable {
	// MARK: Properties

	let wrapped: DeviceInformationMismatch

	// MARK: Initialization

	init(wrapped: DeviceInformationMismatch) {
		self.wrapped = wrapped
	}

	// MARK: Coding Keys

	enum DeviceInformationMismatchType: String, Encodable {
		case MissingAuthenticatorInMobileDevice
		case MissingAuthenticatorInServer
		case MissingDeviceInformationInMobileDevice
		case MissingDeviceInformationInServer
		case DeviceNameMismatch
		case FcmRegistrationTokenMismatch
	}

	enum CodingKeys: String, CodingKey {
		case mismatches
		case errors
	}

	// MARK: Decodable

	init(from decoder: Decoder) throws {
		let container = try decoder.container(keyedBy: TypedCodingKeys.self)
		let type = try container.decode(String.self, forKey: .type)
		switch type {
			case DeviceInformationMismatchType.MissingAuthenticatorInMobileDevice.rawValue:
				let dataContainer = try container.nestedContainer(
					keyedBy: DeviceInformationMismatch.CodingKeys.MissingAuthenticatorInMobileDevice.self,
					forKey: .data
				)
				let keyId = try dataContainer.decode(String.self, forKey: .keyId)
				let aaid = try dataContainer.decode(String.self, forKey: .aaid)
				let server = try dataContainer.decode(TypedServer.self, forKey: .server)
				self.wrapped = .MissingAuthenticatorInMobileDevice(keyId: keyId, aaid: aaid, server: server)
			case DeviceInformationMismatchType.MissingAuthenticatorInServer.rawValue:
				let dataContainer = try container.nestedContainer(
					keyedBy: DeviceInformationMismatch.CodingKeys.MissingAuthenticatorInServer.self,
					forKey: .data
				)
				let keyId = try dataContainer.decode(String.self, forKey: .keyId)
				let account = try dataContainer.decode(TypedAccount.self, forKey: .account)
				let aaid = try dataContainer.decode(String.self, forKey: .aaid)
				self.wrapped = .MissingAuthenticatorInServer(keyId: keyId, account: account, aaid: aaid)
			case DeviceInformationMismatchType.MissingDeviceInformationInMobileDevice.rawValue:
				let dataContainer = try container.nestedContainer(
					keyedBy: DeviceInformationMismatch.CodingKeys.MissingDeviceInformationInMobileDevice.self,
					forKey: .data
				)
				let dispatchTargetId = try dataContainer.decode(String.self, forKey: .dispatchTargetId)
				let server = try dataContainer.decode(TypedServer.self, forKey: .server)
				self.wrapped = .MissingDeviceInformationInMobileDevice(dispatchTargetId: dispatchTargetId, server: server)
			case DeviceInformationMismatchType.MissingDeviceInformationInServer.rawValue:
				let dataContainer = try container.nestedContainer(
					keyedBy: DeviceInformationMismatch.CodingKeys.MissingDeviceInformationInServer.self,
					forKey: .data
				)
				let idUsernamePair = try dataContainer.decode(DeviceInformation.IdUsernamePair.self, forKey: .idUsernamePair)
				let server = try dataContainer.decode(TypedServer.self, forKey: .server)
				self.wrapped = .MissingDeviceInformationInServer(idUsernamePair: idUsernamePair, server: server)
			case DeviceInformationMismatchType.DeviceNameMismatch.rawValue:
				let dataContainer = try container.nestedContainer(
					keyedBy: DeviceInformationMismatch.CodingKeys.DeviceNameMismatch.self,
					forKey: .data
				)
				let nameInServer = try dataContainer.decode(String.self, forKey: .nameInServer)
				let nameInMobileDevice = try dataContainer.decode(String.self, forKey: .nameInMobileDevice)
				let server = try dataContainer.decode(TypedServer.self, forKey: .server)
				self.wrapped = .DeviceNameMismatch(nameInServer: nameInServer, nameInMobileDevice: nameInMobileDevice, server: server)
			case DeviceInformationMismatchType.FcmRegistrationTokenMismatch.rawValue:
				let dataContainer = try container.nestedContainer(
					keyedBy: DeviceInformationMismatch.CodingKeys.FcmRegistrationTokenMismatch.self,
					forKey: .data
				)
				let fcmRegistrationTokenInServer = try dataContainer.decodeIfPresent(String.self, forKey: .fcmRegistrationTokenInServer)
				let fcmRegistrationTokenInMobileDevice = try dataContainer.decodeIfPresent(String.self, forKey: .fcmRegistrationTokenInMobileDevice)
				let server = try dataContainer.decode(TypedServer.self, forKey: .server)
				self.wrapped = .FcmRegistrationTokenMismatch(fcmRegistrationTokenInServer: fcmRegistrationTokenInServer, fcmRegistrationTokenInMobileDevice: fcmRegistrationTokenInMobileDevice, server: server)
			default:
				throw DecodingError.dataCorrupted(
					DecodingError.Context(
						codingPath: container.codingPath,
						debugDescription: "Failed to decode device information mismatch!"
					)
				)
		}
	}

	// MARK: Encodable

	func encode(to encoder: Encoder) throws {
		var container = encoder.container(keyedBy: TypedCodingKeys.self)
		switch wrapped {
			case .MissingAuthenticatorInMobileDevice:
				try container.encode(DeviceInformationMismatchType.MissingAuthenticatorInMobileDevice, forKey: .type)
			case .MissingAuthenticatorInServer:
				try container.encode(DeviceInformationMismatchType.MissingAuthenticatorInServer, forKey: .type)
			case .MissingDeviceInformationInMobileDevice:
				try container.encode(DeviceInformationMismatchType.MissingDeviceInformationInMobileDevice, forKey: .type)
			case .MissingDeviceInformationInServer:
				try container.encode(DeviceInformationMismatchType.MissingDeviceInformationInServer, forKey: .type)
			case .DeviceNameMismatch:
				try container.encode(DeviceInformationMismatchType.DeviceNameMismatch, forKey: .type)
			case .FcmRegistrationTokenMismatch:
				try container.encode(DeviceInformationMismatchType.FcmRegistrationTokenMismatch, forKey: .type)
			default:
				throw EncodingError.invalidValue(
					wrapped,
					EncodingError.Context(
						codingPath: container.codingPath,
						debugDescription: "Failed to encode device information mismatch!"
					)
				)
		}

		try wrapped.encode(to: container.superEncoder(forKey: .data))
	}
}

extension DeviceInformationMismatch {
	enum CodingKeys {
		enum MissingAuthenticatorInMobileDevice: CodingKey {
			case keyId
			case aaid
			case server
		}

		enum MissingAuthenticatorInServer: CodingKey {
			case keyId
			case account
			case aaid
		}

		enum MissingDeviceInformationInMobileDevice: CodingKey {
			case dispatchTargetId
			case server
		}

		enum MissingDeviceInformationInServer: CodingKey {
			case idUsernamePair
			case server
		}

		enum DeviceNameMismatch: CodingKey {
			case nameInServer
			case nameInMobileDevice
			case server
		}

		enum FcmRegistrationTokenMismatch: CodingKey {
			case fcmRegistrationTokenInServer
			case fcmRegistrationTokenInMobileDevice
			case server
		}
	}
}
