#![allow(dead_code)]
//! TDD Tests for DBN Loader File Extension Filtering
//!
//! **Mission**: Verify DBN loader skips compressed files (.zst, .gz, .bz2, .tmp, etc.)
//!
//! **Test Strategy**:
//! 1. Create temp directory with mixed file types
//! 2. Add valid .dbn files
//! 3. Add compressed files (.dbn.zst, .dbn.gz, .dbn.bz2)
//! 4. Add invalid files (.tmp, .txt, .dbn.old)
//! 5. Verify loader only processes valid .dbn files
//!
//! **TDD Phases**:
//! - RED: Write tests first (all should FAIL initially)
//! - GREEN: Implement filtering logic (make tests PASS)
//! - REFACTOR: Improve quality and coverage

use backtesting_service::dbn_data_source::DbnDataSource;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;

/// Helper: Create temp directory with test DBN files
fn create_test_dbn_files() -> (
    TempDir,
    PathBuf,
    PathBuf,
    PathBuf,
    PathBuf,
    PathBuf,
    PathBuf,
) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let base_path = temp_dir.path();

    // Valid DBN file (we'll create empty files for testing structure)
    let valid_dbn = base_path.join("ES.FUT_valid.dbn");
    fs::write(&valid_dbn, b"").expect("Failed to create valid DBN file");

    // Compressed DBN files (should be skipped)
    let zst_file = base_path.join("ES.FUT_compressed.dbn.zst");
    fs::write(&zst_file, b"").expect("Failed to create .zst file");

    let gz_file = base_path.join("ES.FUT_compressed.dbn.gz");
    fs::write(&gz_file, b"").expect("Failed to create .gz file");

    let bz2_file = base_path.join("ES.FUT_compressed.dbn.bz2");
    fs::write(&bz2_file, b"").expect("Failed to create .bz2 file");

    // Invalid file types (should be skipped)
    let tmp_file = base_path.join("ES.FUT_temp.dbn.tmp");
    fs::write(&tmp_file, b"").expect("Failed to create .tmp file");

    let txt_file = base_path.join("ES.FUT_readme.txt");
    fs::write(&txt_file, b"").expect("Failed to create .txt file");

    (
        temp_dir, valid_dbn, zst_file, gz_file, bz2_file, tmp_file, txt_file,
    )
}

#[tokio::test]
async fn test_is_valid_dbn_file_valid_extension() {
    // RED: This test should FAIL initially because is_valid_dbn_file() doesn't exist
    let valid_paths = vec![
        "/path/to/ES.FUT.dbn",
        "/path/to/data.DBN",
        "relative/path/file.dbn",
        "./test.dbn",
    ];

    for path in valid_paths {
        // We need to access the helper function from DbnDataSource
        // This will fail until we implement it
        assert!(
            is_valid_dbn_file(path),
            "Expected {} to be valid DBN file",
            path
        );
    }
}

#[tokio::test]
async fn test_is_valid_dbn_file_compressed_extensions() {
    // RED: This test should FAIL initially
    let compressed_paths = vec![
        "/path/to/ES.FUT.dbn.zst",
        "/path/to/data.dbn.ZST",
        "/path/to/file.dbn.gz",
        "/path/to/file.dbn.GZ",
        "/path/to/file.dbn.bz2",
        "/path/to/file.dbn.BZ2",
    ];

    for path in compressed_paths {
        assert!(
            !is_valid_dbn_file(path),
            "Expected {} to be invalid (compressed)",
            path
        );
    }
}

#[tokio::test]
async fn test_is_valid_dbn_file_invalid_extensions() {
    // RED: This test should FAIL initially
    let invalid_paths = vec![
        "/path/to/file.tmp",
        "/path/to/file.dbn.tmp",
        "/path/to/file.txt",
        "/path/to/file.md",
        "/path/to/file.dbn.old",
        "/path/to/file.dbn.backup",
        "/path/to/file", // No extension
    ];

    for path in invalid_paths {
        assert!(!is_valid_dbn_file(path), "Expected {} to be invalid", path);
    }
}

#[tokio::test]
async fn test_load_skips_compressed_files_from_directory() {
    // RED: This test should FAIL because load_from_directory() doesn't exist
    let (_temp_dir, _valid_dbn, _zst, _gz, _bz2, _tmp, _txt) = create_test_dbn_files();

    // Create DbnDataSource that can scan a directory
    // This functionality doesn't exist yet
    let data_source = DbnDataSource::from_directory(_temp_dir.path().to_str().expect("INVARIANT: Path should be valid UTF-8"))
        .await
        .expect("Failed to create data source from directory");

    let symbols = data_source.available_symbols();

    // Should only find 1 valid symbol (from ES.FUT_valid.dbn)
    assert_eq!(
        symbols.len(),
        1,
        "Expected 1 valid symbol, found {}",
        symbols.len()
    );
}

#[tokio::test]
async fn test_add_symbol_mapping_validates_extension() {
    // RED: This test should FAIL because validation doesn't exist
    let mut file_mapping = HashMap::new();
    file_mapping.insert("ES.FUT".to_string(), "test_data/valid.dbn".to_string());

    let mut data_source = DbnDataSource::new(file_mapping)
        .await
        .expect("Failed to create data source");

    // Attempt to add compressed file - should be rejected or warned
    let result = data_source.add_symbol_mapping_validated(
        "NQ.FUT".to_string(),
        "test_data/compressed.dbn.zst".to_string(),
    );

    assert!(
        result.is_err(),
        "Expected error when adding compressed file"
    );
}

#[tokio::test]
async fn test_get_valid_dbn_files_from_directory() {
    // RED: This test should FAIL because function doesn't exist
    let (temp_dir, valid_dbn, _zst, _gz, _bz2, _tmp, _txt) = create_test_dbn_files();

    // Get list of valid DBN files from directory
    let valid_files = get_valid_dbn_files(temp_dir.path())
        .await
        .expect("Failed to get valid DBN files");

    // Should return only 1 valid file
    assert_eq!(valid_files.len(), 1, "Expected 1 valid file");
    assert_eq!(
        valid_files[0],
        valid_dbn.to_string_lossy().to_string(),
        "Expected valid.dbn file"
    );
}

#[tokio::test]
async fn test_case_insensitive_extension_filtering() {
    // RED: This test should FAIL initially
    let invalid_paths = vec![
        "file.dbn.ZST",
        "file.dbn.Zst",
        "file.dbn.GZ",
        "file.dbn.Gz",
        "file.dbn.BZ2",
        "file.dbn.Bz2",
        "file.dbn.TMP",
        "file.dbn.Tmp",
    ];

    for path in invalid_paths {
        assert!(
            !is_valid_dbn_file(path),
            "Expected {} to be invalid (case-insensitive)",
            path
        );
    }
}

#[tokio::test]
async fn test_intermediate_extension_filtering() {
    // Test files with intermediate extensions (should be rejected)
    let invalid_paths = vec![
        "file.uncompressed.dbn",
        "file.temp.dbn",
        "file.backup.dbn",
        "ES.FUT_data.v1.dbn",
        "ES.FUT_data.v2.dbn",
        "data.processed.dbn",
    ];

    for path in invalid_paths {
        assert!(
            !is_valid_dbn_file(path),
            "Expected {} to be invalid (intermediate extension)",
            path
        );
    }
}

#[tokio::test]
async fn test_real_directory_with_actual_files() {
    // This test uses actual test_data directory
    // RED: Should FAIL if compressed files are processed

    // Point to real test data directory
    let test_data_path = std::env::current_dir()
        .unwrap()
        .ancestors()
        .find(|p| p.join("test_data").exists())
        .expect("Could not find workspace root")
        .join("test_data/real/databento");

    if !test_data_path.exists() {
        eprintln!("Test data directory not found, skipping test");
        return;
    }

    // Get all valid DBN files
    let valid_files = get_valid_dbn_files(&test_data_path)
        .await
        .expect("Failed to scan directory");

    println!("Found {} valid DBN files", valid_files.len());

    // Verify no compressed files are included
    for file in &valid_files {
        assert!(
            !file.ends_with(".zst"),
            "Found .zst file in results: {}",
            file
        );
        assert!(
            !file.ends_with(".gz"),
            "Found .gz file in results: {}",
            file
        );
        assert!(
            !file.ends_with(".bz2"),
            "Found .bz2 file in results: {}",
            file
        );
        assert!(
            !file.ends_with(".tmp"),
            "Found .tmp file in results: {}",
            file
        );
    }

    // All files should end with .dbn
    for file in &valid_files {
        assert!(
            file.ends_with(".dbn"),
            "File doesn't end with .dbn: {}",
            file
        );
    }
}

// ============================================================================
// GREEN PHASE: Implementation
// ============================================================================

/// Check if file path is a valid DBN file (not compressed)
pub fn is_valid_dbn_file(path: &str) -> bool {
    let path_lower = path.to_lowercase();

    // Must end with .dbn
    if !path_lower.ends_with(".dbn") {
        return false;
    }

    // Reject compressed formats (case-insensitive)
    let compressed_extensions = [".dbn.zst", ".dbn.gz", ".dbn.bz2", ".dbn.xz"];
    for ext in &compressed_extensions {
        if path_lower.ends_with(ext) {
            return false;
        }
    }

    // Reject temporary/backup files
    let invalid_extensions = [
        ".dbn.tmp",
        ".dbn.old",
        ".dbn.backup",
        ".dbn.swp",
        ".uncompressed.dbn",
    ];
    for ext in &invalid_extensions {
        if path_lower.ends_with(ext) {
            return false;
        }
    }

    // Additional check: reject files with common intermediate extensions
    // but allow symbol names with dots (e.g., ES.FUT.dbn)
    let intermediate_patterns = [
        ".backup.dbn",
        ".temp.dbn",
        ".processed.dbn",
        ".v1.dbn",
        ".v2.dbn",
    ];
    for pattern in &intermediate_patterns {
        if path_lower.contains(pattern) {
            return false;
        }
    }

    true
}

/// Get list of valid DBN files from directory
pub async fn get_valid_dbn_files(dir: &std::path::Path) -> Result<Vec<String>, std::io::Error> {
    let mut valid_files = Vec::new();

    for entry in fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_file() {
            if let Some(path_str) = path.to_str() {
                if is_valid_dbn_file(path_str) {
                    valid_files.push(path_str.to_string());
                }
            }
        }
    }

    valid_files.sort();
    Ok(valid_files)
}

// Extension trait for validated operations (to be implemented)
trait DbnDataSourceExt {
    fn add_symbol_mapping_validated(
        &mut self,
        symbol: String,
        file_path: String,
    ) -> Result<(), String>;
    async fn from_directory(dir_path: &str) -> Result<DbnDataSource, anyhow::Error>;
}

impl DbnDataSourceExt for DbnDataSource {
    fn add_symbol_mapping_validated(
        &mut self,
        symbol: String,
        file_path: String,
    ) -> Result<(), String> {
        // Validate file extension
        if !is_valid_dbn_file(&file_path) {
            return Err(format!(
                "Invalid DBN file: {}. Must be .dbn file (not compressed)",
                file_path
            ));
        }

        // Add mapping if valid
        self.add_symbol_mapping(symbol, file_path);
        Ok(())
    }

    async fn from_directory(dir_path: &str) -> Result<DbnDataSource, anyhow::Error> {
        let dir = std::path::Path::new(dir_path);

        if !dir.exists() {
            return Err(anyhow::anyhow!("Directory does not exist: {}", dir_path));
        }

        let valid_files = get_valid_dbn_files(dir).await?;

        let mut file_mapping = HashMap::new();

        // Extract symbol from filename (e.g., "ES.FUT_valid.dbn" -> "ES.FUT")
        for file_path in valid_files {
            if let Some(file_name) = std::path::Path::new(&file_path).file_name() {
                if let Some(name_str) = file_name.to_str() {
                    // Extract symbol (everything before first underscore or .dbn)
                    let symbol = name_str
                        .split('_')
                        .next()
                        .unwrap_or(name_str)
                        .trim_end_matches(".dbn");
                    file_mapping.insert(symbol.to_string(), file_path);
                }
            }
        }

        DbnDataSource::new(file_mapping).await
    }
}
