//! Encryption format detection and backward compatibility for token storage
//!
//! This module provides:
//! - Format detection to distinguish between hex-encoded (Wave 154) and AES-GCM encrypted (Wave 155) tokens
//! - Encryption/decryption functions using AES-256-GCM authenticated encryption
//! - Backward compatibility layer for seamless migration from hex to encrypted format
//!
//! The detection is based on a simple prefix check: encrypted data starts with "ENC:"

use aes_gcm::{
    aead::{Aead, KeyInit},
    Aes256Gcm, Nonce,
};
use base64::{engine::general_purpose, Engine as _};
use common::{error::ErrorCategory, CommonError};
use rand::{rngs::OsRng, RngCore};

/// Encryption format for MFA secrets and tokens
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionFormat {
    /// Legacy format: plain hex-encoded secret
    /// Example: "4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d"
    HexEncoded,

    /// New format: AES-GCM encrypted secret with "ENC:" prefix
    /// Example: "`ENC:base64_encoded_encrypted_data`"
    AesGcmEncrypted,
}

impl EncryptionFormat {
    /// Detect encryption format from file content
    ///
    /// # Logic
    /// - If data starts with "ENC:", it's AES-GCM encrypted
    /// - Otherwise, it's hex-encoded (legacy format)
    ///
    /// # Arguments
    /// * `data` - The raw file content (trimmed recommended)
    ///
    /// # Returns
    /// The detected encryption format
    ///
    /// # Examples
    /// ```
    /// use fxt::auth::encryption::EncryptionFormat;
    ///
    /// let hex_data = "4a5b6c7d8e9f0a1b";
    /// assert_eq!(EncryptionFormat::detect(hex_data), EncryptionFormat::HexEncoded);
    ///
    /// let encrypted_data = "ENC:abc123==";
    /// assert_eq!(EncryptionFormat::detect(encrypted_data), EncryptionFormat::AesGcmEncrypted);
    /// ```
    pub fn detect(data: &str) -> Self {
        if data.starts_with("ENC:") {
            EncryptionFormat::AesGcmEncrypted
        } else {
            EncryptionFormat::HexEncoded
        }
    }

    /// Check if data is encrypted (convenience method)
    ///
    /// # Arguments
    /// * `data` - The raw file content
    ///
    /// # Returns
    /// `true` if data is in encrypted format, `false` if hex-encoded
    ///
    /// # Examples
    /// ```
    /// use fxt::auth::encryption::EncryptionFormat;
    ///
    /// assert!(!EncryptionFormat::is_encrypted("4a5b6c7d"));
    /// assert!(EncryptionFormat::is_encrypted("ENC:data"));
    /// ```
    pub fn is_encrypted(data: &str) -> bool {
        data.starts_with("ENC:")
    }
}

/// Encrypt a token using AES-256-GCM authenticated encryption
///
/// # Format
/// The output format is: "ENC:" + base64(nonce || ciphertext || tag)
/// - nonce: 12 bytes (96 bits) - randomly generated
/// - ciphertext: variable length (same as plaintext)
/// - tag: 16 bytes (128 bits) - authentication tag appended by GCM
///
/// # Arguments
/// * `token` - The plaintext token to encrypt
/// * `key` - The 32-byte (256-bit) AES encryption key
///
/// # Returns
/// * `Ok(String)` - Encrypted token with "ENC:" prefix and base64-encoded data
/// * `Err(CommonError)` - If key length is invalid or encryption fails
///
/// # Security
/// - Uses cryptographically secure random nonce generation (`OsRng`)
/// - GCM mode provides authenticated encryption (confidentiality + integrity)
/// - Each encryption uses a unique nonce (never reuse with same key)
///
/// # Examples
/// ```no_run
/// use fxt::auth::encryption::encrypt_token;
///
/// let key = [0u8; 32]; // 32-byte key (in production, use proper key derivation)
/// let token = "my_secret_token";
/// let encrypted = encrypt_token(token, &key).unwrap();
/// assert!(encrypted.starts_with("ENC:"));
/// ```
pub fn encrypt_token(token: &str, key: &[u8]) -> Result<String, CommonError> {
    // Validate key length (AES-256 requires exactly 32 bytes)
    if key.len() != 32 {
        return Err(CommonError::service(
            ErrorCategory::Security,
            format!(
                "Invalid encryption key length: expected 32 bytes, got {}",
                key.len()
            ),
        ));
    }

    // Generate random 12-byte nonce (96 bits, recommended for GCM)
    let mut nonce_bytes = [0_u8; 12];
    OsRng.fill_bytes(&mut nonce_bytes);
    let nonce = Nonce::from_slice(&nonce_bytes);

    // Create AES-256-GCM cipher
    let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| {
        CommonError::service(
            ErrorCategory::Security,
            format!("Failed to create cipher: {}", e),
        )
    })?;

    // Encrypt the token (GCM automatically appends 16-byte authentication tag)
    let ciphertext = cipher.encrypt(nonce, token.as_bytes()).map_err(|e| {
        CommonError::service(ErrorCategory::Security, format!("Encryption failed: {}", e))
    })?;

    // Combine nonce + ciphertext (ciphertext already includes the 16-byte tag)
    let mut combined = Vec::with_capacity(12 + ciphertext.len());
    combined.extend_from_slice(&nonce_bytes);
    combined.extend_from_slice(&ciphertext);

    // Encode as base64 with "ENC:" prefix
    let encoded = general_purpose::STANDARD.encode(&combined);
    Ok(format!("ENC:{}", encoded))
}

/// Decrypt a token using AES-256-GCM authenticated encryption
///
/// # Format
/// The input must be: "ENC:" + base64(nonce || ciphertext || tag)
/// - nonce: 12 bytes (96 bits)
/// - ciphertext: variable length (same as original plaintext)
/// - tag: 16 bytes (128 bits) - authentication tag verified by GCM
///
/// # Arguments
/// * `encrypted` - The encrypted token (must start with "ENC:")
/// * `key` - The 32-byte (256-bit) AES decryption key
///
/// # Returns
/// * `Ok(String)` - Decrypted plaintext token
/// * `Err(CommonError)` - If prefix missing, key invalid, data corrupted, or authentication fails
///
/// # Security
/// - GCM automatically verifies authentication tag before decryption
/// - Wrong key or tampered data will fail authentication
/// - Prevents forgery and modification attacks
///
/// # Errors
/// - Missing "ENC:" prefix
/// - Invalid key length (not 32 bytes)
/// - Base64 decode failure
/// - Data too short (< 28 bytes: 12 nonce + 16 tag minimum)
/// - Decryption failure (wrong key or corrupted data)
/// - Authentication tag verification failure
/// - UTF-8 decode failure
///
/// # Examples
/// ```no_run
/// use fxt::auth::encryption::{encrypt_token, decrypt_token};
///
/// let key = [0u8; 32];
/// let token = "my_secret_token";
/// let encrypted = encrypt_token(token, &key).unwrap();
/// let decrypted = decrypt_token(&encrypted, &key).unwrap();
/// assert_eq!(decrypted, token);
/// ```
pub fn decrypt_token(encrypted: &str, key: &[u8]) -> Result<String, CommonError> {
    // Validate key length (AES-256 requires exactly 32 bytes)
    if key.len() != 32 {
        return Err(CommonError::service(
            ErrorCategory::Security,
            format!("Invalid key length: expected 32 bytes, got {}", key.len()),
        ));
    }

    // Validate "ENC:" prefix
    if !encrypted.starts_with("ENC:") {
        return Err(CommonError::service(
            ErrorCategory::Security,
            "Missing ENC: prefix".to_owned(),
        ));
    }

    // Strip "ENC:" prefix and decode base64
    let base64_data = encrypted.get(4..).ok_or_else(|| {
        CommonError::service(
            ErrorCategory::Security,
            "Encrypted data too short for ENC: prefix".to_owned(),
        )
    })?;
    let combined = general_purpose::STANDARD.decode(base64_data).map_err(|e| {
        CommonError::service(
            ErrorCategory::Security,
            format!("Base64 decode failed: {}", e),
        )
    })?;

    // Validate minimum length: 12 bytes (nonce) + 16 bytes (tag) = 28 bytes
    if combined.len() < 28 {
        return Err(CommonError::service(
            ErrorCategory::Security,
            format!(
                "Data too short: expected at least 28 bytes, got {}",
                combined.len()
            ),
        ));
    }

    // Extract nonce (first 12 bytes) -- length validated above (>= 28).
    let nonce_bytes = combined.get(..12).ok_or_else(|| {
        CommonError::service(ErrorCategory::Security, "Nonce extraction failed".to_owned())
    })?;
    let nonce = Nonce::from_slice(nonce_bytes);

    // Extract ciphertext + tag (remaining bytes, tag is last 16 bytes included in ciphertext)
    let ciphertext = combined.get(12..).ok_or_else(|| {
        CommonError::service(ErrorCategory::Security, "Ciphertext extraction failed".to_owned())
    })?;

    // Create AES-256-GCM cipher
    let cipher = Aes256Gcm::new_from_slice(key).map_err(|e| {
        CommonError::service(
            ErrorCategory::Security,
            format!("Cipher creation failed: {}", e),
        )
    })?;

    // Decrypt and verify authentication tag (GCM does both automatically)
    let plaintext_bytes = cipher.decrypt(nonce, ciphertext).map_err(|e| {
        CommonError::service(ErrorCategory::Security, format!("Decryption failed: {}", e))
    })?;

    // Convert decrypted bytes to UTF-8 string
    let plaintext = String::from_utf8(plaintext_bytes).map_err(|e| {
        CommonError::service(
            ErrorCategory::Security,
            format!("UTF-8 decode failed: {}", e),
        )
    })?;

    Ok(plaintext)
}

/// Auto-read token with format detection (backward compatibility)
///
/// This function automatically detects whether the stored token is:
/// - Legacy hex-encoded format (Wave 154)
/// - New AES-GCM encrypted format (Wave 155)
///
/// And returns the plaintext token in both cases.
///
/// # Arguments
/// * `encrypted_data` - Either hex-encoded or "ENC:" prefixed encrypted data
/// * `key` - 32-byte decryption key (only used for encrypted format)
///
/// # Returns
/// * `Ok(String)` - The plaintext token
/// * `Err(CommonError)` - If decoding/decryption fails
///
/// # Migration Strategy
/// This function enables seamless migration:
/// - Read: Supports both hex (old) and encrypted (new) formats
/// - Write: Use `write_token_encrypted()` to always write encrypted format
/// - Result: Automatic migration on first token refresh
///
/// # Examples
/// ```no_run
/// use fxt::auth::encryption::read_token_auto;
///
/// let key = [0u8; 32];
///
/// // Read legacy hex-encoded token (Wave 154)
/// let hex_token = "6d795f746f6b656e"; // "my_token" in hex
/// let plaintext = read_token_auto(hex_token, &key).unwrap();
/// assert_eq!(plaintext, "my_token");
///
/// // Read new encrypted token (Wave 155)
/// let encrypted = "ENC:base64data";
/// let plaintext = read_token_auto(encrypted, &key).unwrap();
/// ```
pub fn read_token_auto(encrypted_data: &str, key: &[u8]) -> Result<String, CommonError> {
    match EncryptionFormat::detect(encrypted_data) {
        EncryptionFormat::HexEncoded => {
            // Legacy format: decode hex and return plaintext
            let token_bytes = hex::decode(encrypted_data).map_err(|e| {
                CommonError::service(
                    ErrorCategory::Security,
                    format!(
                        "Failed to decode hex-encoded token (backward compatibility): {}",
                        e
                    ),
                )
            })?;

            String::from_utf8(token_bytes).map_err(|e| {
                CommonError::service(
                    ErrorCategory::Security,
                    format!("Hex-decoded token is not valid UTF-8: {}", e),
                )
            })
        },
        EncryptionFormat::AesGcmEncrypted => {
            // New format: decrypt using AES-GCM
            decrypt_token(encrypted_data, key)
        },
    }
}

/// Always write token in encrypted format (migration helper)
///
/// This function enforces the migration strategy: all new writes use encrypted format.
/// When combined with `read_token_auto()`, this enables seamless migration:
/// 1. First read after Wave 155: `read_token_auto()` handles old hex format
/// 2. First write after Wave 155: `write_token_encrypted()` converts to encrypted format
/// 3. Subsequent operations: encrypted format throughout
///
/// # Arguments
/// * `token` - The plaintext token to encrypt and store
/// * `key` - 32-byte encryption key
///
/// # Returns
/// * `Ok(String)` - Encrypted data in format: "ENC:base64(nonce || ciphertext || tag)"
/// * `Err(CommonError)` - If encryption fails
///
/// # Examples
/// ```no_run
/// use fxt::auth::encryption::{read_token_auto, write_token_encrypted};
///
/// let key = [0u8; 32];
///
/// // Migration scenario:
/// // 1. Read old hex-encoded token
/// let hex_token = "6d795f746f6b656e"; // "my_token" in hex
/// let plaintext = read_token_auto(hex_token, &key).unwrap();
///
/// // 2. Write in new encrypted format (migration happens here)
/// let encrypted = write_token_encrypted(&plaintext, &key).unwrap();
/// assert!(encrypted.starts_with("ENC:"));
///
/// // 3. Future reads will use encrypted format
/// let plaintext2 = read_token_auto(&encrypted, &key).unwrap();
/// assert_eq!(plaintext, plaintext2);
/// ```
pub fn write_token_encrypted(token: &str, key: &[u8]) -> Result<String, CommonError> {
    // Always use encrypted format for new writes
    encrypt_token(token, key)
}

#[cfg(test)]
#[allow(clippy::indexing_slicing, clippy::string_slice, clippy::unwrap_used, clippy::expect_used, clippy::shadow_unrelated, clippy::shadow_reuse, clippy::assertions_on_result_states, clippy::str_to_string)]
mod tests {
    use super::*;

    #[test]
    fn test_detect_hex_format() {
        // Typical hex-encoded MFA secret (32 chars = 16 bytes)
        let hex_secret = "4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d";
        assert_eq!(
            EncryptionFormat::detect(hex_secret),
            EncryptionFormat::HexEncoded
        );

        // Short hex string
        let short_hex = "abc123";
        assert_eq!(
            EncryptionFormat::detect(short_hex),
            EncryptionFormat::HexEncoded
        );

        // Empty string (edge case - treated as hex)
        let empty = "";
        assert_eq!(
            EncryptionFormat::detect(empty),
            EncryptionFormat::HexEncoded
        );

        // Hex string with whitespace (should be trimmed before detection)
        let hex_with_whitespace = "  4a5b6c7d8e9f0a1b  ";
        assert_eq!(
            EncryptionFormat::detect(hex_with_whitespace.trim()),
            EncryptionFormat::HexEncoded
        );
    }

    #[test]
    fn test_detect_encrypted_format() {
        // Typical encrypted format with base64 data
        let encrypted = "ENC:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=";
        assert_eq!(
            EncryptionFormat::detect(encrypted),
            EncryptionFormat::AesGcmEncrypted
        );

        // Minimal encrypted format (just prefix)
        let minimal = "ENC:";
        assert_eq!(
            EncryptionFormat::detect(minimal),
            EncryptionFormat::AesGcmEncrypted
        );

        // Encrypted format with short data
        let short_encrypted = "ENC:abc";
        assert_eq!(
            EncryptionFormat::detect(short_encrypted),
            EncryptionFormat::AesGcmEncrypted
        );

        // Encrypted format with whitespace after prefix
        let encrypted_with_space = "ENC: data";
        assert_eq!(
            EncryptionFormat::detect(encrypted_with_space),
            EncryptionFormat::AesGcmEncrypted
        );
    }

    #[test]
    fn test_is_encrypted() {
        // Hex-encoded (not encrypted)
        assert!(!EncryptionFormat::is_encrypted("4a5b6c7d8e9f0a1b"));
        assert!(!EncryptionFormat::is_encrypted(""));
        assert!(!EncryptionFormat::is_encrypted("random_string"));

        // Encrypted format
        assert!(EncryptionFormat::is_encrypted("ENC:data"));
        assert!(EncryptionFormat::is_encrypted("ENC:"));
        assert!(EncryptionFormat::is_encrypted(
            "ENC:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="
        ));
    }

    #[test]
    fn test_edge_cases() {
        // String starting with "enc:" (lowercase) - treated as hex
        let lowercase = "enc:data";
        assert_eq!(
            EncryptionFormat::detect(lowercase),
            EncryptionFormat::HexEncoded
        );
        assert!(!EncryptionFormat::is_encrypted(lowercase));

        // String containing "ENC:" but not at start - treated as hex
        let middle = "data_ENC:something";
        assert_eq!(
            EncryptionFormat::detect(middle),
            EncryptionFormat::HexEncoded
        );
        assert!(!EncryptionFormat::is_encrypted(middle));

        // Mixed case prefix - treated as hex
        let mixed_case = "EnC:data";
        assert_eq!(
            EncryptionFormat::detect(mixed_case),
            EncryptionFormat::HexEncoded
        );
        assert!(!EncryptionFormat::is_encrypted(mixed_case));
    }

    #[test]
    fn test_consistency_between_methods() {
        // Verify detect() and is_encrypted() are consistent
        let test_cases = vec![
            ("4a5b6c7d", false),
            ("ENC:data", true),
            ("", false),
            ("ENC:", true),
            ("enc:data", false),
            ("random", false),
        ];

        for (data, expected_encrypted) in test_cases {
            let detected_format = EncryptionFormat::detect(data);
            let is_encrypted = EncryptionFormat::is_encrypted(data);

            // Verify consistency
            assert_eq!(
                is_encrypted, expected_encrypted,
                "is_encrypted mismatch for: {}",
                data
            );
            assert_eq!(
                detected_format,
                if expected_encrypted {
                    EncryptionFormat::AesGcmEncrypted
                } else {
                    EncryptionFormat::HexEncoded
                },
                "detect mismatch for: {}",
                data
            );
        }
    }

    // Tests for encrypt_token()

    #[test]
    fn test_encrypt_token_success() {
        // Test successful encryption with valid 32-byte key
        let key = [0_u8; 32];
        let token = "my_test_token_12345";

        let result = encrypt_token(token, &key);
        assert!(result.is_ok(), "Encryption should succeed with valid key");

        let encrypted = result.unwrap();

        // Verify format
        assert!(
            encrypted.starts_with("ENC:"),
            "Encrypted token should start with 'ENC:' prefix"
        );

        // Verify it's detected as encrypted
        assert!(
            EncryptionFormat::is_encrypted(&encrypted),
            "Output should be detected as encrypted format"
        );

        // Verify length (4 + base64_len(12 + token_len + 16))
        // Base64 expands data by ~4/3
        // Data size: 12 (nonce) + token.len() + 16 (tag)
        let data_size = 12 + token.len() + 16;
        let base64_len = data_size.div_ceil(3) * 4; // Base64 length calculation
        let expected_len = 4 + base64_len; // "ENC:" + base64

        assert_eq!(
            encrypted.len(),
            expected_len,
            "Encrypted token length should match expected format"
        );
    }

    #[test]
    fn test_encrypt_token_base64_decodable() {
        // Test that output is valid base64
        let key = [1_u8; 32];
        let token = "test_token";

        let encrypted = encrypt_token(token, &key).unwrap();

        // Remove "ENC:" prefix and decode base64
        let base64_data = &encrypted[4..];
        let decoded = general_purpose::STANDARD.decode(base64_data);

        assert!(decoded.is_ok(), "Encrypted data should be valid base64");

        let decoded_bytes = decoded.unwrap();

        // Verify decoded length (12 nonce + token_len + 16 tag)
        let expected_len = 12 + token.len() + 16;
        assert_eq!(
            decoded_bytes.len(),
            expected_len,
            "Decoded data should have correct length"
        );
    }

    #[test]
    fn test_encrypt_token_invalid_key_length() {
        // Test encryption fails with wrong key length
        let token = "test_token";

        // Test various invalid key lengths
        let invalid_keys = vec![
            vec![0_u8; 16], // Too short (AES-128)
            vec![0_u8; 24], // Too short (AES-192)
            vec![0_u8; 31], // One byte short
            vec![0_u8; 33], // One byte too long
            vec![0_u8; 0],  // Empty
            vec![0_u8; 64], // Too long
        ];

        for key in invalid_keys {
            let result = encrypt_token(token, &key);
            assert!(
                result.is_err(),
                "Encryption should fail with key length {}",
                key.len()
            );

            // Verify error message mentions key length
            let err = result.unwrap_err();
            let err_msg = format!("{}", err);
            assert!(
                err_msg.contains("key length") || err_msg.contains("32 bytes"),
                "Error should mention key length issue: {}",
                err_msg
            );
        }
    }

    #[test]
    fn test_encrypt_token_different_outputs() {
        // Test that same input produces different outputs (due to random nonce)
        let key = [2_u8; 32];
        let token = "same_token";

        let encrypted1 = encrypt_token(token, &key).unwrap();
        let encrypted2 = encrypt_token(token, &key).unwrap();

        // Both should have "ENC:" prefix
        assert!(encrypted1.starts_with("ENC:"));
        assert!(encrypted2.starts_with("ENC:"));

        // But the encrypted data should be different (different nonces)
        assert_ne!(
            encrypted1, encrypted2,
            "Multiple encryptions of same data should produce different outputs"
        );
    }

    #[test]
    fn test_encrypt_token_empty_string() {
        // Test encrypting empty string (edge case)
        let key = [3_u8; 32];
        let token = "";

        let result = encrypt_token(token, &key);
        assert!(result.is_ok(), "Should be able to encrypt empty string");

        let encrypted = result.unwrap();
        assert!(encrypted.starts_with("ENC:"));

        // Length should be: 4 + base64_len(12 + 0 + 16) = 4 + base64_len(28)
        // Base64 of 28 bytes = 38 chars (28 * 4/3 rounded up to multiple of 4)
        assert_eq!(encrypted.len(), 4 + 40); // "ENC:" + base64(28 bytes)
    }

    #[test]
    fn test_encrypt_token_long_string() {
        // Test encrypting long string
        let key = [4_u8; 32];
        let token = "a".repeat(1000); // 1000-character token

        let result = encrypt_token(&token, &key);
        assert!(result.is_ok(), "Should be able to encrypt long strings");

        let encrypted = result.unwrap();
        assert!(encrypted.starts_with("ENC:"));

        // Verify length calculation
        let data_size = 12 + token.len() + 16; // nonce + plaintext + tag
        let base64_len = data_size.div_ceil(3) * 4;
        assert_eq!(encrypted.len(), 4 + base64_len);
    }

    #[test]
    fn test_encrypt_token_special_characters() {
        // Test encrypting string with special characters
        let key = [5_u8; 32];
        let tokens = vec![
            "token-with-dashes",
            "token_with_underscores",
            "token.with.dots",
            "token@with#special$chars%",
            "token with spaces",
            "token\nwith\nnewlines",
            "token\twith\ttabs",
            "token\u{1f680}with\u{1f600}emojis",
        ];

        for token in tokens {
            let result = encrypt_token(token, &key);
            assert!(
                result.is_ok(),
                "Should encrypt token with special chars: {:?}",
                token
            );

            let encrypted = result.unwrap();
            assert!(encrypted.starts_with("ENC:"));
        }
    }

    // Tests for decrypt_token()

    #[test]
    fn test_decrypt_token_success() {
        // Test successful decryption with valid key
        let key = [0_u8; 32];
        let token = "test_token_to_decrypt";

        // Encrypt first
        let encrypted = encrypt_token(token, &key).unwrap();

        // Decrypt
        let decrypted = decrypt_token(&encrypted, &key);
        assert!(decrypted.is_ok(), "Decryption should succeed");

        let decrypted_token = decrypted.unwrap();
        assert_eq!(decrypted_token, token, "Decrypted should match original");
    }

    #[test]
    fn test_decrypt_token_missing_prefix() {
        // Test that decryption fails without "ENC:" prefix
        let key = [0_u8; 32];
        let invalid_data = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=";

        let result = decrypt_token(invalid_data, &key);
        assert!(result.is_err(), "Should fail without ENC: prefix");

        let err_msg = format!("{}", result.unwrap_err());
        assert!(
            err_msg.contains("ENC:") || err_msg.contains("prefix"),
            "Error should mention missing prefix: {}",
            err_msg
        );
    }

    #[test]
    fn test_decrypt_token_invalid_base64() {
        // Test that decryption fails with invalid base64
        let key = [0_u8; 32];
        let invalid_data = "ENC:not_valid_base64!!!";

        let result = decrypt_token(invalid_data, &key);
        assert!(result.is_err(), "Should fail with invalid base64");
    }

    #[test]
    fn test_decrypt_token_data_too_short() {
        // Test that decryption fails with data too short
        let key = [0_u8; 32];
        // Valid base64 but data too short (less than 28 bytes)
        let short_data = general_purpose::STANDARD.encode([0_u8; 10]);
        let invalid_data = format!("ENC:{}", short_data);

        let result = decrypt_token(&invalid_data, &key);
        assert!(result.is_err(), "Should fail with data too short");

        let err_msg = format!("{}", result.unwrap_err());
        assert!(
            err_msg.contains("too short") || err_msg.contains("28 bytes"),
            "Error should mention data too short: {}",
            err_msg
        );
    }

    #[test]
    fn test_decrypt_token_wrong_key() {
        // Test that decryption fails with wrong key
        let key1 = [0_u8; 32];
        let key2 = [1_u8; 32];
        let token = "test_token";

        // Encrypt with key1
        let encrypted = encrypt_token(token, &key1).unwrap();

        // Try to decrypt with key2
        let result = decrypt_token(&encrypted, &key2);
        assert!(
            result.is_err(),
            "Should fail when decrypting with wrong key"
        );
    }

    #[test]
    fn test_decrypt_token_tampered_data() {
        // Test that decryption fails with tampered data
        let key = [0_u8; 32];
        let token = "test_token";

        // Encrypt
        let encrypted = encrypt_token(token, &key).unwrap();

        // Tamper with multiple positions in the base64 payload to guarantee
        // AES-GCM authentication failure regardless of the random nonce.
        let mut chars: Vec<char> = encrypted.chars().collect();
        for &pos in &[6, 12, 18, 24] {
            if let Some(c) = chars.get_mut(pos) {
                *c = if *c == 'A' { 'B' } else { 'A' };
            }
        }
        let tampered: String = chars.into_iter().collect();

        // Try to decrypt tampered data
        let result = decrypt_token(&tampered, &key);
        // Should fail (either base64 decode or authentication failure)
        assert!(result.is_err(), "Should fail when decrypting tampered data");
    }

    // Tests for read_token_auto() (backward compatibility)

    #[test]
    fn test_read_token_auto_hex_format() {
        // Test reading legacy hex-encoded token (Wave 154 format)
        let key = [0_u8; 32];
        let token = "my_token";
        let hex_encoded = hex::encode(token);

        let result = read_token_auto(&hex_encoded, &key);
        assert!(
            result.is_ok(),
            "Should read hex-encoded token (backward compatibility)"
        );

        let plaintext = result.unwrap();
        assert_eq!(plaintext, token, "Decoded token should match original");
    }

    #[test]
    fn test_read_token_auto_encrypted_format() {
        // Test reading new AES-GCM encrypted token (Wave 155 format)
        let key = [0_u8; 32];
        let token = "my_token";

        // Encrypt using Wave 155 format
        let encrypted = encrypt_token(token, &key).unwrap();

        // Read using auto-detection
        let result = read_token_auto(&encrypted, &key);
        assert!(result.is_ok(), "Should read encrypted token");

        let plaintext = result.unwrap();
        assert_eq!(plaintext, token, "Decrypted token should match original");
    }

    #[test]
    fn test_read_token_auto_format_detection() {
        // Test that auto-detection works correctly for both formats
        let key = [0_u8; 32];
        let token = "test_token";

        // Wave 154 format (hex)
        let hex_encoded = hex::encode(token);
        let hex_format = EncryptionFormat::detect(&hex_encoded);
        assert_eq!(
            hex_format,
            EncryptionFormat::HexEncoded,
            "Should detect hex format"
        );

        // Wave 155 format (encrypted)
        let encrypted = encrypt_token(token, &key).unwrap();
        let enc_format = EncryptionFormat::detect(&encrypted);
        assert_eq!(
            enc_format,
            EncryptionFormat::AesGcmEncrypted,
            "Should detect encrypted format"
        );
    }

    #[test]
    fn test_read_token_auto_invalid_hex() {
        // Test error handling for invalid hex encoding
        let key = [0_u8; 32];
        let invalid_hex = "not_valid_hex!!!";

        let result = read_token_auto(invalid_hex, &key);
        assert!(
            result.is_err(),
            "Should fail with invalid hex (backward compatibility)"
        );

        let err_msg = format!("{}", result.unwrap_err());
        assert!(
            err_msg.contains("hex") || err_msg.contains("backward compatibility"),
            "Error should mention hex decoding: {}",
            err_msg
        );
    }

    #[test]
    fn test_read_token_auto_invalid_encrypted() {
        // Test error handling for invalid encrypted format
        let key = [0_u8; 32];
        let invalid_encrypted = "ENC:not_valid_base64!!!";

        let result = read_token_auto(invalid_encrypted, &key);
        assert!(result.is_err(), "Should fail with invalid encrypted data");
    }

    // Tests for write_token_encrypted() (migration helper)

    #[test]
    fn test_write_token_encrypted_always_encrypted() {
        // Test that write_token_encrypted() always returns encrypted format
        let key = [0_u8; 32];
        let token = "my_token";

        let result = write_token_encrypted(token, &key);
        assert!(result.is_ok(), "Should write token in encrypted format");

        let encrypted = result.unwrap();

        // Verify format
        assert!(
            encrypted.starts_with("ENC:"),
            "Output should always have ENC: prefix"
        );

        // Verify detection
        assert!(
            EncryptionFormat::is_encrypted(&encrypted),
            "Output should be detected as encrypted"
        );
    }

    #[test]
    fn test_write_token_encrypted_roundtrip() {
        // Test that write → read roundtrip works
        let key = [0_u8; 32];
        let token = "roundtrip_token";

        // Write (encrypt)
        let encrypted = write_token_encrypted(token, &key).unwrap();

        // Read (decrypt)
        let decrypted = read_token_auto(&encrypted, &key).unwrap();

        assert_eq!(decrypted, token, "Roundtrip should preserve token");
    }

    // Tests for migration scenario

    #[test]
    fn test_migration_scenario() {
        // Test complete migration scenario: hex → auto-read → encrypted write
        let key = [0_u8; 32];
        let token = "migration_token";

        // Step 1: Start with Wave 154 hex-encoded token
        let hex_encoded = hex::encode(token);
        assert!(
            !EncryptionFormat::is_encrypted(&hex_encoded),
            "Initial format should be hex (Wave 154)"
        );

        // Step 2: Read old format using auto-detection
        let plaintext = read_token_auto(&hex_encoded, &key).unwrap();
        assert_eq!(plaintext, token, "Should read hex format correctly");

        // Step 3: Write in new encrypted format (migration happens here)
        let encrypted = write_token_encrypted(&plaintext, &key).unwrap();
        assert!(
            encrypted.starts_with("ENC:"),
            "New format should be encrypted (Wave 155)"
        );

        // Step 4: Future reads use encrypted format
        let plaintext2 = read_token_auto(&encrypted, &key).unwrap();
        assert_eq!(plaintext2, token, "Should read encrypted format correctly");

        // Verify migration completed
        assert_ne!(
            hex_encoded, encrypted,
            "Format should have changed from hex to encrypted"
        );
    }

    #[test]
    fn test_migration_multiple_tokens() {
        // Test migration with multiple different tokens
        let key = [0_u8; 32];
        let tokens = vec![
            "token1",
            "token2_with_underscores",
            "token-3-with-dashes",
            "token.4.with.dots",
        ];

        for token in tokens {
            // Old format (hex)
            let hex_encoded = hex::encode(token);

            // Read old format
            let plaintext = read_token_auto(&hex_encoded, &key).unwrap();
            assert_eq!(plaintext, token);

            // Write new format
            let encrypted = write_token_encrypted(&plaintext, &key).unwrap();
            assert!(encrypted.starts_with("ENC:"));

            // Read new format
            let plaintext2 = read_token_auto(&encrypted, &key).unwrap();
            assert_eq!(plaintext2, token);
        }
    }

    #[test]
    fn test_migration_idempotent() {
        // Test that migrating already-migrated tokens doesn't break
        let key = [0_u8; 32];
        let token = "already_migrated_token";

        // Start with encrypted format (already migrated)
        let encrypted1 = write_token_encrypted(token, &key).unwrap();

        // Read and write again (should still work)
        let plaintext = read_token_auto(&encrypted1, &key).unwrap();
        let encrypted2 = write_token_encrypted(&plaintext, &key).unwrap();

        // Both encrypted versions should decrypt to same token
        let decrypted1 = read_token_auto(&encrypted1, &key).unwrap();
        let decrypted2 = read_token_auto(&encrypted2, &key).unwrap();

        assert_eq!(decrypted1, token);
        assert_eq!(decrypted2, token);

        // But ciphertexts will differ (different nonces)
        assert_ne!(
            encrypted1, encrypted2,
            "Different encryptions should produce different ciphertexts"
        );
    }
}
