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

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