//! Integration tests for FileTokenStorage encryption
//!
//! These tests verify:
//! - Encrypted token storage and retrieval
//! - Backward compatibility (hex → encrypted migration)
//! - Encryption key derivation consistency
//! - Error handling for corrupted/invalid data
//!
//! Note: Requires test-utils feature to access with_directory() method

// Suppress false-positive unused_crate_dependencies warnings
// dev-dependencies are shared across ALL test targets in the crate
// This test may not use all deps, but they are required by other integration tests
#![allow(unused_crate_dependencies)]
#![allow(clippy::doc_markdown, clippy::indexing_slicing)]

#![cfg(feature = "test-utils")]

use anyhow::Result;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
use fxt::auth::token_manager::{FileTokenStorage, TokenStorage};

/// Helper: Create FileTokenStorage with temporary directory
fn create_test_storage() -> Result<(FileTokenStorage, TempDir)> {
    let temp_dir = TempDir::new()?;
    let storage = FileTokenStorage::with_directory(temp_dir.path().to_path_buf())?;
    Ok((storage, temp_dir))
}

/// Helper: Get token file path
fn get_token_path(temp_dir: &TempDir, token_type: &str) -> PathBuf {
    temp_dir.path().join(format!("{}_token", token_type))
}

#[tokio::test]
async fn test_file_storage_encrypted_roundtrip() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let test_token = "test_access_token_12345";

    // Store token
    storage.store_access_token(test_token).await?;

    // Verify file contains "ENC:" prefix (not hex)
    let token_path = get_token_path(&temp_dir, "access");
    let file_contents = fs::read_to_string(&token_path)?;
    assert!(
        file_contents.starts_with("ENC:"),
        "Token file should have ENC: prefix, got: {}",
        &file_contents[..10.min(file_contents.len())]
    );

    // Verify it's NOT hex format
    assert!(
        !file_contents.chars().all(|c| c.is_ascii_hexdigit()),
        "Token should not be in hex format"
    );

    // Retrieve token and verify it matches original
    let retrieved = storage.get_access_token().await?;
    assert_eq!(
        retrieved.as_deref(),
        Some(test_token),
        "Retrieved token should match original"
    );

    // Cleanup
    drop(storage);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_migration_hex_to_encrypted() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let test_token = "test_migration_token_67890";
    let token_path = get_token_path(&temp_dir, "access");

    // Manually create hex-encoded token file (Wave 154 format)
    let hex_encoded = hex::encode(test_token.as_bytes());
    fs::write(&token_path, &hex_encoded)?;
    println!("Created hex token file: {}", hex_encoded);

    // Use FileTokenStorage to read (should detect hex and decode)
    let retrieved = storage.get_access_token().await?;
    assert_eq!(
        retrieved.as_deref(),
        Some(test_token),
        "Should successfully read hex-encoded token"
    );

    // Use FileTokenStorage to write (should upgrade to encrypted)
    storage.store_access_token(test_token).await?;

    // Read file directly, verify "ENC:" prefix
    let file_contents = fs::read_to_string(&token_path)?;
    assert!(
        file_contents.starts_with("ENC:"),
        "Token file should be upgraded to ENC: format, got: {}",
        &file_contents[..10.min(file_contents.len())]
    );

    // Verify it's no longer hex
    assert!(
        !file_contents.chars().all(|c| c.is_ascii_hexdigit()),
        "Token should no longer be in hex format"
    );

    // Verify token is still readable
    let final_retrieved = storage.get_access_token().await?;
    assert_eq!(
        final_retrieved.as_deref(),
        Some(test_token),
        "Token should still be readable after upgrade"
    );

    // Cleanup
    drop(storage);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_encryption_key_derivation() -> Result<()> {
    let temp_dir = TempDir::new()?;
    let test_token = "test_key_derivation_token";

    // Store same token with first instance
    {
        let storage1 = FileTokenStorage::with_directory(temp_dir.path().to_path_buf())?;
        storage1.store_access_token(test_token).await?;
        drop(storage1);
    }

    let token_path = get_token_path(&temp_dir, "access");
    let first_encrypted = fs::read_to_string(&token_path)?;
    assert!(
        first_encrypted.starts_with("ENC:"),
        "First encryption should use ENC: format"
    );

    // Store same token with second instance
    {
        let storage2 = FileTokenStorage::with_directory(temp_dir.path().to_path_buf())?;
        storage2.store_access_token(test_token).await?;
        drop(storage2);
    }

    let second_encrypted = fs::read_to_string(&token_path)?;
    assert!(
        second_encrypted.starts_with("ENC:"),
        "Second encryption should use ENC: format"
    );

    // Verify both can decrypt (same system key)
    // Note: Encrypted values will differ due to random nonce, but both should decrypt correctly
    let storage3 = FileTokenStorage::with_directory(temp_dir.path().to_path_buf())?;
    let retrieved = storage3.get_access_token().await?;
    assert_eq!(
        retrieved.as_deref(),
        Some(test_token),
        "Both encryptions should decrypt to original token"
    );

    // Cleanup
    drop(storage3);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_corrupted_encrypted_data() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let test_token = "test_corruption_token";

    // Store encrypted token
    storage.store_access_token(test_token).await?;

    let token_path = get_token_path(&temp_dir, "access");
    let original_contents = fs::read_to_string(&token_path)?;

    // Manually corrupt the file (change bytes after "ENC:" prefix)
    let corrupted = if original_contents.len() > 20 {
        let mut chars: Vec<char> = original_contents.chars().collect();
        // Corrupt a character in the middle of the encrypted data
        let idx = original_contents.len() / 2;
        chars[idx] = if chars[idx] == 'A' { 'B' } else { 'A' };
        chars.into_iter().collect()
    } else {
        "ENC:corrupted_data".to_string()
    };
    fs::write(&token_path, corrupted)?;

    // Attempt to read → should return error (GCM tag verification fails)
    let result = storage.get_access_token().await;
    assert!(
        result.is_err(),
        "Reading corrupted encrypted data should fail"
    );

    // Cleanup
    drop(storage);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_wrong_format_prefix() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let token_path = get_token_path(&temp_dir, "access");

    // Manually create file with "WRONG:" prefix
    fs::write(&token_path, "WRONG:invalid_format_data")?;

    // Attempt to read → should return error
    let result = storage.get_access_token().await;
    assert!(
        result.is_err(),
        "Reading token with wrong prefix should fail"
    );

    // Cleanup
    drop(storage);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_empty_file() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let token_path = get_token_path(&temp_dir, "access");

    // Create empty token file
    fs::write(&token_path, "")?;

    // Attempt to read → empty string decodes as empty hex (backward compatibility)
    // This returns Some("") (empty token), which is technically valid
    let result = storage.get_access_token().await?;
    assert_eq!(
        result,
        Some(String::new()),
        "Empty file decodes to empty token (hex backward compatibility)"
    );

    // Cleanup
    drop(storage);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_both_tokens_encrypted() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let access_token = "test_access_token_both";
    let refresh_token = "test_refresh_token_both";

    // Store both access_token and refresh_token
    storage.store_access_token(access_token).await?;
    storage.store_refresh_token(refresh_token).await?;

    // Verify both files use "ENC:" format
    let access_path = get_token_path(&temp_dir, "access");
    let refresh_path = get_token_path(&temp_dir, "refresh");

    let access_contents = fs::read_to_string(&access_path)?;
    let refresh_contents = fs::read_to_string(&refresh_path)?;

    assert!(
        access_contents.starts_with("ENC:"),
        "Access token should use ENC: format"
    );
    assert!(
        refresh_contents.starts_with("ENC:"),
        "Refresh token should use ENC: format"
    );

    // Retrieve both tokens successfully
    let retrieved_access = storage.get_access_token().await?;
    let retrieved_refresh = storage.get_refresh_token().await?;

    assert_eq!(
        retrieved_access.as_deref(),
        Some(access_token),
        "Access token should match"
    );
    assert_eq!(
        retrieved_refresh.as_deref(),
        Some(refresh_token),
        "Refresh token should match"
    );

    // Cleanup
    drop(storage);
    Ok(())
}

#[tokio::test]
async fn test_file_storage_encryption_idempotent() -> Result<()> {
    let (storage, temp_dir) = create_test_storage()?;
    let test_token = "test_idempotent_token";
    let token_path = get_token_path(&temp_dir, "access");

    // Store token
    storage.store_access_token(test_token).await?;

    // Read and re-store token 3 times
    for i in 1..=3 {
        let retrieved = storage.get_access_token().await?;
        assert_eq!(
            retrieved.as_deref(),
            Some(test_token),
            "Token should be readable on iteration {}",
            i
        );

        storage.store_access_token(test_token).await?;

        // Verify still encrypted
        let contents = fs::read_to_string(&token_path)?;
        assert!(
            contents.starts_with("ENC:"),
            "Token should remain encrypted after iteration {}",
            i
        );
    }

    // Verify final token is still readable
    let final_retrieved = storage.get_access_token().await?;
    assert_eq!(
        final_retrieved.as_deref(),
        Some(test_token),
        "Final token should still be readable"
    );

    // Cleanup
    drop(storage);
    Ok(())
}
