//! Safe Tensor Operations (Candle-free).
//!
//! With the Candle removal, tensor-level safety checks operate on raw data
//! (slices/shapes) rather than `candle_core::Tensor` objects.
//! GPU tensor operations are validated by `cuda_autograd` dimension checks.

#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::panic)]

use tracing::debug;

use super::{MLSafetyConfig, MLSafetyError, SafetyResult};

/// Safe tensor operations with comprehensive validation.
///
/// Operates on raw data (shapes, values) instead of candle Tensors.
#[derive(Debug, Clone)]
pub struct SafeTensorOps {
    config: MLSafetyConfig,
}

impl SafeTensorOps {
    /// Create new safe tensor operations
    pub fn new(config: &MLSafetyConfig) -> Self {
        Self {
            config: config.clone(),
        }
    }

    /// Validate raw data and shape for tensor creation.
    pub fn validate_data(
        &self,
        data: &[f64],
        shape: &[usize],
        operation: &str,
    ) -> SafetyResult<()> {
        let total_elements: usize = shape.iter().product();

        if total_elements == 0 {
            return Err(MLSafetyError::TensorSafety {
                reason: "Cannot create tensor with zero elements".to_owned(),
            });
        }

        if total_elements > self.config.max_tensor_elements {
            return Err(MLSafetyError::TensorSafety {
                reason: format!(
                    "Tensor too large: {} elements > {} limit",
                    total_elements, self.config.max_tensor_elements
                ),
            });
        }

        if data.len() != total_elements {
            return Err(MLSafetyError::TensorSafety {
                reason: format!(
                    "Data length {} doesn't match shape size {}",
                    data.len(),
                    total_elements
                ),
            });
        }

        if self.config.nan_infinity_checks {
            for (i, &value) in data.iter().enumerate() {
                if !value.is_finite() {
                    return Err(MLSafetyError::InvalidFloat {
                        operation: format!("Tensor data at index {}: {} in {}", i, value, operation),
                    });
                }
            }
        }

        debug!("Tensor data validation passed for operation: {}", operation);
        Ok(())
    }

    /// Validate that a shape is reasonable (no zero dims, not too large).
    pub fn validate_shape(
        &self,
        shape: &[usize],
        operation: &str,
    ) -> SafetyResult<()> {
        if shape.is_empty() {
            debug!("Scalar tensor in operation: {}", operation);
            return Ok(());
        }

        for (i, &dim_size) in shape.iter().enumerate() {
            if dim_size == 0 {
                return Err(MLSafetyError::TensorSafety {
                    reason: format!(
                        "Zero-size dimension {} in tensor for operation: {}",
                        i, operation
                    ),
                });
            }
        }

        let total_elements: usize = shape.iter().product();
        if total_elements > self.config.max_tensor_elements {
            return Err(MLSafetyError::TensorSafety {
                reason: format!(
                    "Tensor too large for operation {}: {} elements > {} limit",
                    operation, total_elements, self.config.max_tensor_elements
                ),
            });
        }

        Ok(())
    }

    /// Estimate memory usage for a shape with given element size in bytes.
    pub fn estimate_memory_usage(shape: &[usize], element_bytes: usize) -> usize {
        let elements: usize = shape.iter().product();
        elements * element_bytes
    }
}

#[cfg(test)]
#[allow(clippy::assertions_on_result_states)]
mod tests {
    use super::*;

    fn create_test_ops() -> SafeTensorOps {
        SafeTensorOps::new(&MLSafetyConfig::default())
    }

    #[test]
    fn test_validate_data_valid() {
        let ops = create_test_ops();
        let data = vec![1.0, 2.0, 3.0, 4.0];
        let shape = &[2, 2];
        let result = ops.validate_data(&data, shape, "test");
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_data_nan() {
        let ops = create_test_ops();
        let data = vec![1.0, f64::NAN, 3.0, 4.0];
        let shape = &[2, 2];
        let result = ops.validate_data(&data, shape, "test");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_data_shape_mismatch() {
        let ops = create_test_ops();
        let data = vec![1.0, 2.0, 3.0];
        let shape = &[2, 2];
        let result = ops.validate_data(&data, shape, "test");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_shape_zero_dim() {
        let ops = create_test_ops();
        let result = ops.validate_shape(&[2, 0, 3], "test");
        assert!(result.is_err());
    }
}
