//! Secure credential storage module
//!
//! Provides secure storage for access tokens using:
//! - Primary: System keyring (Secret Service on Linux, Keychain on macOS)
//! - Fallback: AES-256-GCM encrypted file when keyring unavailable
//!
//! The fallback is less secure as the encryption key is derived from machine
//! identifiers, but provides functionality on headless systems.
//!
//! TRACES: UR-012 | IR-014

use aes_gcm::{
    aead::{Aead, KeyInit},
    Aes256Gcm, Nonce,
};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use directories::ProjectDirs;
use log::{info, warn};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::PathBuf;

#[cfg(not(target_os = "android"))]
const SERVICE_NAME: &str = "com.dtourolle.jellytau";

const CREDENTIALS_FILENAME: &str = "credentials.enc";

/// Result of a credential storage operation
#[derive(Debug)]
pub enum CredentialResult {
    /// Operation succeeded using the system keyring
    Keyring,
    /// Operation succeeded using encrypted file fallback
    EncryptedFile,
}

/// Error types for credential operations
#[derive(Debug)]
pub enum CredentialError {
    /// Keyring operation failed
    Keyring(String),
    /// Encryption/decryption failed
    Encryption(String),
    /// File I/O failed
    Io(String),
    /// Credential not found
    NotFound,
}

impl std::fmt::Display for CredentialError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Keyring(msg) => write!(f, "Keyring error: {}", msg),
            Self::Encryption(msg) => write!(f, "Encryption error: {}", msg),
            Self::Io(msg) => write!(f, "I/O error: {}", msg),
            Self::NotFound => write!(f, "Credential not found"),
        }
    }
}

impl std::error::Error for CredentialError {}

/// Credential storage manager
pub struct CredentialStore {
    /// Whether we're using keyring (true) or encrypted file (false)
    using_keyring: bool,
    /// Path to the encrypted credentials file (fallback)
    credentials_path: PathBuf,
    /// Encryption key for file fallback (derived from machine ID)
    encryption_key: [u8; 32],
}

impl CredentialStore {
    /// Create a new credential store, detecting the best available backend
    pub fn new() -> Self {
        let credentials_path = Self::get_credentials_path();
        let encryption_key = Self::derive_encryption_key();

        // Test if keyring is available by trying a dummy operation
        let using_keyring = Self::test_keyring_available();

        if !using_keyring {
            warn!(
                "[INIT] System keyring unavailable, using encrypted file fallback at {:?}. \
                 This is less secure than system keyring storage.",
                credentials_path
            );
        } else {
            info!("[INIT] Using system keyring for credential storage");
        }

        Self {
            using_keyring,
            credentials_path,
            encryption_key,
        }
    }

    /// Check if we're using the secure keyring backend
    pub fn is_using_keyring(&self) -> bool {
        self.using_keyring
    }

    /// Save an access token for a user
    pub fn save_token(
        &self,
        user_id: &str,
        token: &str,
    ) -> Result<CredentialResult, CredentialError> {
        if self.using_keyring {
            log::debug!("Saving token for user {} to keyring", user_id);
            self.save_to_keyring(user_id, token)?;
            Ok(CredentialResult::Keyring)
        } else {
            log::debug!(
                "Saving token for user {} to encrypted file at {:?}",
                user_id,
                self.credentials_path
            );
            self.save_to_file(user_id, token)?;
            log::debug!("Successfully saved token to encrypted file");
            Ok(CredentialResult::EncryptedFile)
        }
    }

    /// Get an access token for a user
    pub fn get_token(&self, user_id: &str) -> Result<String, CredentialError> {
        if self.using_keyring {
            log::debug!("Getting token for user {} from keyring", user_id);
            self.get_from_keyring(user_id)
        } else {
            log::debug!(
                "Getting token for user {} from encrypted file at {:?}",
                user_id,
                self.credentials_path
            );
            let result = self.get_from_file(user_id);
            if result.is_ok() {
                log::debug!("Successfully retrieved token from encrypted file");
            } else {
                log::warn!("Failed to retrieve token from encrypted file: {:?}", result);
            }
            result
        }
    }

    /// Delete an access token for a user
    pub fn delete_token(&self, user_id: &str) -> Result<(), CredentialError> {
        if self.using_keyring {
            self.delete_from_keyring(user_id)
        } else {
            self.delete_from_file(user_id)
        }
    }

    // --- Keyring backend ---

    fn test_keyring_available() -> bool {
        // On Android, use Android Keystore via JNI
        #[cfg(target_os = "android")]
        {
            android_test_keystore_available()
        }

        // On Linux, the keyring test can block indefinitely if Secret Service
        // (gnome-keyring/kwallet) is unresponsive. Use a timeout to prevent hanging.
        #[cfg(target_os = "linux")]
        {
            use std::sync::mpsc;
            use std::thread;
            use std::time::Duration;

            let (tx, rx) = mpsc::channel();

            thread::spawn(move || {
                let result = Self::test_keyring_inner();
                let _ = tx.send(result);
            });

            // Wait up to 2 seconds for keyring response
            match rx.recv_timeout(Duration::from_secs(2)) {
                Ok(result) => result,
                Err(_) => {
                    log::warn!("Keyring availability check timed out after 2 seconds");
                    false
                }
            }
        }

        #[cfg(all(not(target_os = "linux"), not(target_os = "android")))]
        {
            Self::test_keyring_inner()
        }
    }

    #[cfg(target_os = "linux")]
    fn test_keyring_inner() -> bool {
        // On Linux, test if secret-tool is available
        use std::process::Command;

        // secret-tool doesn't support --version, so we test with a search command
        // that will succeed even if no items are found
        Command::new("secret-tool")
            .arg("search")
            .arg("service")
            .arg("__nonexistent_test__")
            .output()
            .is_ok()
    }

    #[cfg(all(not(target_os = "android"), not(target_os = "linux")))]
    fn test_keyring_inner() -> bool {
        // On macOS/Windows, test using the keyring-rs library
        let entry = keyring::Entry::new(SERVICE_NAME, "__test__");
        match entry {
            Ok(e) => {
                // Try to get (will fail with NotFound, which is fine)
                // If it fails with a different error, keyring is not available
                match e.get_password() {
                    Ok(_) => true,
                    Err(keyring::Error::NoEntry) => true,
                    Err(keyring::Error::NoStorageAccess(_)) => false,
                    Err(_) => false,
                }
            }
            Err(_) => false,
        }
    }

    fn save_to_keyring(&self, user_id: &str, token: &str) -> Result<(), CredentialError> {
        // On Android, use Android Keystore via JNI
        #[cfg(target_os = "android")]
        {
            android_keystore::save_token(user_id, token)
        }

        #[cfg(target_os = "linux")]
        {
            // Use secret-tool directly on Linux as a workaround for keyring-rs library issues
            // See Technical Debt section in README.md for details
            use std::io::Write;
            use std::process::{Command, Stdio};

            let key = format!("access_token:{}", user_id);
            let mut child = Command::new("secret-tool")
                .arg("store")
                .arg("--label")
                .arg(format!("{}@{}", key, SERVICE_NAME))
                .arg("service")
                .arg(SERVICE_NAME)
                .arg("username")
                .arg(&key)
                .stdin(Stdio::piped())
                .stdout(Stdio::null())
                .stderr(Stdio::null())
                .spawn()
                .map_err(|e| {
                    CredentialError::Keyring(format!("Failed to spawn secret-tool: {}", e))
                })?;

            if let Some(mut stdin) = child.stdin.take() {
                stdin.write_all(token.as_bytes()).map_err(|e| {
                    CredentialError::Keyring(format!("Failed to write to secret-tool: {}", e))
                })?;
            }

            let status = child.wait().map_err(|e| {
                CredentialError::Keyring(format!("Failed to wait for secret-tool: {}", e))
            })?;

            if status.success() {
                Ok(())
            } else {
                Err(CredentialError::Keyring(format!(
                    "secret-tool failed with status: {}",
                    status
                )))
            }
        }

        #[cfg(all(not(target_os = "linux"), not(target_os = "android")))]
        {
            let key = format!("access_token:{}", user_id);
            let entry = keyring::Entry::new(SERVICE_NAME, &key)
                .map_err(|e| CredentialError::Keyring(e.to_string()))?;
            entry
                .set_password(token)
                .map_err(|e| CredentialError::Keyring(e.to_string()))
        }
    }

    fn get_from_keyring(&self, user_id: &str) -> Result<String, CredentialError> {
        // On Android, use Android Keystore via JNI
        #[cfg(target_os = "android")]
        {
            android_keystore::get_token(user_id)
        }

        #[cfg(target_os = "linux")]
        {
            // Use secret-tool directly on Linux as a workaround for keyring-rs library issues
            // See Technical Debt section in README.md for details
            use std::process::Command;

            let key = format!("access_token:{}", user_id);
            log::debug!(
                "Looking up token with service={}, username={}",
                SERVICE_NAME,
                key
            );

            let output = Command::new("secret-tool")
                .arg("lookup")
                .arg("service")
                .arg(SERVICE_NAME)
                .arg("username")
                .arg(&key)
                .output()
                .map_err(|e| {
                    CredentialError::Keyring(format!("Failed to run secret-tool: {}", e))
                })?;

            if output.status.success() {
                log::debug!(
                    "secret-tool lookup succeeded, token length: {}",
                    output.stdout.len()
                );
                let token = String::from_utf8(output.stdout)
                    .map_err(|e| {
                        CredentialError::Keyring(format!("Invalid UTF-8 in token: {}", e))
                    })?
                    .trim()
                    .to_string();
                Ok(token)
            } else {
                let stderr = String::from_utf8_lossy(&output.stderr);
                log::warn!(
                    "secret-tool lookup failed with status: {} stderr: {}",
                    output.status,
                    stderr
                );
                Err(CredentialError::NotFound)
            }
        }

        #[cfg(all(not(target_os = "linux"), not(target_os = "android")))]
        {
            let key = format!("access_token:{}", user_id);
            let entry = keyring::Entry::new(SERVICE_NAME, &key)
                .map_err(|e| CredentialError::Keyring(e.to_string()))?;
            entry.get_password().map_err(|e| match e {
                keyring::Error::NoEntry => CredentialError::NotFound,
                _ => CredentialError::Keyring(e.to_string()),
            })
        }
    }

    fn delete_from_keyring(&self, user_id: &str) -> Result<(), CredentialError> {
        // On Android, use Android Keystore via JNI
        #[cfg(target_os = "android")]
        {
            android_keystore::delete_token(user_id)
        }

        #[cfg(target_os = "linux")]
        {
            // Use secret-tool directly on Linux as a workaround for keyring-rs library issues
            // See Technical Debt section in README.md for details
            use std::process::Command;

            let key = format!("access_token:{}", user_id);
            let status = Command::new("secret-tool")
                .arg("clear")
                .arg("service")
                .arg(SERVICE_NAME)
                .arg("username")
                .arg(&key)
                .status()
                .map_err(|e| {
                    CredentialError::Keyring(format!("Failed to run secret-tool: {}", e))
                })?;

            // secret-tool clear returns success even if entry doesn't exist
            if status.success() {
                Ok(())
            } else {
                Err(CredentialError::Keyring(format!(
                    "secret-tool clear failed with status: {}",
                    status
                )))
            }
        }

        #[cfg(all(not(target_os = "linux"), not(target_os = "android")))]
        {
            let key = format!("access_token:{}", user_id);
            let entry = keyring::Entry::new(SERVICE_NAME, &key)
                .map_err(|e| CredentialError::Keyring(e.to_string()))?;
            match entry.delete_credential() {
                Ok(_) => Ok(()),
                Err(keyring::Error::NoEntry) => Ok(()), // Already deleted
                Err(e) => Err(CredentialError::Keyring(e.to_string())),
            }
        }
    }

    // --- Encrypted file backend ---

    fn get_credentials_path() -> PathBuf {
        if let Some(proj_dirs) = ProjectDirs::from("com", "dtourolle", "jellytau") {
            proj_dirs.data_dir().join(CREDENTIALS_FILENAME)
        } else {
            PathBuf::from(CREDENTIALS_FILENAME)
        }
    }

    fn derive_encryption_key() -> [u8; 32] {
        // Derive a key from machine-specific identifiers
        // This is less secure than a true keyring but provides some protection
        let mut hasher = Sha256::new();

        // Use hostname on Linux (where it's available and stable)
        #[cfg(target_os = "linux")]
        {
            if let Ok(hostname) = hostname::get() {
                hasher.update(hostname.to_string_lossy().as_bytes());
            }
        }

        // On Android, read device properties from the filesystem
        #[cfg(target_os = "android")]
        {
            // Try to read Android build properties from /system/build.prop
            let build_prop_paths = ["/system/build.prop", "/vendor/build.prop"];

            for path in &build_prop_paths {
                if let Ok(content) = fs::read_to_string(path) {
                    // Extract key properties for device fingerprint
                    for line in content.lines() {
                        if line.starts_with("ro.build.fingerprint=")
                            || line.starts_with("ro.serialno=")
                            || line.starts_with("ro.build.id=")
                            || line.starts_with("ro.product.model=")
                        {
                            hasher.update(line.as_bytes());
                        }
                    }
                }
            }

            // Also use the app data directory path as it's device/install-specific
            if let Some(proj_dirs) = ProjectDirs::from("com", "dtourolle", "jellytau") {
                hasher.update(proj_dirs.data_dir().to_string_lossy().as_bytes());
            }
        }

        // Use a static salt (app-specific)
        hasher.update(b"jellytau-credential-encryption-v1");

        // Add username for additional entropy (if available)
        if let Ok(user) = std::env::var("USER").or_else(|_| std::env::var("USERNAME")) {
            hasher.update(user.as_bytes());
        }

        hasher.finalize().into()
    }

    /// Load and decrypt the credential map.
    ///
    /// A file that is present but **undecryptable** is deliberately reported as
    /// an *empty* credential set rather than as an error. The key never leaves
    /// the device it was derived on (Android Keystore keys are never backed up,
    /// and the file fallback's key is derived from machine identifiers), so a
    /// restored/transferred install gets ciphertext with no key and every read
    /// would fail *permanently*. Surfacing that as an error made session restore
    /// throw instead of falling back to the login screen: an unrecoverable app
    /// rather than a clean logged-out one. The next successful login re-encrypts
    /// the file with the current key, so the state self-heals.
    ///
    /// TRACES: UR-012 | IR-014
    fn load_credentials_file(&self) -> Result<serde_json::Value, CredentialError> {
        if !self.credentials_path.exists() {
            return Ok(serde_json::json!({}));
        }

        let encrypted_data = fs::read_to_string(&self.credentials_path)
            .map_err(|e| CredentialError::Io(e.to_string()))?;

        if encrypted_data.is_empty() {
            return Ok(serde_json::json!({}));
        }

        let decrypted = match self.decrypt(&encrypted_data) {
            Ok(decrypted) => decrypted,
            Err(e) => {
                warn!(
                    "Credentials file at {:?} exists but cannot be decrypted ({}); \
                     treating as no stored credentials. This is expected after a \
                     backup restore or device transfer - the encryption key does \
                     not travel with the data. Signing in again will rewrite it.",
                    self.credentials_path, e
                );
                return Ok(serde_json::json!({}));
            }
        };

        match serde_json::from_str(&decrypted) {
            Ok(value) => Ok(value),
            Err(e) => {
                warn!(
                    "Credentials file at {:?} decrypted to invalid JSON ({}); \
                     treating as no stored credentials.",
                    self.credentials_path, e
                );
                Ok(serde_json::json!({}))
            }
        }
    }

    fn save_credentials_file(&self, data: &serde_json::Value) -> Result<(), CredentialError> {
        // Ensure parent directory exists
        if let Some(parent) = self.credentials_path.parent() {
            fs::create_dir_all(parent).map_err(|e| CredentialError::Io(e.to_string()))?;
        }

        let json =
            serde_json::to_string(data).map_err(|e| CredentialError::Encryption(e.to_string()))?;
        let encrypted = self.encrypt(&json)?;

        fs::write(&self.credentials_path, encrypted).map_err(|e| CredentialError::Io(e.to_string()))
    }

    fn encrypt(&self, plaintext: &str) -> Result<String, CredentialError> {
        let cipher = Aes256Gcm::new_from_slice(&self.encryption_key)
            .map_err(|e| CredentialError::Encryption(e.to_string()))?;

        // Generate a random nonce
        let mut nonce_bytes = [0u8; 12];
        getrandom::getrandom(&mut nonce_bytes)
            .map_err(|e| CredentialError::Encryption(e.to_string()))?;
        let nonce = Nonce::from_slice(&nonce_bytes);

        let ciphertext = cipher
            .encrypt(nonce, plaintext.as_bytes())
            .map_err(|e| CredentialError::Encryption(e.to_string()))?;

        // Prepend nonce to ciphertext and encode as base64
        let mut combined = nonce_bytes.to_vec();
        combined.extend(ciphertext);

        Ok(BASE64.encode(&combined))
    }

    fn decrypt(&self, encrypted: &str) -> Result<String, CredentialError> {
        let combined = BASE64
            .decode(encrypted)
            .map_err(|e| CredentialError::Encryption(e.to_string()))?;

        if combined.len() < 12 {
            return Err(CredentialError::Encryption(
                "Invalid encrypted data".to_string(),
            ));
        }

        let (nonce_bytes, ciphertext) = combined.split_at(12);
        let nonce = Nonce::from_slice(nonce_bytes);

        let cipher = Aes256Gcm::new_from_slice(&self.encryption_key)
            .map_err(|e| CredentialError::Encryption(e.to_string()))?;

        let plaintext = cipher
            .decrypt(nonce, ciphertext)
            .map_err(|e| CredentialError::Encryption(e.to_string()))?;

        String::from_utf8(plaintext).map_err(|e| CredentialError::Encryption(e.to_string()))
    }

    fn save_to_file(&self, user_id: &str, token: &str) -> Result<(), CredentialError> {
        let mut data = self.load_credentials_file()?;
        data[user_id] = serde_json::json!(token);
        self.save_credentials_file(&data)
    }

    fn get_from_file(&self, user_id: &str) -> Result<String, CredentialError> {
        let data = self.load_credentials_file()?;
        data.get(user_id)
            .and_then(|v| v.as_str())
            .map(|s| s.to_string())
            .ok_or(CredentialError::NotFound)
    }

    fn delete_from_file(&self, user_id: &str) -> Result<(), CredentialError> {
        let mut data = self.load_credentials_file()?;
        if let Some(obj) = data.as_object_mut() {
            obj.remove(user_id);
        }
        self.save_credentials_file(&data)
    }
}

impl Default for CredentialStore {
    fn default() -> Self {
        Self::new()
    }
}

// --- Android Keystore integration via JNI ---

#[cfg(target_os = "android")]
mod android_keystore {
    use super::*;
    use jni::objects::{JClass, JObject, JString, JValue};
    use jni::JNIEnv;
    use std::sync::OnceLock;

    /// Cached reference to the SecureStorage class
    static SECURE_STORAGE_CLASS: OnceLock<String> = OnceLock::new();

    const SECURE_STORAGE_CLASS_NAME: &str = "com/dtourolle/jellytau/security/SecureStorage";

    /// Initialize the SecureStorage singleton from Android context
    pub fn initialize_secure_storage(env: &mut JNIEnv, context: &JObject) -> Result<(), String> {
        log::info!("Initializing Android SecureStorage...");

        // Get the ClassLoader from the Context
        let class_loader = env
            .call_method(context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])
            .map_err(|e| format!("Failed to get ClassLoader: {}", e))?
            .l()
            .map_err(|e| format!("Failed to convert ClassLoader: {}", e))?;

        // Load the SecureStorage class
        let class_name = env
            .new_string(SECURE_STORAGE_CLASS_NAME.replace('/', "."))
            .map_err(|e| format!("Failed to create class name string: {}", e))?;

        let storage_class_obj = env
            .call_method(
                &class_loader,
                "loadClass",
                "(Ljava/lang/String;)Ljava/lang/Class;",
                &[JValue::Object(&class_name.into())],
            )
            .map_err(|e| format!("Failed to load SecureStorage class: {}", e))?
            .l()
            .map_err(|e| format!("Failed to convert to Class: {}", e))?;

        let storage_class = JClass::from(storage_class_obj);

        // Call SecureStorage.initialize(context)
        env.call_static_method(
            &storage_class,
            "initialize",
            "(Landroid/content/Context;)V",
            &[JValue::Object(context)],
        )
        .map_err(|e| format!("Failed to initialize SecureStorage: {}", e))?;

        // Cache the class name for future use
        let _ = SECURE_STORAGE_CLASS.set(SECURE_STORAGE_CLASS_NAME.to_string());

        log::info!("Android SecureStorage initialized successfully");
        Ok(())
    }

    /// Test if Android Keystore is available
    pub fn test_keystore_available() -> bool {
        // Get JNI environment
        let ctx = ndk_context::android_context();
        let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) };

        let vm = match vm {
            Ok(vm) => vm,
            Err(e) => {
                log::warn!("Failed to get JavaVM for keystore test: {}", e);
                return false;
            }
        };

        let mut env = match vm.attach_current_thread() {
            Ok(env) => env,
            Err(e) => {
                log::warn!("Failed to attach thread for keystore test: {}", e);
                return false;
            }
        };

        // Try to get the SecureStorage instance
        match get_secure_storage_instance(&mut env) {
            Ok(_) => {
                log::info!("Android Keystore available via SecureStorage");
                true
            }
            Err(e) => {
                log::warn!("Android Keystore not available: {}", e);
                false
            }
        }
    }

    /// Get the SecureStorage singleton instance
    fn get_secure_storage_instance<'a>(env: &mut JNIEnv<'a>) -> Result<JObject<'a>, String> {
        let class_name = SECURE_STORAGE_CLASS
            .get()
            .ok_or_else(|| "SecureStorage not initialized".to_string())?;

        // Get the Android context
        let ctx = ndk_context::android_context();
        let context = unsafe { JObject::from_raw(ctx.context().cast()) };

        // Get the ClassLoader from the Context
        let class_loader = env
            .call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])
            .map_err(|e| format!("Failed to get ClassLoader: {}", e))?
            .l()
            .map_err(|e| format!("Failed to convert ClassLoader: {}", e))?;

        // Load the SecureStorage class using the app's classloader
        let class_name_jstring = env
            .new_string(class_name.replace('/', "."))
            .map_err(|e| format!("Failed to create class name string: {}", e))?;

        let storage_class_obj = env
            .call_method(
                &class_loader,
                "loadClass",
                "(Ljava/lang/String;)Ljava/lang/Class;",
                &[JValue::Object(&class_name_jstring.into())],
            )
            .map_err(|e| format!("Failed to load SecureStorage class: {}", e))?
            .l()
            .map_err(|e| format!("Failed to convert to Class: {}", e))?;

        let storage_class = JClass::from(storage_class_obj);

        let instance = env
            .call_static_method(
                &storage_class,
                "getInstance",
                "()Lcom/dtourolle/jellytau/security/SecureStorage;",
                &[],
            )
            .map_err(|e| format!("Failed to get SecureStorage instance: {}", e))?
            .l()
            .map_err(|e| format!("Failed to convert to object: {}", e))?;

        Ok(instance)
    }

    /// Save a token using Android Keystore
    pub fn save_token(user_id: &str, token: &str) -> Result<(), CredentialError> {
        let ctx = ndk_context::android_context();
        let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }
            .map_err(|e| CredentialError::Keyring(format!("Failed to get JavaVM: {}", e)))?;

        let mut env = vm
            .attach_current_thread()
            .map_err(|e| CredentialError::Keyring(format!("Failed to attach thread: {}", e)))?;

        let instance =
            get_secure_storage_instance(&mut env).map_err(|e| CredentialError::Keyring(e))?;

        let key = format!("access_token:{}", user_id);
        let key_jstring = env
            .new_string(&key)
            .map_err(|e| CredentialError::Keyring(format!("Failed to create key string: {}", e)))?;
        let token_jstring = env.new_string(token).map_err(|e| {
            CredentialError::Keyring(format!("Failed to create token string: {}", e))
        })?;

        let result = env
            .call_method(
                instance,
                "saveToken",
                "(Ljava/lang/String;Ljava/lang/String;)Z",
                &[
                    JValue::Object(&key_jstring.into()),
                    JValue::Object(&token_jstring.into()),
                ],
            )
            .map_err(|e| CredentialError::Keyring(format!("Failed to call saveToken: {}", e)))?
            .z()
            .map_err(|e| {
                CredentialError::Keyring(format!("Failed to get boolean result: {}", e))
            })?;

        if result {
            Ok(())
        } else {
            Err(CredentialError::Keyring(
                "saveToken returned false".to_string(),
            ))
        }
    }

    /// Get a token from Android Keystore
    pub fn get_token(user_id: &str) -> Result<String, CredentialError> {
        let ctx = ndk_context::android_context();
        let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }
            .map_err(|e| CredentialError::Keyring(format!("Failed to get JavaVM: {}", e)))?;

        let mut env = vm
            .attach_current_thread()
            .map_err(|e| CredentialError::Keyring(format!("Failed to attach thread: {}", e)))?;

        let instance =
            get_secure_storage_instance(&mut env).map_err(|e| CredentialError::Keyring(e))?;

        let key = format!("access_token:{}", user_id);
        let key_jstring = env
            .new_string(&key)
            .map_err(|e| CredentialError::Keyring(format!("Failed to create key string: {}", e)))?;

        let result = env
            .call_method(
                instance,
                "getToken",
                "(Ljava/lang/String;)Ljava/lang/String;",
                &[JValue::Object(&key_jstring.into())],
            )
            .map_err(|e| CredentialError::Keyring(format!("Failed to call getToken: {}", e)))?
            .l()
            .map_err(|e| CredentialError::Keyring(format!("Failed to get object result: {}", e)))?;

        if result.is_null() {
            return Err(CredentialError::NotFound);
        }

        let token_jstring = JString::from(result);
        let token: String = env
            .get_string(&token_jstring)
            .map_err(|e| CredentialError::Keyring(format!("Failed to get string: {}", e)))?
            .into();

        Ok(token)
    }

    /// Delete a token from Android Keystore
    pub fn delete_token(user_id: &str) -> Result<(), CredentialError> {
        let ctx = ndk_context::android_context();
        let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }
            .map_err(|e| CredentialError::Keyring(format!("Failed to get JavaVM: {}", e)))?;

        let mut env = vm
            .attach_current_thread()
            .map_err(|e| CredentialError::Keyring(format!("Failed to attach thread: {}", e)))?;

        let instance =
            get_secure_storage_instance(&mut env).map_err(|e| CredentialError::Keyring(e))?;

        let key = format!("access_token:{}", user_id);
        let key_jstring = env
            .new_string(&key)
            .map_err(|e| CredentialError::Keyring(format!("Failed to create key string: {}", e)))?;

        let result = env
            .call_method(
                instance,
                "deleteToken",
                "(Ljava/lang/String;)Z",
                &[JValue::Object(&key_jstring.into())],
            )
            .map_err(|e| CredentialError::Keyring(format!("Failed to call deleteToken: {}", e)))?
            .z()
            .map_err(|e| {
                CredentialError::Keyring(format!("Failed to get boolean result: {}", e))
            })?;

        if result {
            Ok(())
        } else {
            Err(CredentialError::Keyring(
                "deleteToken returned false".to_string(),
            ))
        }
    }
}

// Export Android keystore functions at the module level for easier access
#[cfg(target_os = "android")]
pub use android_keystore::{
    initialize_secure_storage, test_keystore_available as android_test_keystore_available,
};

#[cfg(test)]
mod tests {
    use super::*;

    /// Build a store pinned to the encrypted-file backend with an explicit key,
    /// so a test can simulate "same file, different machine key" (which is what
    /// a restored backup looks like).
    fn file_backed_store(credentials_path: PathBuf, encryption_key: [u8; 32]) -> CredentialStore {
        CredentialStore {
            using_keyring: false,
            credentials_path,
            encryption_key,
        }
    }

    /// A credentials file we cannot decrypt must read as *no credentials stored*,
    /// not as a hard error. This is the restored-backup case: the ciphertext comes
    /// back but the key that encrypted it (Android Keystore / the machine-derived
    /// key) does not, so every read fails forever.
    ///
    /// TRACES: UR-012 | IR-014
    #[test]
    fn undecryptable_credentials_file_reads_as_not_found() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join(CREDENTIALS_FILENAME);

        let original = file_backed_store(path.clone(), [1u8; 32]);
        original.save_to_file("user-1", "token-abc").unwrap();

        // Restored onto a device whose derived key differs: same bytes, no key.
        let restored = file_backed_store(path.clone(), [2u8; 32]);
        match restored.get_token("user-1") {
            Err(CredentialError::NotFound) => {}
            other => panic!("expected NotFound for undecryptable ciphertext, got {other:?}"),
        }
    }

    /// Garbage in the file (truncation, partial restore) is the same story.
    ///
    /// TRACES: UR-012 | IR-014
    #[test]
    fn corrupt_credentials_file_reads_as_not_found() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join(CREDENTIALS_FILENAME);
        fs::write(&path, "not base64 at all !!!").unwrap();

        let store = file_backed_store(path, [3u8; 32]);
        match store.get_token("user-1") {
            Err(CredentialError::NotFound) => {}
            other => panic!("expected NotFound for corrupt file, got {other:?}"),
        }
    }

    /// …and the logged-out state must be recoverable: signing in again has to be
    /// able to write over the unreadable file rather than failing on load.
    ///
    /// TRACES: UR-012 | IR-014
    #[test]
    fn login_after_undecryptable_file_rewrites_it() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join(CREDENTIALS_FILENAME);

        let original = file_backed_store(path.clone(), [1u8; 32]);
        original.save_to_file("user-1", "token-abc").unwrap();

        let restored = file_backed_store(path.clone(), [2u8; 32]);
        restored.save_to_file("user-1", "token-fresh").unwrap();

        assert_eq!(restored.get_from_file("user-1").unwrap(), "token-fresh");
    }

    #[test]
    fn test_encryption_roundtrip() {
        let store = CredentialStore::new();
        let plaintext = "test-access-token-12345";

        let encrypted = store.encrypt(plaintext).unwrap();
        let decrypted = store.decrypt(&encrypted).unwrap();

        assert_eq!(plaintext, decrypted);
    }

    #[test]
    fn test_derive_encryption_key_is_deterministic() {
        let key1 = CredentialStore::derive_encryption_key();
        let key2 = CredentialStore::derive_encryption_key();
        assert_eq!(key1, key2);
    }
}
