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

import { Account } from '../../localData/Account';
import { IdUserNamePair } from '../../localData/IdUserNamePair';
import { Server } from '../../localData/Server';

enum DeviceInformationMismatchType {
	MissingAuthenticatorInMobileDevice,
	MissingAuthenticatorInServer,
	MissingDeviceInformationInMobileDevice,
	MissingDeviceInformationInServer,
	DeviceNameMismatch,
	FcmRegistrationTokenMismatch,
}

/**
 * The configuration mismatches between the server and the mobile device application.
 *
 * @see {@link DeviceInformationCheck}
 * @see {@link DeviceInformationSync}
 */
export abstract class DeviceInformationMismatch {
	/**
	 * Alternate constructor that creates a {@link DeviceInformationMismatch} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns a {@link DeviceInformationMismatch} instance.
	 */
	static fromJson(json: any): DeviceInformationMismatch {
		const subtype =
			DeviceInformationMismatchType[json.type as keyof typeof DeviceInformationMismatchType];
		switch (subtype) {
			case DeviceInformationMismatchType.MissingAuthenticatorInMobileDevice:
				return MissingAuthenticatorInMobileDevice.fromJson(json.data);
			case DeviceInformationMismatchType.MissingAuthenticatorInServer:
				return MissingAuthenticatorInServer.fromJson(json.data);
			case DeviceInformationMismatchType.MissingDeviceInformationInMobileDevice:
				return MissingDeviceInformationInMobileDevice.fromJson(json.data);
			case DeviceInformationMismatchType.MissingDeviceInformationInServer:
				return MissingDeviceInformationInServer.fromJson(json.data);
			case DeviceInformationMismatchType.DeviceNameMismatch:
				return DeviceNameMismatch.fromJson(json.data);
			case DeviceInformationMismatchType.FcmRegistrationTokenMismatch:
				return FcmRegistrationTokenMismatch.fromJson(json.data);
			default:
				throw new Error(`Unknown device information mismatch (${json.type}).`);
		}
	}
}

/**
 * An authenticator is registered in the server for an account, but not in the mobile device application.
 *
 * Fixing this mismatch when invoking {@link DeviceInformationSync} will remove the authenticator
 * from the server.
 */
export abstract class MissingAuthenticatorInMobileDevice extends DeviceInformationMismatch {
	/**
	 * The key ID of the FIDO UAF credentials associated with the missing authenticator.
	 *
	 * It is encoded in base 64 URL.
	 */
	abstract keyId: string;

	/**
	 * The AAID of the missing authenticator.
	 */
	abstract aaid: string;

	/**
	 * The server where the mismatch occurs.
	 */
	abstract server: Server;

	/**
	 * Default constructor for {@link MissingAuthenticatorInMobileDevice}.
	 *
	 * @param keyId the key ID of the FIDO UAF credentials associated with the missing authenticator.
	 * @param aaid the AAID of the missing authenticator.
	 * @param server the server where the mismatch occurs.
	 * @returns the created {@link MissingAuthenticatorInMobileDevice} instance.
	 */
	static create(keyId: string, aaid: string, server: Server): MissingAuthenticatorInMobileDevice {
		return new MissingAuthenticatorInMobileDeviceImpl(keyId, aaid, server);
	}

	/**
	 * Alternate constructor that creates a {@link MissingAuthenticatorInMobileDevice} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link MissingAuthenticatorInMobileDevice} instance.
	 */
	static fromJson(json: any): MissingAuthenticatorInMobileDevice {
		return MissingAuthenticatorInMobileDeviceImpl.fromJson(json);
	}
}

export class MissingAuthenticatorInMobileDeviceImpl extends MissingAuthenticatorInMobileDevice {
	keyId: string;
	aaid: string;
	server: Server;

	constructor(keyId: string, aaid: string, server: Server) {
		super();
		this.keyId = keyId;
		this.aaid = aaid;
		this.server = server;
	}

	static fromJson(json: any): MissingAuthenticatorInMobileDeviceImpl {
		const server = Server.fromJson(json.server);
		return new MissingAuthenticatorInMobileDeviceImpl(json.keyId, json.aaid, server);
	}
}

/**
 * An authenticator is registered in the mobile device application for an account, but not in the server.
 *
 * Fixing this mismatch when invoking {@link DeviceInformationSync} will remove the authenticator
 * in the SDK. If this is the latest authenticator for the account, the device information for the account
 * will also be removed from the application and the server.
 */
export abstract class MissingAuthenticatorInServer extends DeviceInformationMismatch {
	/**
	 * The key ID of the FIDO UAF credentials associated with the missing authenticator.
	 *
	 * It is encoded in base 64 URL.
	 */
	abstract keyId: string;

	/**
	 * The account associated with the missing authenticator.
	 */
	abstract account: Account;

	/**
	 * The AAID of the missing authenticator.
	 */
	abstract aaid: string;

	/**
	 * Default constructor for {@link MissingAuthenticatorInServer}.
	 *
	 * @param keyId the key ID of the FIDO UAF credentials associated with the missing authenticator.
	 * @param account the account associated with the missing authenticator.
	 * @param aaid the AAID of the missing authenticator.
	 * @returns the created {@link MissingAuthenticatorInServer} instance.
	 */
	static create(keyId: string, account: Account, aaid: string): MissingAuthenticatorInServer {
		return new MissingAuthenticatorInServerImpl(keyId, account, aaid);
	}

	/**
	 * Alternate constructor that creates a {@link MissingAuthenticatorInServer} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link MissingAuthenticatorInServer} instance.
	 */
	static fromJson(json: any): MissingAuthenticatorInServer {
		return MissingAuthenticatorInServerImpl.fromJson(json);
	}
}

export class MissingAuthenticatorInServerImpl extends MissingAuthenticatorInServer {
	keyId: string;
	account: Account;
	aaid: string;

	constructor(keyId: string, account: Account, aaid: string) {
		super();
		this.keyId = keyId;
		this.account = account;
		this.aaid = aaid;
	}

	static fromJson(json: any): MissingAuthenticatorInServerImpl {
		const account = Account.fromJson(json.account);
		return new MissingAuthenticatorInServerImpl(json.keyId, account, json.aaid);
	}
}

/**
 * The device information for the given {@link IdUserNamePair} is defined in the server but not in
 * the mobile device application.
 *
 * Fixing this mismatch when invoking {@link DeviceInformationSync} will remove the information associated
 * with the `IdUsernamePair` from the server.
 */
export abstract class MissingDeviceInformationInMobileDevice extends DeviceInformationMismatch {
	/**
	 * The identifier of the dispatch target that is missing in the mobile device.
	 */
	abstract dispatchTargetId: string;

	/**
	 * The server where the mismatch occurs.
	 */
	abstract server: Server;

	/**
	 * Default constructor for {@link MissingDeviceInformationInMobileDevice}.
	 *
	 * @param dispatchTargetId the identifier of the dispatch target that is missing in the mobile device.
	 * @param server the server where the mismatch occurs.
	 * @returns the created {@link MissingDeviceInformationInMobileDevice} instance.
	 */
	static create(
		dispatchTargetId: string,
		server: Server
	): MissingDeviceInformationInMobileDevice {
		return new MissingDeviceInformationInMobileDeviceImpl(dispatchTargetId, server);
	}

	/**
	 * Alternate constructor that creates a {@link MissingDeviceInformationInMobileDevice} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link MissingDeviceInformationInMobileDevice} instance.
	 */
	static fromJson(json: any): MissingDeviceInformationInMobileDevice {
		return MissingDeviceInformationInMobileDeviceImpl.fromJson(json);
	}
}

export class MissingDeviceInformationInMobileDeviceImpl extends MissingDeviceInformationInMobileDevice {
	dispatchTargetId: string;
	server: Server;

	constructor(dispatchTargetId: string, server: Server) {
		super();
		this.dispatchTargetId = dispatchTargetId;
		this.server = server;
	}

	static fromJson(json: any): MissingDeviceInformationInMobileDeviceImpl {
		const server = Server.fromJson(json.server);
		return new MissingDeviceInformationInMobileDeviceImpl(json.dispatchTargetId, server);
	}
}

/**
 * The device information for the given {@link IdUserNamePair} is defined in the SDK but not in the
 * server.
 *
 * This mismatch cannot be solved, providing it to the {@link DeviceInformationSync#mismatches(Set)}
 * method will have no effect.
 */
export abstract class MissingDeviceInformationInServer extends DeviceInformationMismatch {
	/**
	 * The {@link IdUserNamePair} that is missing in the server.
	 */
	abstract idUsernamePair: IdUserNamePair;

	/**
	 * The server where the mismatch occurs.
	 */
	abstract server: Server;

	/**
	 * Default constructor for {@link MissingDeviceInformationInServer}.
	 *
	 * @param idUsernamePair the {@link IdUserNamePair} that is missing in the server.
	 * @param server the server where the mismatch occurs.
	 * @returns the created {@link MissingDeviceInformationInServer} instance.
	 */
	static create(
		idUsernamePair: IdUserNamePair,
		server: Server
	): MissingDeviceInformationInServer {
		return new MissingDeviceInformationInServerImpl(idUsernamePair, server);
	}

	/**
	 * Alternate constructor that creates a {@link MissingDeviceInformationInServer} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link MissingDeviceInformationInServer} instance.
	 */
	static fromJson(json: any): MissingDeviceInformationInServer {
		return MissingDeviceInformationInServerImpl.fromJson(json);
	}
}

export class MissingDeviceInformationInServerImpl extends MissingDeviceInformationInServer {
	idUsernamePair: IdUserNamePair;
	server: Server;

	constructor(idUsernamePair: IdUserNamePair, server: Server) {
		super();
		this.idUsernamePair = idUsernamePair;
		this.server = server;
	}

	static fromJson(json: any): MissingDeviceInformationInServerImpl {
		const server = Server.fromJson(json.server);
		const idUsernamePair = IdUserNamePair.fromJson(json.idUsernamePair);
		return new MissingDeviceInformationInServerImpl(idUsernamePair, server);
	}
}

/**
 * There is a mismatch between the name of the device in the server and the one in the mobile device
 * application.
 *
 * Fixing this mismatch when invoking {@link DeviceInformationSync} will update the values
 * in all servers with the values in the mobile device application.
 *
 * If you rather have the value in the server to be used, you can provide the name returned
 * by {@link nameInServer}, and execute a {@link DeviceInformationChange} operation with
 * that value.
 */
export abstract class DeviceNameMismatch extends DeviceInformationMismatch {
	/**
	 * The name of the device in the server.
	 */
	abstract nameInServer: string;

	/**
	 * The name of the device in the mobile application (that is, as returned by {@link DeviceInformation.name}
	 * through {@link LocalData.deviceInformation}).
	 */
	abstract nameInMobileDevice: string;

	/**
	 * The server where the mismatch occurs.
	 */
	abstract server: Server;

	/**
	 * Default constructor for {@link DeviceNameMismatch}.
	 *
	 * @param nameInServer the name of the device in the server.
	 * @param nameInMobileDevice the name of the device in the mobile application.
	 * @param server the server where the mismatch occurs.
	 * @returns the created {@link DeviceNameMismatch} instance.
	 */
	static create(
		nameInServer: string,
		nameInMobileDevice: string,
		server: Server
	): DeviceNameMismatch {
		return new DeviceNameMismatchImpl(nameInServer, nameInMobileDevice, server);
	}

	/**
	 * Alternate constructor that creates a {@link DeviceNameMismatch} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link DeviceNameMismatch} instance.
	 */
	static fromJson(json: any): DeviceNameMismatch {
		return DeviceNameMismatchImpl.fromJson(json);
	}
}

export class DeviceNameMismatchImpl extends DeviceNameMismatch {
	nameInServer: string;
	nameInMobileDevice: string;
	server: Server;

	constructor(nameInServer: string, nameInMobileDevice: string, server: Server) {
		super();
		this.nameInServer = nameInServer;
		this.nameInMobileDevice = nameInMobileDevice;
		this.server = server;
	}

	static fromJson(json: any): DeviceNameMismatchImpl {
		const server = Server.fromJson(json.server);
		return new DeviceNameMismatchImpl(json.nameInServer, json.nameInMobileDevice, server);
	}
}

/**
 * There is a mismatch between the firebase registration token in the server and the one in the mobile
 * device application.
 *
 * Fixing this mismatch when invoking {@link DeviceInformationSync} will update the values in the
 * server with the values in the mobile device application.
 *
 * <If you rather have the value in the server to be used, you can provide the registration
 * token returned by {@link fcmRegistrationTokenInServer}, and execute a {@link DeviceInformationChange}
 * operation with that value.
 */
export abstract class FcmRegistrationTokenMismatch extends DeviceInformationMismatch {
	/**
	 * The firebase registration token in the server.
	 */
	abstract fcmRegistrationTokenInServer?: string;

	/**
	 * The firebase registration token in the mobile device application (that is, as returned by
	 * {@link DeviceInformation.fcmRegistrationToken} through {@link LocalData.deviceInformation}).
	 */
	abstract fcmRegistrationTokenInMobileDevice?: string;

	/**
	 * The server where the mismatch occurs.
	 */
	abstract server: Server;

	/**
	 * Default constructor for {@link FcmRegistrationTokenMismatch}.
	 *
	 * @param server the server where the mismatch occurs.
	 * @param fcmRegistrationTokenInServer the firebase registration token in the server.
	 * @param fcmRegistrationTokenInMobileDevice the firebase registration token in the mobile device application.
	 * @returns the created {@link FcmRegistrationTokenMismatch} instance.
	 */
	static create(
		server: Server,
		fcmRegistrationTokenInServer?: string,
		fcmRegistrationTokenInMobileDevice?: string
	): FcmRegistrationTokenMismatch {
		return new FcmRegistrationTokenMismatchImpl(
			server,
			fcmRegistrationTokenInServer,
			fcmRegistrationTokenInMobileDevice
		);
	}

	/**
	 * Alternate constructor that creates an {@link FcmRegistrationTokenMismatch} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link FcmRegistrationTokenMismatch} instance.
	 */
	static fromJson(json: any): FcmRegistrationTokenMismatch {
		return FcmRegistrationTokenMismatchImpl.fromJson(json);
	}
}

export class FcmRegistrationTokenMismatchImpl extends FcmRegistrationTokenMismatch {
	fcmRegistrationTokenInServer?: string;
	fcmRegistrationTokenInMobileDevice?: string;
	server: Server;

	constructor(
		server: Server,
		fcmRegistrationTokenInServer?: string,
		fcmRegistrationTokenInMobileDevice?: string
	) {
		super();
		this.fcmRegistrationTokenInServer = fcmRegistrationTokenInServer;
		this.fcmRegistrationTokenInMobileDevice = fcmRegistrationTokenInMobileDevice;
		this.server = server;
	}

	static fromJson(json: any): FcmRegistrationTokenMismatchImpl {
		const server = Server.fromJson(json.server);
		return new FcmRegistrationTokenMismatchImpl(
			server,
			json.fcmRegistrationTokenInServer,
			json.fcmRegistrationTokenInMobileDevice
		);
	}
}
