<?php
/**
 * Gestione delle credenziali WebAuthn salvate in wp_usermeta.
 *
 * Struttura dati: ogni utente può avere PIÙ dispositivi registrati
 * (es. smartphone personale + tablet ufficio), quindi le credenziali
 * sono salvate come array JSON in un unico meta_key, non come singolo
 * valore. Questo evita di dover creare tabelle custom e resta
 * compatibile con l'export/import standard di WordPress.
 *
 * Formato di ogni credenziale salvata:
 * [
 *   'credential_id'   => string (base64url, come restituito dal browser),
 *   'public_key'      => string (COSE key, base64),
 *   'sign_count'      => int    (contatore anti-replay),
 *   'label'           => string (es. "iPhone di Carlo"),
 *   'transports'      => array  (es. ['internal'], ['usb','nfc']),
 *   'created_at'      => int    (timestamp Unix),
 *   'last_used_at'    => int|null,
 * ]
 *
 * @package SPIDy_WP
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class SPIDy_WP_Credential_Store {

	/**
	 * Meta key unico dove sono salvate tutte le credenziali dell'utente.
	 */
	const META_KEY = '_spidy_wp_webauthn_credentials';

	/**
	 * Restituisce tutte le credenziali registrate per un utente.
	 *
	 * @param int $user_id ID utente WordPress.
	 * @return array Elenco di credenziali (array associativi).
	 */
	public static function get_credentials( int $user_id ): array {
		$raw = get_user_meta( $user_id, self::META_KEY, true );

		if ( empty( $raw ) || ! is_array( $raw ) ) {
			return array();
		}

		return $raw;
	}

	/**
	 * Verifica rapida: l'utente ha almeno un dispositivo biometrico attivo?
	 * Usata in Fase 3 per decidere se intercettare il login standard.
	 */
	public static function user_has_credentials( int $user_id ): bool {
		return count( self::get_credentials( $user_id ) ) > 0;
	}

	/**
	 * Recupera una singola credenziale tramite il suo Credential ID.
	 * Usata in Fase 3/4 per recuperare la chiave pubblica al momento
	 * della verifica del login.
	 *
	 * @param int    $user_id       ID utente.
	 * @param string $credential_id Credential ID (base64url) da cercare.
	 * @return array|null La credenziale trovata, o null.
	 */
	public static function get_credential_by_id( int $user_id, string $credential_id ): ?array {
		foreach ( self::get_credentials( $user_id ) as $credential ) {
			if ( hash_equals( $credential['credential_id'], $credential_id ) ) {
				return $credential;
			}
		}
		return null;
	}

	/**
	 * Salva una nuova credenziale dopo una registrazione riuscita
	 * (navigator.credentials.create() -> Fase 2).
	 *
	 * @param int    $user_id       ID utente.
	 * @param string $credential_id Credential ID (base64url).
	 * @param string $public_key    Chiave pubblica (COSE, base64).
	 * @param int    $sign_count    Contatore iniziale restituito dal device.
	 * @param string $label         Etichetta leggibile scelta dall'utente.
	 * @param array  $transports    Trasporti supportati dal device.
	 * @return bool True se salvata correttamente.
	 */
	public static function add_credential(
		int $user_id,
		string $credential_id,
		string $public_key,
		int $sign_count = 0,
		string $label = '',
		array $transports = array(),
		string $source_json = ''
	): bool {

		$credentials = self::get_credentials( $user_id );

		// Evita duplicati se lo stesso Credential ID viene inviato due volte.
		foreach ( $credentials as $existing ) {
			if ( hash_equals( $existing['credential_id'], $credential_id ) ) {
				return false;
			}
		}

		$credentials[] = array(
			'credential_id' => $credential_id,
			'public_key'    => $public_key,
			'sign_count'    => $sign_count,
			'label'         => $label ? sanitize_text_field( $label ) : __( 'Dispositivo senza nome', 'spidy-2fa-login' ),
			'transports'    => array_map( 'sanitize_text_field', $transports ),
			'created_at'    => time(),
			'last_used_at'  => null,
			'source_json'   => $source_json, // Usato da web-auth/webauthn-framework per riverificare la firma al login.
		);

		return (bool) update_user_meta( $user_id, self::META_KEY, $credentials );
	}

	/**
	 * Aggiorna il source_json (e implicitamente il sign counter in esso
	 * contenuto) dopo una verifica di login riuscita.
	 */
	public static function update_source_json( int $user_id, string $credential_id, string $source_json ): bool {
		$credentials = self::get_credentials( $user_id );
		$updated     = false;

		foreach ( $credentials as &$credential ) {
			if ( hash_equals( $credential['credential_id'], $credential_id ) ) {
				$credential['source_json']  = $source_json;
				$credential['last_used_at'] = time();
				$updated                    = true;
				break;
			}
		}
		unset( $credential );

		if ( ! $updated ) {
			return false;
		}

		return (bool) update_user_meta( $user_id, self::META_KEY, $credentials );
	}

	/**
	 * Aggiorna il contatore anti-replay dopo un login riuscito (Fase 4).
	 * Il valore ricevuto dal device deve SEMPRE essere maggiore
	 * dell'ultimo salvato: in caso contrario la firma va rifiutata
	 * a monte, prima ancora di chiamare questa funzione (indice di
	 * possibile clonazione del dispositivo).
	 *
	 * @param int    $user_id           ID utente.
	 * @param string $credential_id     Credential ID aggiornato.
	 * @param int    $new_sign_count    Nuovo valore del contatore.
	 * @return bool
	 */
	public static function update_sign_count( int $user_id, string $credential_id, int $new_sign_count ): bool {
		$credentials = self::get_credentials( $user_id );
		$updated     = false;

		foreach ( $credentials as &$credential ) {
			if ( hash_equals( $credential['credential_id'], $credential_id ) ) {
				$credential['sign_count']   = $new_sign_count;
				$credential['last_used_at'] = time();
				$updated                    = true;
				break;
			}
		}
		unset( $credential );

		if ( ! $updated ) {
			return false;
		}

		return (bool) update_user_meta( $user_id, self::META_KEY, $credentials );
	}

	/**
	 * Rimuove una credenziale (es. l'utente perde lo smartphone e la
	 * elimina manualmente dal profilo, oppure il supporto la rimuove
	 * dopo aver usato SPIDY_WP_BYPASS_BIOMETRIC).
	 *
	 * @param int    $user_id       ID utente.
	 * @param string $credential_id Credential ID da rimuovere.
	 * @return bool
	 */
	public static function remove_credential( int $user_id, string $credential_id ): bool {
		$credentials = self::get_credentials( $user_id );
		$filtered    = array_values(
			array_filter(
				$credentials,
				function ( $credential ) use ( $credential_id ) {
					return ! hash_equals( $credential['credential_id'], $credential_id );
				}
			)
		);

		if ( count( $filtered ) === count( $credentials ) ) {
			return false; // Nulla da rimuovere.
		}

		if ( empty( $filtered ) ) {
			return delete_user_meta( $user_id, self::META_KEY );
		}

		return (bool) update_user_meta( $user_id, self::META_KEY, $filtered );
	}
}
