//! Tests for validation functionality.
//! 
//! These tests verify that all validation checks work correctly, including
//! filename validity, permissions, circular renames, and OS-specific rules.
//! Tests use realistic scenarios and verify specific error types.
//! All tests are async to match the async API of the validation module.

use freneng::RenamingEngine;
use freneng::{FileRename, ValidationIssue};
use tokio::fs;
use tempfile::TempDir;

// ============================================================================
// validation module tests
// ============================================================================

#[tokio::test]
async fn test_validate_empty_filename() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("test.txt");
    fs::write(&file, "test").await.unwrap();
    
    let preview = engine.generate_preview(&[file.clone()], "%X/.//").await.unwrap();
    let validation = engine.validate(&preview.renames, false).await;
    
    println!("=== test_validate_empty_filename ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::EmptyFilename));
}

#[tokio::test]
async fn test_validate_source_not_found() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let nonexistent = temp_dir.path().join("nonexistent.txt");
    
    let renames = vec![
        FileRename {
            old_path: nonexistent.clone(),
            new_path: temp_dir.path().join("new.txt"),
            new_name: "new.txt".to_string(),
        }
    ];
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_source_not_found ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::SourceNotFound(_)));
}

#[tokio::test]
async fn test_validate_target_exists() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let old_file = temp_dir.path().join("old.txt");
    let new_file = temp_dir.path().join("new.txt");
    
    fs::write(&old_file, "test").await.unwrap();
    fs::write(&new_file, "existing").await.unwrap();
    
    let preview = engine.generate_preview(&[old_file.clone()], "new.%E").await.unwrap();
    let validation = engine.validate(&preview.renames, false).await;
    
    println!("=== test_validate_target_exists ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::TargetExists(_)));
}

#[tokio::test]
async fn test_validate_target_exists_with_overwrite() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let old_file = temp_dir.path().join("old.txt");
    let new_file = temp_dir.path().join("new.txt");
    
    fs::write(&old_file, "test").await.unwrap();
    fs::write(&new_file, "existing").await.unwrap();
    
    let preview = engine.generate_preview(&[old_file.clone()], "new.%E").await.unwrap();
    let validation = engine.validate(&preview.renames, true).await;
    
    println!("=== test_validate_target_exists_with_overwrite ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    // With overwrite=true, target existence is not an issue
    assert_eq!(validation.valid.len(), 1);
    assert_eq!(validation.issues.len(), 0);
}

#[tokio::test]
async fn test_validate_invalid_characters() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("test.txt");
    fs::write(&file, "test").await.unwrap();
    
    // Create a rename with invalid characters (OS-specific)
    let invalid_name = if cfg!(windows) {
        "test<file>.txt"
    } else {
        "test/file.txt"  // / is invalid on Unix
    };
    
    let renames = vec![
        FileRename {
            old_path: file.clone(),
            new_path: temp_dir.path().join(invalid_name),
            new_name: invalid_name.to_string(),
        }
    ];
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_invalid_characters ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    if !validation.issues.is_empty() {
        println!("  Issue: {:?}", validation.issues[0].1);
    }
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::InvalidCharacters(_)));
}

#[tokio::test]
#[cfg(windows)]
async fn test_validate_reserved_filename_windows() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("test.txt");
    fs::write(&file, "test").await.unwrap();
    
    // CON is a reserved filename on Windows
    let renames = vec![
        FileRename {
            old_path: file.clone(),
            new_path: temp_dir.path().join("CON.txt"),
            new_name: "CON.txt".to_string(),
        }
    ];
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_reserved_filename_windows ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::ReservedFilename(_)));
}

#[tokio::test]
async fn test_validate_path_too_long() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("test.txt");
    fs::write(&file, "test").await.unwrap();
    
    // Create a very long filename
    let long_name = "a".repeat(300); // Exceeds Windows MAX_PATH (260)
    let long_path = temp_dir.path().join(&long_name);
    
    let renames = vec![
        FileRename {
            old_path: file.clone(),
            new_path: long_path.clone(),
            new_name: long_name.clone(),
        }
    ];
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_path_too_long ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    if !validation.issues.is_empty() {
        println!("  Issue: {:?}", validation.issues[0].1);
    }
    
    // On Windows, this should fail; on Unix it might pass (4096 limit)
    #[cfg(windows)]
    {
        assert_eq!(validation.valid.len(), 0);
        assert_eq!(validation.issues.len(), 1);
        assert!(matches!(validation.issues[0].1, ValidationIssue::PathTooLong { .. }));
    }
    #[cfg(not(windows))]
    {
        // Unix allows longer paths, so this might pass
        // But we still check that validation ran
        assert!(validation.issues.is_empty() || matches!(validation.issues[0].1, ValidationIssue::PathTooLong { .. }));
    }
}

#[tokio::test]
async fn test_validate_circular_rename() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file1 = temp_dir.path().join("file1.txt");
    let file2 = temp_dir.path().join("file2.txt");
    
    fs::write(&file1, "test1").await.unwrap();
    fs::write(&file2, "test2").await.unwrap();
    
    // Circular: file1 -> file2, file2 -> file1
    let renames = vec![
        FileRename {
            old_path: file1.clone(),
            new_path: file2.clone(),
            new_name: "file2.txt".to_string(),
        },
        FileRename {
            old_path: file2.clone(),
            new_path: file1.clone(),
            new_name: "file1.txt".to_string(),
        },
    ];
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_circular_rename ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 2); // Both files have circular rename issue
    assert!(matches!(validation.issues[0].1, ValidationIssue::CircularRename { .. }));
    assert!(matches!(validation.issues[1].1, ValidationIssue::CircularRename { .. }));
}

#[tokio::test]
async fn test_validate_valid_rename() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("old.txt");
    fs::write(&file, "test").await.unwrap();
    
    let preview = engine.generate_preview(&[file.clone()], "new.%E").await.unwrap();
    let validation = engine.validate(&preview.renames, false).await;
    
    println!("=== test_validate_valid_rename ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 1);
    assert_eq!(validation.issues.len(), 0);
}

#[tokio::test]
async fn test_validate_multiple_files_mixed() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let valid_file = temp_dir.path().join("valid.txt");
    let missing_file = temp_dir.path().join("missing.txt");
    
    fs::write(&valid_file, "test").await.unwrap();
    
    let preview = engine.generate_preview(&[valid_file.clone()], "new.%E").await.unwrap();
    let mut renames = preview.renames;
    
    // Add an invalid rename
    renames.push(FileRename {
        old_path: missing_file.clone(),
        new_path: temp_dir.path().join("new2.txt"),
        new_name: "new2.txt".to_string(),
    });
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_multiple_files_mixed ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 1);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::SourceNotFound(_)));
}

#[tokio::test]
#[cfg(windows)]
async fn test_validate_invalid_format_windows() {
    let engine = RenamingEngine;
    let temp_dir = TempDir::new().unwrap();
    let file = temp_dir.path().join("test.txt");
    fs::write(&file, "test").await.unwrap();
    
    // Windows doesn't allow leading/trailing spaces
    let renames = vec![
        FileRename {
            old_path: file.clone(),
            new_path: temp_dir.path().join(" test.txt"),
            new_name: " test.txt".to_string(),
        }
    ];
    
    let validation = engine.validate(&renames, false).await;
    
    println!("=== test_validate_invalid_format_windows ===");
    println!("  Valid: {}", validation.valid.len());
    println!("  Issues: {}", validation.issues.len());
    
    assert_eq!(validation.valid.len(), 0);
    assert_eq!(validation.issues.len(), 1);
    assert!(matches!(validation.issues[0].1, ValidationIssue::InvalidFormat(_)));
}

