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

import type { CertificateChainValidationResult } from './CertificateChainValidationResult';
import type { SecurityLevel } from './SecurityLevel';

enum FidoUafAttestationInformationType {
	OnlySurrogateBasicSupported,
	OnlyDefaultMode,
	StrictMode,
	StrictStrongBoxMode,
}

/**
 * The interface informing about whether the device supports {@link https://docs.nevis.net/configurationguide/mobile-auth-concept-and-integration-guide/use-cases-and-best-practices/uaf-surrogate-full-basic-comparison | Full Basic Attestations}.
 *
 * If full basic is required by the backend during registration, and this device does not support it,
 * registration will fail. This information can be used to preemptively inform the user that the device
 * is not supported.
 *
 * Note that it is guaranteed that the only type of instances that the {@link AndroidDeviceCapabilities.fidoUafAttestationInformationGetter}
 * returns are either {@link OnlySurrogateBasicSupported}, {@link OnlyDefaultMode}, {@link StrictMode} or {@link StrictStrongBoxMode}.
 *
 * @group Common Objects
 * @see
 *  - {@link MobileAuthenticationClient.deviceCapabilities}
 *  - {@link AndroidDeviceCapabilities.fidoUafAttestationInformationGetter}
 *  - {@link FidoUafAttestationInformationGetter}
 */
export abstract class FidoUafAttestationInformation {
	/**
	 * The {@link SecurityLevel} of the environment where the FIDO UAF keys are stored.
	 * This provides information about the security of the environment where the keys are stored.
	 */
	abstract keymasterSecurityLevel: SecurityLevel;

	/**
	 * The {@link https://source.android.com/docs/security/features/keystore/attestation#keydescription-fields | keymaster} version.
	 */
	abstract keymasterVersion: number;

	/**
	 * Returns `true` if the {@link https://source.android.com/docs/security/features/keystore/attestation#rootoftrust-fields | device's bootloader}
	 * is locked, and `false` otherwise.
	 */
	abstract isDeviceBootloaderLocked: boolean;

	/**
	 * Returns `true` if the {@link https://source.android.com/docs/security/features/keystore/attestation#verifiedbootstate-values | boot
	 * state} is `Verified`. Compromised devices (such as some root devices) do not have a valid
	 * boot state.
	 */
	abstract isVerifiedBootStateValid: boolean;

	/**
	 * The result of the certificate chain validation.
	 *
	 * In devices supporting full basic attestation ({@link OnlyDefaultMode}, {@link StrictMode} or
	 * {@link StrictStrongBoxMode}), when a new key is created the device must generate an associated
	 * certificate chain (or certification path) that fulfills the following criteria:
	 * <ul>
	 *     <li>The root certificate is a known {@link https://developer.android.com/privacy-and-security/security-key-attestation#root_certificate | Google
	 *      root certificate}.</li>
	 *     <li>The certificate chain is valid: it does not contain a certificate in the CRL, no certificate
	 *      is expired, the certificates in the chain are signed with the previous one, etc.</li>
	 * </ul>
	 * So, when a device supports full basic, returns {@link CertificateChainValidationResult.Success}.
	 */
	abstract certificateChainValidationResult: CertificateChainValidationResult;

	/**
	 * Alternate constructor that creates a {@link FidoUafAttestationInformation} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns a {@link FidoUafAttestationInformation} instance.
	 */
	static fromJson(json: any): FidoUafAttestationInformation {
		const subtype =
			FidoUafAttestationInformationType[
				json.type as keyof typeof FidoUafAttestationInformationType
			];
		switch (subtype) {
			case FidoUafAttestationInformationType.OnlySurrogateBasicSupported:
				return OnlySurrogateBasicSupported.fromJson(json.data);
			case FidoUafAttestationInformationType.OnlyDefaultMode:
				return OnlyDefaultMode.fromJson(json.data);
			case FidoUafAttestationInformationType.StrictMode:
				return StrictMode.fromJson(json.data);
			case FidoUafAttestationInformationType.StrictStrongBoxMode:
				return StrictStrongBoxMode.fromJson(json.data);
			default:
				throw new Error(`Unknown FIDO UAF attestation information (${json.type}).`);
		}
	}
}

/**
 * Only the surrogate basic attestation is supported.
 *
 * So, neither the `default`, the `strict` nor the `strict-strongbox` modes of full basic attestation
 * are supported (see the {@link https://docs.nevis.net/configurationguide/patterns-reference#basic-full-attestation | nevisFIDO documentation}
 * for details regarding the different modes).
 *
 * Supporting only surrogate basic attestation implies that the certificate chain of the device could
 * not be successfully validated (see {@link certificateChainValidationResult}). This occurs typically
 * in old devices, and devices that do not contain a Google root certificate (like some Huawei models).
 *
 * @group Common Objects
 */
export abstract class OnlySurrogateBasicSupported extends FidoUafAttestationInformation {
	/**
	 * The error that occurred while checking if the full basic attestation is supported.
	 *
	 * Its message provides information about why the device does not support full basic attestation.
	 */
	abstract cause?: string;

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

export class OnlySurrogateBasicSupportedImpl extends OnlySurrogateBasicSupported {
	keymasterSecurityLevel: SecurityLevel;
	keymasterVersion: number;
	isDeviceBootloaderLocked: boolean;
	isVerifiedBootStateValid: boolean;
	certificateChainValidationResult: CertificateChainValidationResult;
	cause: string | undefined;

	constructor(
		keymasterSecurityLevel: SecurityLevel,
		keymasterVersion: number,
		isDeviceBootloaderLocked: boolean,
		isVerifiedBootStateValid: boolean,
		certificateChainValidationResult: CertificateChainValidationResult,
		cause: string | undefined
	) {
		super();
		this.keymasterSecurityLevel = keymasterSecurityLevel;
		this.keymasterVersion = keymasterVersion;
		this.isDeviceBootloaderLocked = isDeviceBootloaderLocked;
		this.isVerifiedBootStateValid = isVerifiedBootStateValid;
		this.certificateChainValidationResult = certificateChainValidationResult;
		this.cause = cause;
	}

	static fromJson(json: any): OnlySurrogateBasicSupportedImpl {
		return new OnlySurrogateBasicSupportedImpl(
			json.keymasterSecurityLevel,
			json.keymasterVersion,
			json.isDeviceBootloaderLocked,
			json.isVerifiedBootStateValid,
			json.certificateChainValidationResult,
			json.cause
		);
	}
}

/**
 * The device supports the `default` full basic attestation mode as described in the {@link https://docs.nevis.net/configurationguide/patterns-reference#basic-full-attestation | nevisFIDO documentation}.
 *
 * It supports surrogate basic attestation, but it does not support neither the `strict` nor the
 * `strict-strongbox` modes.
 *
 * This means that the certificate chain was successfully validated, and thus {@link certificateChainValidationResult}
 * returns {@link CertificateChainValidationResult.Success}. The device contains hardware using a known
 * Google root certificate, but does not fulfill at least one of the other criteria required to be
 * compatible with {@link StrictMode} or {@link StrictStrongBoxMode}.
 *
 * @group Common Objects
 */
export abstract class OnlyDefaultMode extends FidoUafAttestationInformation {
	/**
	 * The error that occurred while checking if the strict mode of the full basic attestation is
	 * supported.
	 *
	 * Its message provides information about why the device does not support the full basic attestation
	 * strict mode.
	 */
	abstract cause?: string;

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

export class OnlyDefaultModeImpl extends OnlyDefaultMode {
	keymasterSecurityLevel: SecurityLevel;
	keymasterVersion: number;
	isDeviceBootloaderLocked: boolean;
	isVerifiedBootStateValid: boolean;
	certificateChainValidationResult: CertificateChainValidationResult;
	cause: string | undefined;

	constructor(
		keymasterSecurityLevel: SecurityLevel,
		keymasterVersion: number,
		isDeviceBootloaderLocked: boolean,
		isVerifiedBootStateValid: boolean,
		certificateChainValidationResult: CertificateChainValidationResult,
		cause: string | undefined
	) {
		super();
		this.keymasterSecurityLevel = keymasterSecurityLevel;
		this.keymasterVersion = keymasterVersion;
		this.isDeviceBootloaderLocked = isDeviceBootloaderLocked;
		this.isVerifiedBootStateValid = isVerifiedBootStateValid;
		this.certificateChainValidationResult = certificateChainValidationResult;
		this.cause = cause;
	}

	static fromJson(json: any): OnlyDefaultModeImpl {
		return new OnlyDefaultModeImpl(
			json.keymasterSecurityLevel,
			json.keymasterVersion,
			json.isDeviceBootloaderLocked,
			json.isVerifiedBootStateValid,
			json.certificateChainValidationResult,
			json.cause
		);
	}
}

/**
 * The device supports the `default` and `strict` full basic attestation modes as described in the
 * {@link https://docs.nevis.net/configurationguide/component-installation-guides/nevisFido/fido-uaf-configuration#full-basic-attestation | nevisFIDO documentation}.
 * It also supports surrogate basic.
 *
 * However, since it does not have a StrongBox that the SDK can use to store the FIDO UAF credentials,
 * the `strict-strongbox` mode is not supported.
 *
 * If the device supports this mode, then:
 * - The FIDO UAF keys will be stored in a Trusted Execution Environment (TEE), and thus {@link keymasterSecurityLevel}
 * will be {@link SecurityLevel.TrustedEnvironment}.
 * - The certificate chain was successfully validated, and thus {@link certificateChainValidationResult}
 *      returns {@link CertificateChainValidationResult.Success}.
 * - The value of {@link keymasterVersion} is 2 or higher.
 * - The verified boot state is valid ({@link isVerifiedBootStateValid} returns `true`).
 * - The device bootloader is locked ({@link isDeviceBootloaderLocked} returns `true`).
 *
 * @group Common Objects
 */
export abstract class StrictMode extends FidoUafAttestationInformation {
	/**
	 * Alternate constructor that creates a {@link StrictMode} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link StrictMode} instance.
	 */
	static fromJson(json: any): StrictMode {
		return StrictModeImpl.fromJson(json);
	}
}

export class StrictModeImpl extends StrictMode {
	keymasterSecurityLevel: SecurityLevel;
	keymasterVersion: number;
	isDeviceBootloaderLocked: boolean;
	isVerifiedBootStateValid: boolean;
	certificateChainValidationResult: CertificateChainValidationResult;

	constructor(
		keymasterSecurityLevel: SecurityLevel,
		keymasterVersion: number,
		isDeviceBootloaderLocked: boolean,
		isVerifiedBootStateValid: boolean,
		certificateChainValidationResult: CertificateChainValidationResult
	) {
		super();
		this.keymasterSecurityLevel = keymasterSecurityLevel;
		this.keymasterVersion = keymasterVersion;
		this.isDeviceBootloaderLocked = isDeviceBootloaderLocked;
		this.isVerifiedBootStateValid = isVerifiedBootStateValid;
		this.certificateChainValidationResult = certificateChainValidationResult;
	}

	static fromJson(json: any): StrictModeImpl {
		return new StrictModeImpl(
			json.keymasterSecurityLevel,
			json.keymasterVersion,
			json.isDeviceBootloaderLocked,
			json.isVerifiedBootStateValid,
			json.certificateChainValidationResult
		);
	}
}

/**
 * The device supports the `default`, `strict` and `strict-strongbox` full basic attestation modes as
 * described in the {@link https://docs.nevis.net/configurationguide/component-installation-guides/nevisFido/fido-uaf-configuration#full-basic-attestation | nevisFIDO documentation}.
 * It also supports surrogate basic.
 *
 * If the device supports this mode, then:
 * - The FIDO UAF keys will be stored in the StrongBox, and thus {@link keymasterSecurityLevel} will
 * be {@link SecurityLevel.StrongBox}.
 * - The certificate chain was successfully validated, and thus {@link certificateChainValidationResult}
 * returns {@link CertificateChainValidationResult.Success}.
 * - The value of {@link keymasterVersion} is 2 or higher.
 * - The verified boot state is valid ({@link isVerifiedBootStateValid} returns `true`).
 * - The device bootloader is locked ({@link isDeviceBootloaderLocked} returns `true`).
 *
 * @group Common Objects
 */
export abstract class StrictStrongBoxMode extends FidoUafAttestationInformation {
	/**
	 * Alternate constructor that creates a {@link StrictStrongBoxMode} from a json.
	 *
	 * @param json contains the source for instance creation.
	 * @returns the created {@link StrictStrongBoxMode} instance.
	 */
	static fromJson(json: any): StrictStrongBoxMode {
		return StrictStrongBoxModeImpl.fromJson(json);
	}
}

export class StrictStrongBoxModeImpl extends StrictStrongBoxMode {
	keymasterSecurityLevel: SecurityLevel;
	keymasterVersion: number;
	isDeviceBootloaderLocked: boolean;
	isVerifiedBootStateValid: boolean;
	certificateChainValidationResult: CertificateChainValidationResult;

	constructor(
		keymasterSecurityLevel: SecurityLevel,
		keymasterVersion: number,
		isDeviceBootloaderLocked: boolean,
		isVerifiedBootStateValid: boolean,
		certificateChainValidationResult: CertificateChainValidationResult
	) {
		super();
		this.keymasterSecurityLevel = keymasterSecurityLevel;
		this.keymasterVersion = keymasterVersion;
		this.isDeviceBootloaderLocked = isDeviceBootloaderLocked;
		this.isVerifiedBootStateValid = isVerifiedBootStateValid;
		this.certificateChainValidationResult = certificateChainValidationResult;
	}

	static fromJson(json: any): StrictStrongBoxModeImpl {
		return new StrictStrongBoxModeImpl(
			json.keymasterSecurityLevel,
			json.keymasterVersion,
			json.isDeviceBootloaderLocked,
			json.isVerifiedBootStateValid,
			json.certificateChainValidationResult
		);
	}
}
