//! Integration tests for the complete renaming workflow.
//! 
//! These tests verify end-to-end functionality by combining preview generation
//! with actual file system operations. They verify that files are actually
//! renamed correctly and content is preserved.
//! All tests are async to match the async API of the engine.

use freneng::RenamingEngine;
use freneng::perform_renames;
use freneng::find_matching_files_recursive;
use tokio::fs;
use tempfile::TempDir;
use std::path::PathBuf;

// ============================================================================
// Integration tests: Full renaming workflow (generate_preview + perform_renames)
// ============================================================================

#[tokio::test]
async fn test_full_rename_workflow_simple() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("MyFile.txt");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview
    let preview = engine.generate_preview(&[file.clone()], "%L%N.%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_simple ===");
    println!("Pattern: %L%N.%E");
    println!("  Original: {}", file.display());
    println!("  Expected: {}", preview.renames[0].new_path.display());
    println!("  Successful: {}", result.successful.len());
    
    assert_eq!(result.successful.len(), 1);
    assert!(fs::metadata(&file).await.is_err());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    
    // Verify content preserved
    let content = fs::read_to_string(&preview.renames[0].new_path).await.unwrap();
    assert_eq!(content, "test content");
    
    // Verify correct name
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "myfile.txt");
}

#[tokio::test]
async fn test_full_rename_workflow_counter() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let files = vec![
        temp_dir.path().join("a.txt"),
        temp_dir.path().join("b.txt"),
        temp_dir.path().join("c.txt"),
    ];
    
    for file in &files {
        fs::write(file, "test").await.unwrap();
    }
    
    // Generate preview with counter
    let preview = engine.generate_preview(&files, "file_%C2.%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_counter ===");
    println!("Pattern: file_%C2.%E");
    println!("  Successful: {}", result.successful.len());
    for rename in &preview.renames {
        println!("  {} -> {}", rename.old_path.display(), rename.new_path.display());
    }
    
    assert_eq!(result.successful.len(), 3);
    
    // Verify all old files are gone
    for old_file in &files {
        assert!(!fs::metadata(&old_file).await.is_ok());
    }
    
    // Verify new files exist with correct names
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "file_01.txt");
    assert_eq!(preview.renames[1].new_path.file_name().unwrap(), "file_02.txt");
    assert_eq!(preview.renames[2].new_path.file_name().unwrap(), "file_03.txt");
    
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    assert!(fs::metadata(&preview.renames[1].new_path).await.is_ok());
    assert!(fs::metadata(&preview.renames[2].new_path).await.is_ok());
}

#[tokio::test]
async fn test_full_rename_workflow_with_modifiers() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("BACKUP_FILE.txt");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview with modifiers
    let preview = engine.generate_preview(&[file.clone()], "%L%N.%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_with_modifiers ===");
    println!("Pattern: %L%N.%E");
    println!("  Original: {}", file.display());
    println!("  New: {}", preview.renames[0].new_path.display());
    
    assert_eq!(result.successful.len(), 1);
    assert!(!fs::metadata(&file).await.is_ok());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "backup_file.txt");
}

#[tokio::test]
async fn test_full_rename_workflow_with_replace() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("backup_2024.txt");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview with replace modifier
    let preview = engine.generate_preview(&[file.clone()], "%N%R/backup/archive.%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_with_replace ===");
    println!("Pattern: %N%R/backup/archive.%E");
    println!("  Original: {}", file.display());
    println!("  New: {}", preview.renames[0].new_path.display());
    
    assert_eq!(result.successful.len(), 1);
    assert!(!fs::metadata(&file).await.is_ok());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "archive_2024.txt");
}

#[tokio::test]
async fn test_full_rename_workflow_substring_selection() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("backup_2024_01_01.dat");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview with substring selection
    let preview = engine.generate_preview(&[file.clone()], "%N1-6.%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_substring_selection ===");
    println!("Pattern: %N1-6.%E");
    println!("  Original: {}", file.display());
    println!("  New: {}", preview.renames[0].new_path.display());
    
    assert_eq!(result.successful.len(), 1);
    assert!(!fs::metadata(&file).await.is_ok());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "backup.dat");
}

#[tokio::test]
async fn test_full_rename_workflow_extension_only() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("document.txt");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview - change extension only
    let preview = engine.generate_preview(&[file.clone()], "%N.md").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_extension_only ===");
    println!("Pattern: %N.md");
    println!("  Original: {}", file.display());
    println!("  New: {}", preview.renames[0].new_path.display());
    
    assert_eq!(result.successful.len(), 1);
    assert!(!fs::metadata(&file).await.is_ok());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "document.md");
    
    // Verify content preserved
    let content = fs::read_to_string(&preview.renames[0].new_path).await.unwrap();
    assert_eq!(content, "test content");
}

#[tokio::test]
async fn test_full_rename_workflow_multiple_placeholders() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let subdir = temp_dir.path().join("photos");
    fs::create_dir_all(&subdir).await.unwrap();
    let file = subdir.join("IMG_001.jpg");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview with multiple placeholders
    let preview = engine.generate_preview(&[file.clone()], "%P_%N_%C.%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_multiple_placeholders ===");
    println!("Pattern: %P_%N_%C.%E");
    println!("  Original: {}", file.display());
    println!("  New: {}", preview.renames[0].new_path.display());
    
    assert_eq!(result.successful.len(), 1);
    assert!(!fs::metadata(&file).await.is_ok());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    
    // Verify name contains expected parts
    let new_name = preview.renames[0].new_path.file_name().unwrap().to_str().unwrap();
    assert!(new_name.contains("photos"));
    assert!(new_name.contains("IMG_001"));
    assert!(new_name.contains("1"));
    assert!(new_name.ends_with(".jpg"));
}

#[tokio::test]
async fn test_full_rename_workflow_prevents_empty_name() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("test.txt");
    fs::write(&file, "test content").await.unwrap();
    
    // Generate preview that would result in empty name
    let preview = engine.generate_preview(&[file.clone()], "%X/.//").await.unwrap();
    
    // Verify it detected empty name
    assert!(preview.has_empty_names);
    assert_eq!(preview.renames[0].new_name, "");
    
    // Attempt to perform rename - should skip due to empty name
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_prevents_empty_name ===");
    println!("Pattern: %X/.//");
    println!("  Original: {}", file.display());
    println!("  Successful: {}", result.successful.len());
    println!("  Skipped: {}", result.skipped.len());
    
    // File should still exist (not renamed)
    assert!(fs::metadata(&file).await.is_ok());
    // Empty name should cause skip (though perform_renames doesn't check this,
    // the engine should prevent it, but let's verify the file wasn't renamed)
    assert_eq!(result.successful.len(), 0);
}

#[tokio::test]
async fn test_full_rename_workflow_with_overwrite() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let old_file = temp_dir.path().join("old.txt");
    let existing_file = temp_dir.path().join("new.txt");
    
    fs::write(&old_file, "new content").await.unwrap();
    fs::write(&existing_file, "old content").await.unwrap();
    
    // Generate preview
    let preview = engine.generate_preview(&[old_file.clone()], "new.%E").await.unwrap();
    
    // Perform rename with overwrite
    let result = perform_renames(&preview.renames, true).await.unwrap();
    
    println!("=== test_full_rename_workflow_with_overwrite ===");
    println!("Pattern: new.%E (overwrite=true)");
    println!("  Original: {}", old_file.display());
    println!("  New: {}", preview.renames[0].new_path.display());
    println!("  Successful: {}", result.successful.len());
    
    assert_eq!(result.successful.len(), 1);
    assert!(!fs::metadata(&old_file).await.is_ok());
    assert!(fs::metadata(&preview.renames[0].new_path).await.is_ok());
    
    // Verify content was overwritten
    let content = fs::read_to_string(&preview.renames[0].new_path).await.unwrap();
    assert_eq!(content, "new content");
}

#[tokio::test]
async fn test_full_rename_workflow_complex_pattern() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let files = vec![
        temp_dir.path().join("photo1.JPG"),
        temp_dir.path().join("photo2.JPG"),
        temp_dir.path().join("photo3.JPG"),
    ];
    
    for file in &files {
        fs::write(file, "test").await.unwrap();
    }
    
    // Complex pattern: lowercase name, add counter, lowercase extension
    let preview = engine.generate_preview(&files, "%L%N_%C2.%L%E").await.unwrap();
    
    // Perform rename
    let result = perform_renames(&preview.renames, false).await.unwrap();
    
    println!("=== test_full_rename_workflow_complex_pattern ===");
    println!("Pattern: %L%N_%C2.%L%E");
    for rename in &preview.renames {
        println!("  {} -> {}", rename.old_path.display(), rename.new_path.display());
    }
    
    assert_eq!(result.successful.len(), 3);
    
    // Verify all renamed correctly
    assert_eq!(preview.renames[0].new_path.file_name().unwrap(), "photo1_01.jpg");
    assert_eq!(preview.renames[1].new_path.file_name().unwrap(), "photo2_02.jpg");
    assert_eq!(preview.renames[2].new_path.file_name().unwrap(), "photo3_03.jpg");
    
    for rename in &preview.renames {
        assert!(fs::metadata(&rename.old_path).await.is_err());
        assert!(fs::metadata(&rename.new_path).await.is_ok());
    }
}

#[tokio::test]
async fn test_recursive_files_preserve_paths_in_preview() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    
    // Create directory structure with files at multiple levels
    let subdir1 = temp_dir.path().join("docs");
    let subdir2 = temp_dir.path().join("images");
    let subsubdir = subdir2.join("thumbnails");
    
    // Ensure all directories exist
    fs::create_dir_all(&subdir1).await.unwrap();
    fs::create_dir_all(&subsubdir).await.unwrap();
    
    let files = vec![
        temp_dir.path().join("root.txt"),              // Root level
        subdir1.join("document.pdf"),                  // First subdirectory
        subdir2.join("photo.jpg"),                     // Second subdirectory
        subsubdir.join("thumb.png"),                   // Nested subdirectory
    ];
    
    for file in &files {
        fs::write(file, "test content").await.unwrap();
    }
    
    // Find files recursively - use multiple patterns to match our test files
    let temp_path = temp_dir.path().canonicalize().unwrap();
    let mut test_files = Vec::new();
    
    // Find each file type separately
    for ext in &["txt", "pdf", "jpg", "png"] {
        let pattern = format!("{}/**/*.{}", temp_path.display(), ext);
        let found = find_matching_files_recursive(&pattern, true).await.unwrap();
        for f in found {
            if files.contains(&f) && !test_files.contains(&f) {
                test_files.push(f);
            }
        }
    }
    test_files.sort();
    
    assert_eq!(test_files.len(), 4, "Should find all 4 test files recursively");
    
    // Generate preview - should preserve directory structure
    let preview = engine.generate_preview(&test_files, "%L%N_v2.%E").await.unwrap();
    
    println!("=== test_recursive_files_preserve_paths_in_preview ===");
    println!("Pattern: %L%N_v2.%E");
    for rename in &preview.renames {
        println!("  {} -> {}", rename.old_path.display(), rename.new_path.display());
    }
    
    // Verify all files are in preview
    assert_eq!(preview.renames.len(), 4);
    
    // Verify paths are preserved correctly
    for rename in &preview.renames {
        // old_path should be the full original path
        assert!(files.contains(&rename.old_path), 
            "old_path should be one of the original files: {:?}", rename.old_path);
        
        // new_path should be in the same directory as old_path
        let old_parent = rename.old_path.parent().unwrap();
        let new_parent = rename.new_path.parent().unwrap();
        assert_eq!(old_parent, new_parent,
            "new_path should be in same directory as old_path. old: {:?}, new: {:?}",
            old_parent, new_parent);
        
        // new_name should just be the filename
        assert_eq!(rename.new_name, rename.new_path.file_name().unwrap().to_str().unwrap(),
            "new_name should match filename part of new_path");
        
        // Verify the new filename is correct
        let old_name = rename.old_path.file_name().unwrap().to_str().unwrap();
        let expected_new_name = format!("{}_v2.{}", 
            old_name.rsplit('.').skip(1).collect::<Vec<_>>().join(".").to_lowercase(),
            old_name.rsplit('.').next().unwrap().to_lowercase());
        assert_eq!(rename.new_name, expected_new_name,
            "new_name should match expected pattern");
    }
    
    // Verify specific files maintain their directory structure
    let root_rename = preview.renames.iter()
        .find(|r| r.old_path.file_name().unwrap() == "root.txt").unwrap();
    assert!(root_rename.new_path.parent().unwrap() == temp_dir.path(),
        "Root file should stay in root directory");
    
    let doc_rename = preview.renames.iter()
        .find(|r| r.old_path.file_name().unwrap() == "document.pdf").unwrap();
    assert!(doc_rename.new_path.parent().unwrap() == subdir1,
        "Document should stay in docs subdirectory");
    
    let thumb_rename = preview.renames.iter()
        .find(|r| r.old_path.file_name().unwrap() == "thumb.png").unwrap();
        assert!(thumb_rename.new_path.parent().unwrap() == subsubdir,
        "Thumbnail should stay in nested subdirectory");
}

#[tokio::test]
async fn test_full_rename_workflow_with_test_data_structure() {
    let engine = RenamingEngine;
    // Use actual test_data files from nested structure
    let files = vec![
        PathBuf::from("../test_data/Photos/IMG_001.jpg"),
        PathBuf::from("../test_data/Photos/IMG_002.jpg"),
        PathBuf::from("../test_data/Photos/IMG_003.jpg"),
    ];
    
    // Generate preview
    let preview = engine.generate_preview(&files, "%L%N_%C2.%E").await.unwrap();
    
    // Verify preview preserves directory structure
    for rename in &preview.renames {
        assert!(rename.old_path.to_string_lossy().contains("Photos"));
        assert!(rename.new_path.to_string_lossy().contains("Photos"));
        assert_eq!(rename.old_path.parent(), rename.new_path.parent(),
            "Files should stay in same directory");
    }
    
    println!("=== test_full_rename_workflow_with_test_data_structure ===");
    println!("Pattern: %L%N_%C2.%E");
    for rename in &preview.renames {
        println!("  {} -> {}", rename.old_path.display(), rename.new_path.display());
    }
    
    assert_eq!(preview.renames.len(), 3);
    assert_eq!(preview.renames[0].new_name, "img_001_01.jpg");
    assert_eq!(preview.renames[1].new_name, "img_002_02.jpg");
    assert_eq!(preview.renames[2].new_name, "img_003_03.jpg");
    
    // Note: We don't actually perform the rename to avoid modifying test_data
    // In a real scenario, you would call perform_renames here
}

#[tokio::test]
async fn test_full_rename_workflow_with_deeply_nested_test_data() {
    let engine = RenamingEngine;
    // Use files from deeply nested test_data structure
    let files = vec![
        PathBuf::from("../test_data/Photos/Vacation/2024/summer_001.jpg"),
        PathBuf::from("../test_data/Photos/Vacation/2024/summer_002.jpg"),
    ];
    
    // Generate preview with parent directory token
    let preview = engine.generate_preview(&files, "%P_%L%N.%E").await.unwrap();
    
    println!("=== test_full_rename_workflow_with_deeply_nested_test_data ===");
    println!("Pattern: %P_%L%N.%E");
    for rename in &preview.renames {
        println!("  {} -> {}", rename.old_path.display(), rename.new_name);
        // Verify paths are preserved in deeply nested structure
        assert!(rename.old_path.to_string_lossy().contains("Photos/Vacation/2024"));
        assert!(rename.new_path.to_string_lossy().contains("Photos/Vacation/2024"));
        assert_eq!(rename.old_path.parent(), rename.new_path.parent(),
            "Files should stay in same directory");
        // Verify parent directory name (2024) is in the new name
        assert!(rename.new_name.contains("2024"));
    }
    
    assert_eq!(preview.renames.len(), 2);
}

#[tokio::test]
async fn test_full_rename_workflow_with_mixed_test_data_types() {
    let engine = RenamingEngine;
    // Use files from different directories and types
    let files = vec![
        PathBuf::from("../test_data/Documents/report.pdf"),
        PathBuf::from("../test_data/Documents/Projects/project_a.txt"),
        PathBuf::from("../test_data/Documents/Archive/doc1.txt"),
    ];
    
    // Generate preview
    let preview = engine.generate_preview(&files, "%L%N_%C2.%E").await.unwrap();
    
    println!("=== test_full_rename_workflow_with_mixed_test_data_types ===");
    println!("Pattern: %L%N_%C2.%E");
    for rename in &preview.renames {
        println!("  {} -> {}", rename.old_path.display(), rename.new_name);
        // Verify each file stays in its original directory
        let old_parent = rename.old_path.parent().unwrap();
        let new_parent = rename.new_path.parent().unwrap();
        assert_eq!(old_parent, new_parent,
            "File should stay in same directory: {:?}", old_parent);
    }
    
    assert_eq!(preview.renames.len(), 3);
    
    // Verify extensions are preserved
    assert_eq!(preview.renames[0].new_name, "report_01.pdf");
    assert_eq!(preview.renames[1].new_name, "project_a_02.txt");
    assert_eq!(preview.renames[2].new_name, "doc1_03.txt");
    
    // Verify each file is in its correct subdirectory
    assert!(preview.renames[0].old_path.to_string_lossy().contains("Documents"));
    assert!(preview.renames[1].old_path.to_string_lossy().contains("Documents/Projects"));
    assert!(preview.renames[2].old_path.to_string_lossy().contains("Documents/Archive"));
}

