//! TPM (Trusted Platform Module) storage adapter
//!
//! This adapter implements the CredentialStorage trait using TPM 2.0 for secure credential storage.
//! Credentials are sealed using the TPM, ensuring they can only be unsealed on the same machine.
//!
//! The implementation uses:
//! - TPM2_CreatePrimary to create a primary storage key in the owner hierarchy
//! - TPM2_Create to seal the credential data under the primary key
//! - TPM2_Load and TPM2_Unseal to retrieve sealed credentials
//!
//! File structure: {storage_dir}/{rp_id}/{cred_id_hex}.tpm
//! Indexes are built by scanning directory structure (no unsealing needed)
//! Credentials are only unsealed when needed for authentication

pub mod init;

use crate::storage::index::{
    CredentialCache, CredentialIndexes, get_credential_path, load_credential_paths,
    update_indexes_on_delete, update_indexes_on_write,
};
use crate::storage::{CredentialFilter, CredentialStorage};

use soft_fido2::{Credential, CredentialRef, RelyingParty, Result};

use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Mutex;
use std::time::Instant;

use aes_gcm::aead::{Aead, KeyInit, OsRng};
use aes_gcm::{Aes256Gcm, Nonce};
use log::{debug, info};
use rand::RngCore;
use sha2::digest::generic_array::GenericArray;
use tss_esapi::constants::SessionType;
use tss_esapi::interface_types::algorithm::PublicAlgorithm;
use tss_esapi::interface_types::key_bits::RsaKeyBits;
use tss_esapi::interface_types::{algorithm::HashingAlgorithm, resource_handles::Hierarchy};
use tss_esapi::structures::{KeyedHashScheme, PublicKeyedHashParameters};
use tss_esapi::structures::{Public, PublicBuilder};
use tss_esapi::structures::{
    PublicKeyRsa, PublicRsaParametersBuilder, RsaExponent, RsaScheme, SymmetricDefinitionObject,
};
use tss_esapi::tss2_esys::{TPM2B_PRIVATE, TPM2B_PUBLIC};
use tss_esapi::{Context, Tcti};

/// TPM storage adapter
///
/// Stores credentials as TPM-sealed files in a directory structure.
/// File structure: {storage_dir}/{rp_id}/{cred_id_hex}.tpm
/// Uses indexes for efficient lookups without unsealing all credentials.
pub struct TpmStorageAdapter {
    storage_dir: PathBuf,
    indexes: CredentialIndexes,
    /// Time-limited cache: file_path -> (credential, expiry_time)
    cache: CredentialCache,
    iteration_index: usize,
    iteration_entries: Vec<PathBuf>,
    context: Mutex<Context>,
}

/// Represents a sealed credential blob stored on disk
#[derive(serde::Serialize, serde::Deserialize)]
struct SealedBlob {
    /// The encrypted credential data (AES-GCM encrypted)
    encrypted_data: Vec<u8>,
    /// The nonce used for AES-GCM encryption (96 bits)
    nonce: Vec<u8>,
    /// The private part of the TPM-sealed encryption key
    tpm_private: Vec<u8>,
    /// The public part of the TPM-sealed encryption key
    tpm_public: Vec<u8>,
}

impl TpmStorageAdapter {
    /// Create a new TPM storage adapter
    ///
    /// # Note
    ///
    /// If the storage directory does not exist, this will prompt the user
    /// via desktop notifications to create it.
    pub fn new(storage_dir: PathBuf, tcti: Option<String>) -> Result<Self> {
        info!("Using TPM 2.0 backend");
        info!("Storage path: {}", storage_dir.display());

        // Ensure the storage directory is initialized
        // This will prompt the user via notifications if not initialized
        self::init::ensure_initialized(&storage_dir).map_err(|_| soft_fido2::Error::Other)?;

        // Initialize TPM context
        let tcti_conf = if let Some(ref tcti_str) = tcti {
            info!("Using TCTI configuration: {}", tcti_str);
            Tcti::from_str(tcti_str).map_err(|e| {
                log::error!("Failed to parse TCTI configuration '{}': {}", tcti_str, e);
                log::error!("TCTI format examples:");
                log::error!("  - Hardware TPM: device:/dev/tpmrm0");
                log::error!("  - swtpm socket: swtpm:path=/path/to/socket");

                soft_fido2::Error::Other
            })?
        } else {
            // Default TCTI (device or simulator)
            info!("Using default TCTI: device:/dev/tpmrm0");
            Tcti::Device(Default::default())
        };

        let mut context = Context::new(tcti_conf).map_err(|e| {
            log::error!("Failed to create TPM context: {}", e);
            if let Some(ref tcti_str) = tcti {
                log::error!("TCTI used: {}", tcti_str);
            } else {
                log::error!("TCTI used: device:/dev/tpmrm0 (default)");
            }
            log::error!("Troubleshooting steps:");
            log::error!("  1. Verify TPM device exists: ls -l /dev/tpm*");
            log::error!("  2. Check permissions on TPM device");
            soft_fido2::Error::Other
        })?;

        // Start auth session for encryption
        let session = context
            .start_auth_session(
                None,
                None,
                None,
                SessionType::Hmac,
                SymmetricDefinitionObject::AES_128_CFB.into(),
                HashingAlgorithm::Sha256,
            )
            .map_err(|e| {
                log::error!("Failed to start TPM auth session: {}", e);
                soft_fido2::Error::Other
            })?
            .ok_or_else(|| {
                log::error!("TPM auth session returned None");
                soft_fido2::Error::Other
            })?;

        context.set_sessions((Some(session), None, None));

        info!("TPM context initialized successfully");

        // Load indexes by scanning directory structure (no unsealing!)
        let indexes = load_credential_paths(&storage_dir, "tpm").map_err(|e| {
            log::error!("Failed to load credential paths: {}", e);
            soft_fido2::Error::Other
        })?;

        let adapter = Self {
            storage_dir,
            indexes,
            cache: Default::default(),
            iteration_index: 0,
            iteration_entries: Vec::new(),
            context: Mutex::new(context),
        };

        Ok(adapter)
    }

    /// Create a primary storage key in the owner hierarchy
    ///
    /// This creates a transient primary key that can be used as a parent for sealing operations.
    /// The key is created with the same parameters each time, so it has the same handle.
    fn create_primary_key(&self, context: &mut Context) -> Result<tss_esapi::handles::KeyHandle> {
        // Create a primary storage key (RSA 2048, storage parent)
        let object_attributes = tss_esapi::attributes::ObjectAttributesBuilder::new()
            .with_fixed_tpm(true)
            .with_fixed_parent(true)
            .with_sensitive_data_origin(true)
            .with_user_with_auth(true)
            .with_decrypt(true)
            .with_restricted(true)
            .build()
            .map_err(|e| {
                log::error!("Failed to build object attributes: {}", e);
                soft_fido2::Error::Other
            })?;

        // For a storage key, we need to specify the symmetric algorithm used to encrypt child objects
        let rsa_params = PublicRsaParametersBuilder::new()
            .with_symmetric(SymmetricDefinitionObject::AES_128_CFB)
            .with_scheme(RsaScheme::Null)
            .with_key_bits(RsaKeyBits::Rsa2048)
            .with_exponent(RsaExponent::default())
            .with_is_signing_key(false)
            .with_is_decryption_key(true)
            .with_restricted(true)
            .build()
            .map_err(|e| {
                log::error!("Failed to build RSA parameters: {}", e);
                soft_fido2::Error::Other
            })?;

        let primary_pub = PublicBuilder::new()
            .with_public_algorithm(PublicAlgorithm::Rsa)
            .with_name_hashing_algorithm(HashingAlgorithm::Sha256)
            .with_object_attributes(object_attributes)
            .with_rsa_parameters(rsa_params)
            .with_rsa_unique_identifier(PublicKeyRsa::default())
            .build()
            .map_err(|e| {
                log::error!("Failed to build primary key public: {}", e);
                soft_fido2::Error::Other
            })?;

        let primary_key_result = context
            .create_primary(Hierarchy::Owner, primary_pub, None, None, None, None)
            .map_err(|e| {
                log::error!("Failed to create primary key: {}", e);
                soft_fido2::Error::Other
            })?;

        Ok(primary_key_result.key_handle)
    }

    /// Create a keyed hash object public structure for sealing data
    fn create_sealing_public(&self) -> Result<Public> {
        // For a sealing object, we need minimal attributes
        let object_attributes = tss_esapi::attributes::ObjectAttributesBuilder::new()
            .with_fixed_tpm(true)
            .with_fixed_parent(true)
            .with_user_with_auth(true)
            .build()
            .map_err(|e| {
                log::error!("Failed to build sealing object attributes: {}", e);
                soft_fido2::Error::Other
            })?;

        // Create keyed hash parameters for sealing (Null scheme)
        let keyed_hash_params = PublicKeyedHashParameters::new(KeyedHashScheme::Null);

        // Build the sealing object with required unique identifier
        PublicBuilder::new()
            .with_public_algorithm(PublicAlgorithm::KeyedHash)
            .with_name_hashing_algorithm(HashingAlgorithm::Sha256)
            .with_object_attributes(object_attributes)
            .with_keyed_hash_parameters(keyed_hash_params)
            .with_keyed_hash_unique_identifier(tss_esapi::structures::Digest::default())
            .build()
            .map_err(|e| {
                log::error!("Failed to build sealing object public: {}", e);
                soft_fido2::Error::Other
            })
    }

    /// Seal data using TPM with hybrid encryption
    ///
    /// Uses AES-256-GCM to encrypt the data, then seals only the encryption key with TPM.
    /// This avoids TPM size limits on sealed data (typically 128 bytes).
    fn seal_data(&self, data: &[u8]) -> Result<Vec<u8>> {
        debug!("Sealing {} bytes with TPM (hybrid encryption)", data.len());

        // Generate a random 256-bit AES key
        let mut aes_key = [0u8; 32];
        OsRng.fill_bytes(&mut aes_key);

        // Generate a random 96-bit nonce for AES-GCM
        let mut nonce_bytes = [0u8; 12];
        OsRng.fill_bytes(&mut nonce_bytes);
        let nonce = GenericArray::from_slice(&nonce_bytes);

        // Encrypt the credential data with AES-GCM
        let cipher = Aes256Gcm::new_from_slice(&aes_key).map_err(|e| {
            log::error!("Failed to create AES cipher: {}", e);
            soft_fido2::Error::Other
        })?;

        let encrypted_data = cipher.encrypt(nonce, data).map_err(|e| {
            log::error!("Failed to encrypt data with AES-GCM: {}", e);
            soft_fido2::Error::Other
        })?;

        debug!(
            "Encrypted {} bytes to {} bytes, now sealing 32-byte AES key with TPM",
            data.len(),
            encrypted_data.len()
        );

        // Now seal only the AES key (32 bytes) with TPM
        let mut context = self.context.lock().map_err(|e| {
            log::error!("Failed to lock TPM context: {}", e);
            soft_fido2::Error::Other
        })?;

        // Create primary key
        let primary_key = self.create_primary_key(&mut context)?;

        // Create sealing object public
        let sealing_pub = self.create_sealing_public()?;

        // Create the sealed object with the AES key
        let sensitive_data = tss_esapi::structures::SensitiveData::try_from(aes_key.to_vec())
            .map_err(|e| {
                log::error!("Failed to create sensitive data for AES key: {}", e);
                soft_fido2::Error::Other
            })?;

        let create_result = context
            .create(
                primary_key,
                sealing_pub,
                None,
                Some(sensitive_data),
                None,
                None,
            )
            .map_err(|e| {
                log::error!("Failed to create sealed object: {}", e);
                soft_fido2::Error::Other
            })?;

        // Flush the primary key (we'll recreate it when unsealing)
        context.flush_context(primary_key.into()).map_err(|e| {
            log::error!("Failed to flush primary key: {}", e);
            soft_fido2::Error::Other
        })?;

        let private_tpm: TPM2B_PRIVATE = create_result.out_private.into();
        // Store just the actual data, not the whole buffer
        let private_bytes = private_tpm.buffer[..private_tpm.size as usize].to_vec();

        #[allow(clippy::unnecessary_fallible_conversions)]
        let public_tpm: TPM2B_PUBLIC = create_result.out_public.try_into().map_err(|e| {
            log::error!("Failed to convert public to TPM2B: {:?}", e);
            soft_fido2::Error::Other
        })?;

        // For Public, we need to store the entire TPM2B_PUBLIC structure as bytes
        // because it has a complex nested structure
        let public_bytes = unsafe {
            let ptr = &public_tpm as *const TPM2B_PUBLIC as *const u8;
            std::slice::from_raw_parts(ptr, std::mem::size_of::<TPM2B_PUBLIC>()).to_vec()
        };

        let sealed_blob = SealedBlob {
            encrypted_data,
            nonce: nonce_bytes.to_vec(),
            tpm_private: private_bytes,
            tpm_public: public_bytes,
        };

        // Serialize the blob to JSON
        serde_json::to_vec(&sealed_blob).map_err(|e| {
            log::error!("Failed to serialize sealed blob: {}", e);
            soft_fido2::Error::Other
        })
    }

    /// Unseal data using TPM with hybrid decryption
    ///
    /// Unseals the AES key from TPM, then decrypts the data with AES-GCM.
    fn unseal_data(&self, sealed_data: &[u8]) -> Result<Vec<u8>> {
        debug!("Unsealing data with TPM (hybrid decryption)");

        let mut context = self.context.lock().map_err(|e| {
            log::error!("Failed to lock TPM context: {}", e);
            soft_fido2::Error::Other
        })?;

        let sealed_blob: SealedBlob = serde_json::from_slice(sealed_data).map_err(|e| {
            log::error!("Failed to deserialize sealed blob: {}", e);
            soft_fido2::Error::Other
        })?;

        let primary_key = self.create_primary_key(&mut context)?;

        let mut private_tpm = TPM2B_PRIVATE {
            size: sealed_blob.tpm_private.len() as u16,
            buffer: [0u8; 1550],
        };
        private_tpm.buffer[..sealed_blob.tpm_private.len()]
            .copy_from_slice(&sealed_blob.tpm_private);

        let public_tpm: TPM2B_PUBLIC = unsafe {
            let mut public_struct: TPM2B_PUBLIC = std::mem::zeroed();
            let ptr = &mut public_struct as *mut TPM2B_PUBLIC as *mut u8;
            std::ptr::copy_nonoverlapping(
                sealed_blob.tpm_public.as_ptr(),
                ptr,
                std::cmp::min(
                    sealed_blob.tpm_public.len(),
                    std::mem::size_of::<TPM2B_PUBLIC>(),
                ),
            );
            public_struct
        };

        // Convert to tss-esapi types
        let private = tss_esapi::structures::Private::try_from(private_tpm).map_err(|e| {
            log::error!("Failed to convert TPM2B_PRIVATE to Private: {}", e);
            soft_fido2::Error::Other
        })?;

        let public = tss_esapi::structures::Public::try_from(public_tpm).map_err(|e| {
            log::error!("Failed to convert TPM2B_PUBLIC to Public: {}", e);
            soft_fido2::Error::Other
        })?;

        // Load the sealed object
        let sealed_handle = context.load(primary_key, private, public).map_err(|e| {
            log::error!("Failed to load sealed object: {}", e);
            soft_fido2::Error::Other
        })?;

        // Unseal the AES key from TPM
        let unsealed_key = context.unseal(sealed_handle.into()).map_err(|e| {
            log::error!("Failed to unseal AES key: {}", e);
            soft_fido2::Error::Other
        })?;

        // Flush handles
        context.flush_context(sealed_handle.into()).map_err(|e| {
            log::error!("Failed to flush sealed handle: {}", e);
            soft_fido2::Error::Other
        })?;

        context.flush_context(primary_key.into()).map_err(|e| {
            log::error!("Failed to flush primary key: {}", e);
            soft_fido2::Error::Other
        })?;

        // Drop the mutex before AES decryption
        drop(context);

        // Extract the AES key
        let aes_key = unsealed_key.value();
        if aes_key.len() != 32 {
            log::error!(
                "Unsealed key has wrong size: {} (expected 32)",
                aes_key.len()
            );
            return Err(soft_fido2::Error::Other);
        }

        // Decrypt the data with AES-GCM
        let nonce = Nonce::from_slice(&sealed_blob.nonce);
        let cipher = Aes256Gcm::new_from_slice(aes_key).map_err(|e| {
            log::error!("Failed to create AES cipher: {}", e);
            soft_fido2::Error::Other
        })?;

        let decrypted_data = cipher
            .decrypt(nonce, sealed_blob.encrypted_data.as_ref())
            .map_err(|e| {
                log::error!("Failed to decrypt data with AES-GCM: {}", e);
                soft_fido2::Error::Other
            })?;

        debug!("Successfully decrypted {} bytes", decrypted_data.len());

        Ok(decrypted_data)
    }

    /// Read a credential from a specific file path WITHOUT caching
    /// Used for operations that need &self
    #[allow(dead_code)]
    fn read_credential_from_path_no_cache(&self, path: &Path) -> Result<Credential> {
        debug!("Reading credential (no cache) from path: {:?}", path);

        let mut file = File::open(path).map_err(|e| {
            log::error!("Failed to open credential file {}: {}", path.display(), e);
            soft_fido2::Error::DoesNotExist
        })?;

        let mut sealed_data = Vec::new();
        file.read_to_end(&mut sealed_data).map_err(|e| {
            log::error!("Failed to read credential file {}: {}", path.display(), e);
            soft_fido2::Error::Other
        })?;

        // Unseal the data using TPM
        let unsealed_data = self.unseal_data(&sealed_data)?;

        Credential::from_bytes(&unsealed_data)
    }

    /// Read a credential from a specific file path
    /// Uses time-limited cache to avoid redundant TPM unsealing
    fn read_credential_from_path(&mut self, path: &Path) -> Result<Credential> {
        if let Some(cached) = self.cache.get(path) {
            if Instant::now() < cached.expires_at {
                debug!("Cache HIT for path: {:?}", path);
                return Ok(cached.credential.clone());
            } else {
                debug!("Cache entry expired for path: {:?}", path);
            }
        }

        debug!(
            "Cache MISS - reading and unsealing credential from path: {:?}",
            path
        );

        // Evict expired entries before adding new one
        self.cache.evict_expired();

        // If cache is full, evict oldest entry
        self.cache.evict_oldest_if_full();

        let mut file = File::open(path).map_err(|e| {
            log::error!("Failed to open credential file {}: {}", path.display(), e);
            soft_fido2::Error::DoesNotExist
        })?;

        let mut sealed_data = Vec::new();
        file.read_to_end(&mut sealed_data).map_err(|e| {
            log::error!("Failed to read credential file {}: {}", path.display(), e);
            soft_fido2::Error::Other
        })?;

        // Unseal the data using TPM
        let unsealed_data = self.unseal_data(&sealed_data)?;

        let credential = Credential::from_bytes(&unsealed_data)?;

        // Cache the unsealed credential with automatic TTL
        self.cache.insert(path.to_path_buf(), credential.clone());

        Ok(credential)
    }

    /// Read a credential by its ID
    fn read_credential_by_id(&mut self, id: &[u8]) -> Result<Credential> {
        debug!("Reading credential by ID: {:02x?}", &id[..id.len().min(8)]);

        let path = self
            .indexes
            .id
            .get(id)
            .ok_or_else(|| {
                debug!("Credential not found in index");
                soft_fido2::Error::DoesNotExist
            })?
            .clone();

        self.read_credential_from_path(&path)
    }

    /// Load all credentials from storage (non-caching version)
    /// Used for operations that need &self
    #[allow(dead_code)]
    fn load_all_credentials_no_cache(&self) -> Vec<Credential> {
        debug!("Loading all credentials from storage (no cache)");
        let mut credentials = Vec::new();

        for path in self.indexes.id.values() {
            if let Ok(cred) = self.read_credential_from_path_no_cache(path) {
                credentials.push(cred);
            }
        }

        debug!("Loaded {} credentials", credentials.len());
        credentials
    }

    /// Find the next credential matching the current filter
    /// Uses indexes for efficient lookup
    fn find_next(&mut self) -> Result<Credential> {
        debug!(
            "Finding next credential (index: {}/{})",
            self.iteration_index,
            self.iteration_entries.len()
        );

        if self.iteration_index >= self.iteration_entries.len() {
            debug!("No more credentials matching filter");
            return Err(soft_fido2::Error::DoesNotExist);
        }

        let path = self.iteration_entries[self.iteration_index].clone();
        self.iteration_index += 1;

        self.read_credential_from_path(&path)
    }

    /// Write a credential to storage
    /// Uses new directory structure: {storage_dir}/{rp_id}/{cred_id_hex}.tpm
    fn write_credential(&mut self, cred: &Credential) -> Result<()> {
        self.cache.evict_expired();

        let path = get_credential_path(&self.storage_dir, &cred.rp.id, &cred.id, "tpm");
        debug!("Writing credential to: {:?}", path);

        // Ensure parent directory exists
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent).map_err(|e| {
                debug!("Failed to create directory: {}", e);
                soft_fido2::Error::Other
            })?;
        }

        let bytes = cred.to_bytes()?;

        // Seal the data using TPM
        let sealed_data = self.seal_data(&bytes)?;

        let mut file = File::create(&path).map_err(|e| {
            log::error!("Failed to create credential file {}: {}", path.display(), e);
            soft_fido2::Error::Other
        })?;

        file.write_all(&sealed_data).map_err(|e| {
            log::error!("Failed to write credential file {}: {}", path.display(), e);
            soft_fido2::Error::Other
        })?;

        debug!("Successfully wrote and sealed credential");

        // Invalidate cache entry for this credential to ensure fresh reads
        self.cache.remove(&path);

        // Update all indexes using shared function
        update_indexes_on_write(
            &mut self.indexes,
            path,
            cred.id.to_vec(),
            cred.rp.id.clone(),
        );

        Ok(())
    }

    /// Delete a credential from storage
    fn delete_credential(&mut self, id: &[u8]) -> Result<()> {
        self.cache.evict_expired();

        debug!(
            "Deleting credential with ID: {:02x?}",
            &id[..id.len().min(8)]
        );

        let path = self
            .indexes
            .id
            .get(id)
            .ok_or_else(|| {
                debug!("Credential not found in index");
                soft_fido2::Error::DoesNotExist
            })?
            .clone();

        // Read credential to get RP info for index cleanup
        let cred = self.read_credential_from_path(&path)?;

        // Delete the file
        std::fs::remove_file(&path).map_err(|e| {
            debug!("Failed to delete file: {}", e);
            soft_fido2::Error::Other
        })?;

        // Remove from cache
        self.cache.remove(&path);

        // Remove from all indexes using shared function
        update_indexes_on_delete(&mut self.indexes, &path, id, &cred.rp.id);

        debug!("Successfully deleted credential");

        Ok(())
    }
}

impl CredentialStorage for TpmStorageAdapter {
    fn read_first(&mut self, filter: CredentialFilter) -> Result<Credential> {
        self.cache.evict_expired();

        debug!("read_first called with filter: {:?}", filter);

        // Initialize iteration using the appropriate index
        self.iteration_entries = match &filter {
            CredentialFilter::None => {
                // No filter: iterate all credentials
                self.indexes.id.values().cloned().collect()
            }
            CredentialFilter::ById(id) => {
                // ById: direct lookup in index
                if let Some(path) = self.indexes.id.get(id.as_slice()) {
                    vec![path.clone()]
                } else {
                    Vec::new()
                }
            }
            CredentialFilter::ByRp(rp_id) => {
                // ByRp: lookup in index
                if let Some(paths) = self.indexes.rp.get(rp_id.as_str()) {
                    paths.clone()
                } else {
                    Vec::new()
                }
            }
            CredentialFilter::ByHash(hash) => {
                // ByHash: lookup in index
                if let Some(paths) = self.indexes.rp_hash.get(hash) {
                    paths.clone()
                } else {
                    Vec::new()
                }
            }
        };

        self.iteration_index = 0;

        debug!(
            "Starting iteration with {} entries for filter: {:?}",
            self.iteration_entries.len(),
            filter
        );

        self.find_next()
    }

    fn read_next(&mut self) -> Result<Credential> {
        self.cache.evict_expired();

        debug!("read_next called");
        self.find_next()
    }

    fn read(&mut self, id: &[u8], _rp: &str) -> Result<Vec<u8>> {
        self.cache.evict_expired();

        debug!("read called with id: {}", hex::encode(id));

        let cred = self.read_credential_by_id(id)?;

        cred.to_bytes()
    }

    fn write(&mut self, _id: &[u8], _rp: &str, cred_ref: CredentialRef) -> Result<()> {
        self.cache.evict_expired();

        debug!("write called for RP: {}", cred_ref.rp_id);

        // Just save the credential as provided by soft-fido2
        // This is called both during registration (new credential) and
        // during authentication (updating sign counter)
        let credential = cred_ref.to_owned();
        self.write_credential(&credential)
    }

    fn delete(&mut self, id: &[u8]) -> Result<()> {
        self.cache.evict_expired();

        debug!("delete called with id: {}", hex::encode(id));
        self.delete_credential(id)
    }

    fn select_users(&self, rp_id: &str) -> Vec<String> {
        debug!("select_users called for RP: {}", rp_id);

        let credentials = self.load_all_credentials_no_cache();
        let users: Vec<String> = credentials
            .iter()
            .filter(|cred| cred.rp.id == rp_id)
            .map(|cred| String::from_utf8_lossy(&cred.user.id).to_string())
            .collect();

        debug!("Found {} users for RP: {}", users.len(), rp_id);
        users
    }

    fn count_credentials(&self) -> usize {
        let count = self.indexes.id.len();
        debug!("count_credentials: {}", count);
        count
    }

    fn get_relying_parties(&self) -> Result<Vec<RelyingParty>> {
        debug!("get_relying_parties called");

        let credentials = self.load_all_credentials_no_cache();

        let rp_list: Vec<RelyingParty> = credentials.into_iter().map(|c| c.rp).collect();
        debug!("Found {} relying parties", rp_list.len());
        Ok(rp_list)
    }

    fn disable_user_verification(&self) -> bool {
        // TPM backend supports user verification
        false
    }

    fn cleanup_expired_cache(&mut self) {
        self.cache.evict_expired();
    }
}
