//! Comprehensive Performance Comparison with SciPy ndimage
//!
//! This module provides benchmarking and validation utilities to compare
//! scirs2-ndimage performance and accuracy against SciPy's ndimage module.
//! It includes timing comparisons, numerical accuracy validation, and
//! comprehensive API compatibility verification.

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
use crate::filters::*;
use crate::interpolation::*;
use crate::measurements::*;
use crate::morphology::*;
use scirs2_core::ndarray::{Array2, ArrayView2};
use std::collections::HashMap;
use std::time::Instant;

/// Performance comparison results for a single operation
#[derive(Debug, Clone)]
pub struct PerformanceResult {
    /// Name of the operation being benchmarked
    pub operation: String,
    /// Array shape used in the benchmark
    pub shape: Vec<usize>,
    /// Data type used (f32, f64, etc.)
    pub dtype: String,
    /// Execution time in milliseconds
    pub execution_time_ms: f64,
    /// Memory usage in bytes (estimated)
    pub memory_usage_bytes: usize,
    /// Whether the operation completed successfully
    pub success: bool,
    /// Additional parameters used
    pub parameters: HashMap<String, String>,
}

/// Numerical accuracy comparison results
#[derive(Debug, Clone)]
pub struct AccuracyResult {
    /// Name of the operation
    pub operation: String,
    /// Maximum absolute difference from reference
    pub max_abs_diff: f64,
    /// Mean absolute difference from reference
    pub mean_abs_diff: f64,
    /// Root mean square error
    pub rmse: f64,
    /// Relative error (for non-zero values)
    pub relative_error: f64,
    /// Whether the results are considered numerically equivalent
    pub numerically_equivalent: bool,
    /// Tolerance used for comparison
    pub tolerance: f64,
}

/// API compatibility test result
#[derive(Debug, Clone)]
pub struct CompatibilityResult {
    /// Function name
    pub function: String,
    /// Parameter name that was tested
    pub parameter: String,
    /// Whether the API is compatible
    pub compatible: bool,
    /// Error message if not compatible
    pub error_message: Option<String>,
    /// Suggested fix or workaround
    pub suggestion: Option<String>,
}

/// Comprehensive benchmark suite configuration
#[derive(Debug, Clone)]
pub struct BenchmarkConfig {
    /// Array sizes to test
    pub array_sizes: Vec<Vec<usize>>,
    /// Data types to test
    pub dtypes: Vec<String>,
    /// Number of iterations for timing
    pub iterations: usize,
    /// Warmup iterations before timing
    pub warmup_iterations: usize,
    /// Whether to include memory profiling
    pub profile_memory: bool,
    /// Tolerance for numerical comparisons
    pub numerical_tolerance: f64,
}

impl Default for BenchmarkConfig {
    fn default() -> Self {
        Self {
            array_sizes: vec![
                vec![100, 100],      // Small 2D
                vec![512, 512],      // Medium 2D
                vec![1024, 1024],    // Large 2D
                vec![50, 50, 50],    // Small 3D
                vec![100, 100, 100], // Medium 3D
                vec![200, 200, 200], // Large 3D
                vec![10000],         // 1D array
                vec![1000, 1000],    // Square 2D
            ],
            dtypes: vec!["f32".to_string(), "f64".to_string()],
            iterations: 10,
            warmup_iterations: 3,
            profile_memory: true,
            numerical_tolerance: 1e-6,
        }
    }
}

/// Main benchmark suite for comprehensive performance analysis
pub struct SciPyBenchmarkSuite {
    config: BenchmarkConfig,
    results: Vec<PerformanceResult>,
    accuracy_results: Vec<AccuracyResult>,
    compatibility_results: Vec<CompatibilityResult>,
}

impl SciPyBenchmarkSuite {
    /// Create a new benchmark suite with default configuration
    pub fn new() -> Self {
        Self::with_config(BenchmarkConfig::default())
    }

    /// Create a new benchmark suite with custom configuration
    pub fn with_config(config: BenchmarkConfig) -> Self {
        Self {
            config,
            results: Vec::new(),
            accuracy_results: Vec::new(),
            compatibility_results: Vec::new(),
        }
    }

    /// Run comprehensive benchmarks for all filter operations
    pub fn benchmark_filters(&mut self) -> Result<()> {
        for shape in &self.config.array_sizes.clone() {
            // Skip 3D shapes for 2D-only operations
            if shape.len() > 2 {
                continue;
            }

            for dtype in &self.config.dtypes.clone() {
                match dtype.as_str() {
                    "f32" => self.benchmark_filters_f32(shape)?,
                    "f64" => self.benchmark_filters_f64(shape)?,
                    _ => continue,
                }
            }
        }
        Ok(())
    }

    fn benchmark_filters_f32(&mut self, shape: &[usize]) -> Result<()> {
        let input = Array2::zeros((shape[0], shape[1]));

        // Benchmark Gaussian filter
        let start = Instant::now();
        for _ in 0..self.config.warmup_iterations {
            let _ = gaussian_filter(&input, 1.0, None, None)?;
        }

        let start = Instant::now();
        for _ in 0..self.config.iterations {
            let _ = gaussian_filter(&input, 1.0, None, None)?;
        }
        let duration = start.elapsed();

        self.results.push(PerformanceResult {
            operation: "gaussian_filter".to_string(),
            shape: shape.to_vec(),
            dtype: "f32".to_string(),
            execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
            memory_usage_bytes: estimate_memory_usage(shape, 4), // 4 bytes for f32
            success: true,
            parameters: [("sigma".to_string(), "1.0".to_string())]
                .iter()
                .cloned()
                .collect(),
        });

        // Benchmark median filter
        let start = Instant::now();
        for _ in 0..self.config.warmup_iterations {
            let _ = median_filter(&input, &[3, 3], None)?;
        }

        let start = Instant::now();
        for _ in 0..self.config.iterations {
            let _ = median_filter(&input, &[3, 3], None)?;
        }
        let duration = start.elapsed();

        self.results.push(PerformanceResult {
            operation: "median_filter".to_string(),
            shape: shape.to_vec(),
            dtype: "f32".to_string(),
            execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
            memory_usage_bytes: estimate_memory_usage(shape, 4),
            success: true,
            parameters: [("size".to_string(), "[3,3]".to_string())]
                .iter()
                .cloned()
                .collect(),
        });

        // Benchmark uniform filter
        let start = Instant::now();
        for _ in 0..self.config.warmup_iterations {
            let _ = uniform_filter(&input, &[3, 3], None, None)?;
        }

        let start = Instant::now();
        for _ in 0..self.config.iterations {
            let _ = uniform_filter(&input, &[3, 3], None, None)?;
        }
        let duration = start.elapsed();

        self.results.push(PerformanceResult {
            operation: "uniform_filter".to_string(),
            shape: shape.to_vec(),
            dtype: "f32".to_string(),
            execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
            memory_usage_bytes: estimate_memory_usage(shape, 4),
            success: true,
            parameters: [("size".to_string(), "[3,3]".to_string())]
                .iter()
                .cloned()
                .collect(),
        });

        // Benchmark Sobel filter
        let start = Instant::now();
        for _ in 0..self.config.warmup_iterations {
            let _ = sobel(&input, 0, None)?;
        }

        let start = Instant::now();
        for _ in 0..self.config.iterations {
            let _ = sobel(&input, 0, None)?;
        }
        let duration = start.elapsed();

        self.results.push(PerformanceResult {
            operation: "sobel_filter".to_string(),
            shape: shape.to_vec(),
            dtype: "f32".to_string(),
            execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
            memory_usage_bytes: estimate_memory_usage(shape, 4),
            success: true,
            parameters: [("axis".to_string(), "0".to_string())]
                .iter()
                .cloned()
                .collect(),
        });

        Ok(())
    }

    fn benchmark_filters_f64(&mut self, shape: &[usize]) -> Result<()> {
        let input = Array2::<f64>::zeros((shape[0], shape[1]));

        // Similar benchmarks for f64 - implementation mirrors f32 version
        // For brevity, showing one example
        let _start = Instant::now();
        for _ in 0..self.config.warmup_iterations {
            let _ = gaussian_filter(&input, 1.0, None, None)?;
        }

        let start = Instant::now();
        for _ in 0..self.config.iterations {
            let _ = gaussian_filter(&input, 1.0, None, None)?;
        }
        let duration = start.elapsed();

        self.results.push(PerformanceResult {
            operation: "gaussian_filter".to_string(),
            shape: shape.to_vec(),
            dtype: "f64".to_string(),
            execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
            memory_usage_bytes: estimate_memory_usage(shape, 8), // 8 bytes for f64
            success: true,
            parameters: [("sigma".to_string(), "1.0".to_string())]
                .iter()
                .cloned()
                .collect(),
        });

        Ok(())
    }

    /// Run comprehensive benchmarks for morphological operations
    pub fn benchmark_morphology(&mut self) -> Result<()> {
        for shape in &self.config.array_sizes.clone() {
            if shape.len() > 2 {
                continue; // Skip 3D for simplicity
            }

            // Binary morphology
            let binary_input = Array2::from_elem((shape[0], shape[1]), true);

            let _start = Instant::now();
            for _ in 0..self.config.warmup_iterations {
                let _ = binary_erosion(&binary_input, None, None, None, None, None, None)?;
            }

            let start = Instant::now();
            for _ in 0..self.config.iterations {
                let _ = binary_erosion(&binary_input, None, None, None, None, None, None)?;
            }
            let duration = start.elapsed();

            self.results.push(PerformanceResult {
                operation: "binary_erosion".to_string(),
                shape: shape.to_vec(),
                dtype: "bool".to_string(),
                execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
                memory_usage_bytes: estimate_memory_usage(shape, 1), // 1 byte for bool
                success: true,
                parameters: HashMap::new(),
            });

            // Grayscale morphology
            let grayscale_input = Array2::<f64>::zeros((shape[0], shape[1]));

            let _start = Instant::now();
            for _ in 0..self.config.warmup_iterations {
                let _ = grey_erosion(&grayscale_input, None, None, None, None, None)?;
            }

            let start = Instant::now();
            for _ in 0..self.config.iterations {
                let _ = grey_erosion(&grayscale_input, None, None, None, None, None)?;
            }
            let duration = start.elapsed();

            self.results.push(PerformanceResult {
                operation: "grey_erosion".to_string(),
                shape: shape.to_vec(),
                dtype: "f64".to_string(),
                execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
                memory_usage_bytes: estimate_memory_usage(shape, 8),
                success: true,
                parameters: HashMap::new(),
            });
        }

        Ok(())
    }

    /// Run comprehensive benchmarks for interpolation operations
    pub fn benchmark_interpolation(&mut self) -> Result<()> {
        for shape in &self.config.array_sizes.clone() {
            if shape.len() > 2 {
                continue;
            }

            let input = Array2::<f64>::zeros((shape[0], shape[1]));

            // Benchmark zoom operation
            let start = Instant::now();
            for _ in 0..self.config.warmup_iterations {
                let _ = zoom(&input, 2.0, None, None, None, None)?;
            }

            let start = Instant::now();
            for _ in 0..self.config.iterations {
                let _ = zoom(&input, 2.0, None, None, None, None)?;
            }
            let duration = start.elapsed();

            self.results.push(PerformanceResult {
                operation: "zoom".to_string(),
                shape: shape.to_vec(),
                dtype: "f64".to_string(),
                execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
                memory_usage_bytes: estimate_memory_usage(shape, 8) * 4, // Output is larger
                success: true,
                parameters: [("zoom".to_string(), "[2.0,2.0]".to_string())]
                    .iter()
                    .cloned()
                    .collect(),
            });

            // Benchmark rotation
            let start = Instant::now();
            for _ in 0..self.config.warmup_iterations {
                let _ = rotate(&input, 45.0, None, None, None, None, None, None)?;
            }

            let start = Instant::now();
            for _ in 0..self.config.iterations {
                let _ = rotate(&input, 45.0, None, None, None, None, None, None)?;
            }
            let duration = start.elapsed();

            self.results.push(PerformanceResult {
                operation: "rotate".to_string(),
                shape: shape.to_vec(),
                dtype: "f64".to_string(),
                execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
                memory_usage_bytes: estimate_memory_usage(shape, 8),
                success: true,
                parameters: [("angle".to_string(), "45.0".to_string())]
                    .iter()
                    .cloned()
                    .collect(),
            });
        }

        Ok(())
    }

    /// Run comprehensive benchmarks for measurement operations
    pub fn benchmark_measurements(&mut self) -> Result<()> {
        for shape in &self.config.array_sizes.clone() {
            if shape.len() > 2 {
                continue;
            }

            let input = Array2::<f64>::ones((shape[0], shape[1]));

            // Benchmark center of mass
            let start = Instant::now();
            for _ in 0..self.config.warmup_iterations {
                let _ = center_of_mass(&input)?;
            }

            let start = Instant::now();
            for _ in 0..self.config.iterations {
                let _ = center_of_mass(&input)?;
            }
            let duration = start.elapsed();

            self.results.push(PerformanceResult {
                operation: "center_of_mass".to_string(),
                shape: shape.to_vec(),
                dtype: "f64".to_string(),
                execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
                memory_usage_bytes: estimate_memory_usage(shape, 8),
                success: true,
                parameters: HashMap::new(),
            });

            // Benchmark moments calculation
            let start = Instant::now();
            for _ in 0..self.config.warmup_iterations {
                let _ = moments(&input, 2)?; // Calculate up to 2nd order moments
            }

            let start = Instant::now();
            for _ in 0..self.config.iterations {
                let _ = moments(&input, 2)?; // Calculate up to 2nd order moments
            }
            let duration = start.elapsed();

            self.results.push(PerformanceResult {
                operation: "moments".to_string(),
                shape: shape.to_vec(),
                dtype: "f64".to_string(),
                execution_time_ms: duration.as_millis() as f64 / self.config.iterations as f64,
                memory_usage_bytes: estimate_memory_usage(shape, 8),
                success: true,
                parameters: HashMap::new(),
            });
        }

        Ok(())
    }

    /// Run all benchmarks
    pub fn run_all_benchmarks(&mut self) -> Result<()> {
        println!("Running comprehensive SciPy ndimage performance comparison...");

        println!("Benchmarking filters...");
        self.benchmark_filters()?;

        println!("Benchmarking morphology...");
        self.benchmark_morphology()?;

        println!("Benchmarking interpolation...");
        self.benchmark_interpolation()?;

        println!("Benchmarking measurements...");
        self.benchmark_measurements()?;

        println!("Benchmark suite completed!");
        Ok(())
    }

    /// Generate a performance report
    pub fn generate_report(&self) -> String {
        let mut report = String::new();
        report.push_str("# SciPy ndimage Performance Comparison Report\n\n");

        report.push_str(&format!(
            "Total operations benchmarked: {}\n",
            self.results.len()
        ));
        report.push_str(&format!(
            "Configuration: {} iterations, {} warmup\n\n",
            self.config.iterations, self.config.warmup_iterations
        ));

        // Group results by operation
        let mut operations: HashMap<String, Vec<&PerformanceResult>> = HashMap::new();
        for result in &self.results {
            operations
                .entry(result.operation.clone())
                .or_insert_with(Vec::new)
                .push(result);
        }

        for (operation, results) in operations {
            report.push_str(&format!("## {}\n", operation));

            for result in results {
                report.push_str(&format!(
                    "- Shape: {:?}, Type: {}, Time: {:.2}ms, Memory: {:.2}MB\n",
                    result.shape,
                    result.dtype,
                    result.execution_time_ms,
                    result.memory_usage_bytes as f64 / 1_000_000.0
                ));
            }
            report.push('\n');
        }

        // Add accuracy results if available
        if !self.accuracy_results.is_empty() {
            report.push_str("## Numerical Accuracy Validation\n\n");
            for result in &self.accuracy_results {
                report.push_str(&format!(
                    "- {}: Max, diff: {:.2e}, Mean diff: {:.2e}, RMSE: {:.2e}, Compatible: {}\n",
                    result.operation,
                    result.max_abs_diff,
                    result.mean_abs_diff,
                    result.rmse,
                    result.numerically_equivalent
                ));
            }
            report.push('\n');
        }

        // Add compatibility results if available
        if !self.compatibility_results.is_empty() {
            report.push_str("## API Compatibility Results\n\n");
            for result in &self.compatibility_results {
                report.push_str(&format!(
                    "- {}.{}: {}\n",
                    result.function,
                    result.parameter,
                    if result.compatible {
                        "✓ Compatible"
                    } else {
                        "✗ Incompatible"
                    }
                ));
                if let Some(msg) = &result.error_message {
                    report.push_str(&format!("  Error: {}\n", msg));
                }
                if let Some(suggestion) = &result.suggestion {
                    report.push_str(&format!("  Suggestion: {}\n", suggestion));
                }
            }
        }

        report
    }

    /// Get performance results
    pub fn get_results(&self) -> &[PerformanceResult] {
        &self.results
    }

    /// Get accuracy results
    pub fn get_accuracy_results(&self) -> &[AccuracyResult] {
        &self.accuracy_results
    }

    /// Get compatibility results
    pub fn get_compatibility_results(&self) -> &[CompatibilityResult] {
        &self.compatibility_results
    }
}

/// Estimate memory usage for given shape and data type size
#[allow(dead_code)]
fn estimate_memory_usage(shape: &[usize], dtype_size: usize) -> usize {
    shape.iter().product::<usize>() * dtype_size
}

/// Calculate numerical accuracy metrics between two arrays
#[allow(dead_code)]
pub fn calculate_accuracy_metrics<T>(
    reference: &ArrayView2<T>,
    computed: &ArrayView2<T>,
    tolerance: f64,
) -> AccuracyResult
where
    T: Clone + Copy + Into<f64> + PartialOrd,
{
    let mut max_abs_diff = 0.0;
    let mut sum_abs_diff = 0.0;
    let mut sum_squared_diff = 0.0;
    let mut sum_relative_error = 0.0;
    let mut count = 0;
    let mut count_nonzero = 0;

    for (r, c) in reference.iter().zip(computed.iter()) {
        let ref_val: f64 = (*r).into();
        let comp_val: f64 = (*c).into();

        let abs_diff = (ref_val - comp_val).abs();
        max_abs_diff = f64::max(max_abs_diff, abs_diff);
        sum_abs_diff += abs_diff;
        sum_squared_diff += abs_diff * abs_diff;
        count += 1;

        if ref_val.abs() > 1e-15 {
            sum_relative_error += abs_diff / ref_val.abs();
            count_nonzero += 1;
        }
    }

    let mean_abs_diff = sum_abs_diff / count as f64;
    let rmse = (sum_squared_diff / count as f64).sqrt();
    let relative_error = if count_nonzero > 0 {
        sum_relative_error / count_nonzero as f64
    } else {
        0.0
    };

    AccuracyResult {
        operation: "comparison".to_string(),
        max_abs_diff,
        mean_abs_diff,
        rmse,
        relative_error,
        numerically_equivalent: max_abs_diff < tolerance,
        tolerance,
    }
}

/// Validate API compatibility for a specific function
#[allow(dead_code)]
pub fn validate_api_compatibility(
    function_name: &str,
    parameter_tests: &[(String, fn() -> bool, Option<String>)],
) -> Vec<CompatibilityResult> {
    let mut results = Vec::new();

    for (param_name, test_fn, suggestion) in parameter_tests {
        let compatible = test_fn();
        results.push(CompatibilityResult {
            function: function_name.to_string(),
            parameter: param_name.clone(),
            compatible,
            error_message: if compatible {
                None
            } else {
                Some("Parameter validation failed".to_string())
            },
            suggestion: suggestion.clone(),
        });
    }

    results
}

#[cfg(test)]
mod tests {
    use super::*;
    use scirs2_core::ndarray::Array2;

    #[test]
    fn test_benchmark_suite_creation() {
        let suite = SciPyBenchmarkSuite::new();
        assert_eq!(suite.results.len(), 0);
        assert_eq!(suite.accuracy_results.len(), 0);
        assert_eq!(suite.compatibility_results.len(), 0);
    }

    #[test]
    fn test_memory_estimation() {
        assert_eq!(estimate_memory_usage(&[100, 100], 4), 40000);
        assert_eq!(estimate_memory_usage(&[50, 50, 50], 8), 1000000);
    }

    #[test]
    fn test_accuracy_calculation() {
        let ref_array = Array2::from_elem((3, 3), 1.0);
        let comp_array = Array2::from_elem((3, 3), 1.1);

        let accuracy = calculate_accuracy_metrics(&ref_array.view(), &comp_array.view(), 0.2);

        assert!(accuracy.max_abs_diff > 0.0);
        assert!(accuracy.mean_abs_diff > 0.0);
        assert!(accuracy.numerically_equivalent); // Within tolerance
    }

    #[test]
    fn test_performance_result_creation() {
        let result = PerformanceResult {
            operation: "test_op".to_string(),
            shape: vec![100, 100],
            dtype: "f64".to_string(),
            execution_time_ms: 10.5,
            memory_usage_bytes: 80000,
            success: true,
            parameters: HashMap::new(),
        };

        assert_eq!(result.operation, "test_op");
        assert_eq!(result.shape, vec![100, 100]);
        assert!(result.success);
    }
}
