//! Device detection and capability management for GPU backends
//!
//! This module provides enhanced device detection and capability querying
//! for different GPU backends, replacing the placeholder implementations
//! with more accurate hardware detection.

use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};

use crate::error::{NdimageError, NdimageResult};

/// Device capability information
#[derive(Debug, Clone)]
pub struct DeviceCapability {
    /// Device name
    pub name: String,
    /// Total memory in bytes
    pub total_memory: usize,
    /// Available memory in bytes
    pub available_memory: usize,
    /// Compute capability (for CUDA)
    pub compute_capability: Option<(u32, u32)>,
    /// Maximum threads per block
    pub max_threads_per_block: Option<usize>,
    /// Maximum block dimensions
    pub max_block_dims: Option<[usize; 3]>,
    /// Maximum grid dimensions
    pub max_grid_dims: Option<[usize; 3]>,
    /// Shared memory per block in bytes
    pub shared_memory_per_block: Option<usize>,
    /// Number of multiprocessors
    pub multiprocessor_count: Option<usize>,
    /// Clock rate in kHz
    pub clock_rate: Option<usize>,
    /// Memory bandwidth in GB/s
    pub memory_bandwidth: Option<f64>,
}

impl Default for DeviceCapability {
    fn default() -> Self {
        Self {
            name: "Unknown Device".to_string(),
            total_memory: 0,
            available_memory: 0,
            compute_capability: None,
            max_threads_per_block: None,
            max_block_dims: None,
            max_grid_dims: None,
            shared_memory_per_block: None,
            multiprocessor_count: None,
            clock_rate: None,
            memory_bandwidth: None,
        }
    }
}

/// Overall system capabilities summary
#[derive(Debug, Clone)]
pub struct SystemCapabilities {
    pub cuda_available: bool,
    pub opencl_available: bool,
    pub metal_available: bool,
    pub gpu_available: bool,
    pub gpu_memory_mb: usize,
    pub compute_units: u32,
}

/// Device detection and management
pub struct DeviceManager {
    #[cfg(feature = "cuda")]
    cuda_devices: Vec<DeviceCapability>,
    #[cfg(feature = "opencl")]
    opencl_devices: Vec<DeviceCapability>,
    #[cfg(all(target_os = "macos", feature = "metal"))]
    metal_devices: Vec<DeviceCapability>,
}

impl DeviceManager {
    /// Create a new device manager and detect all available devices
    pub fn new() -> NdimageResult<Self> {
        let mut manager = Self {
            #[cfg(feature = "cuda")]
            cuda_devices: Vec::new(),
            #[cfg(feature = "opencl")]
            opencl_devices: Vec::new(),
            #[cfg(all(target_os = "macos", feature = "metal"))]
            metal_devices: Vec::new(),
        };

        // Detect devices for each backend
        #[cfg(feature = "cuda")]
        {
            manager.cuda_devices = detect_cuda_devices()?;
        }

        #[cfg(feature = "opencl")]
        {
            manager.opencl_devices = detect_opencl_devices()?;
        }

        #[cfg(all(target_os = "macos", feature = "metal"))]
        {
            manager.metal_devices = detect_metal_devices()?;
        }

        Ok(manager)
    }

    /// Get the best available device for a given workload size
    pub fn get_best_device(&self, requiredmemory: usize) -> Option<(super::Backend, usize)> {
        let mut best_device = None;
        let mut best_score = 0.0;

        #[cfg(feature = "cuda")]
        {
            for (idx, device) in self.cuda_devices.iter().enumerate() {
                if device.available_memory >= requiredmemory {
                    let score = self.calculate_device_score(device);
                    if score > best_score {
                        best_score = score;
                        best_device = Some((super::Backend::Cuda, idx));
                    }
                }
            }
        }

        #[cfg(feature = "opencl")]
        {
            for (idx, device) in self.opencl_devices.iter().enumerate() {
                if device.available_memory >= requiredmemory {
                    let score = self.calculate_device_score(device) * 0.9; // Slight preference for CUDA
                    if score > best_score {
                        best_score = score;
                        best_device = Some((super::Backend::OpenCL, idx));
                    }
                }
            }
        }

        #[cfg(all(target_os = "macos", feature = "metal"))]
        {
            for (idx, device) in self.metal_devices.iter().enumerate() {
                if device.available_memory >= requiredmemory {
                    let score = self.calculate_device_score(device) * 0.8; // Lower preference for Metal
                    if score > best_score {
                        best_score = score;
                        best_device = Some((super::Backend::Metal, idx));
                    }
                }
            }
        }

        best_device
    }

    /// Calculate a performance score for a device
    fn calculate_device_score(&self, device: &DeviceCapability) -> f64 {
        let mut score = 0.0;

        // Memory contribution (GB)
        score += (device.total_memory as f64) / (1024.0 * 1024.0 * 1024.0) * 10.0;

        // Multiprocessor count contribution
        if let Some(mp_count) = device.multiprocessor_count {
            score += (mp_count as f64) * 5.0;
        }

        // Clock rate contribution (GHz)
        if let Some(clock) = device.clock_rate {
            score += (clock as f64) / 1_000_000.0 * 3.0;
        }

        // Memory bandwidth contribution
        if let Some(bandwidth) = device.memory_bandwidth {
            score += bandwidth * 0.1;
        }

        score
    }

    /// Get device capabilities by backend and index
    pub fn get_device_info(
        &self,
        backend: super::Backend,
        device_id: usize,
    ) -> Option<&DeviceCapability> {
        match backend {
            #[cfg(feature = "cuda")]
            super::Backend::Cuda => self.cuda_devices.get(device_id),
            #[cfg(feature = "opencl")]
            super::Backend::OpenCL => self.opencl_devices.get(device_id),
            #[cfg(all(target_os = "macos", feature = "metal"))]
            super::Backend::Metal => self.metal_devices.get(device_id),
            _ => None,
        }
    }

    /// Check if a specific backend is available
    pub fn is_backend_available(&self, backend: super::Backend) -> bool {
        match backend {
            #[cfg(feature = "cuda")]
            super::Backend::Cuda => !self.cuda_devices.is_empty(),
            #[cfg(feature = "opencl")]
            super::Backend::OpenCL => !self.opencl_devices.is_empty(),
            #[cfg(all(target_os = "macos", feature = "metal"))]
            super::Backend::Metal => !self.metal_devices.is_empty(),
            super::Backend::Cpu => true,
            super::Backend::Auto => {
                #[cfg(feature = "cuda")]
                if !self.cuda_devices.is_empty() {
                    return true;
                }
                #[cfg(feature = "opencl")]
                if !self.opencl_devices.is_empty() {
                    return true;
                }
                #[cfg(all(target_os = "macos", feature = "metal"))]
                if !self.metal_devices.is_empty() {
                    return true;
                }
                true // CPU always available
            }
        }
    }

    /// Get the number of devices for a specific backend
    pub fn device_count(&self, backend: super::Backend) -> usize {
        match backend {
            #[cfg(feature = "cuda")]
            super::Backend::Cuda => self.cuda_devices.len(),
            #[cfg(feature = "opencl")]
            super::Backend::OpenCL => self.opencl_devices.len(),
            #[cfg(all(target_os = "macos", feature = "metal"))]
            super::Backend::Metal => self.metal_devices.len(),
            super::Backend::Cpu => 1,
            super::Backend::Auto => {
                let mut total = 1; // CPU
                #[cfg(feature = "cuda")]
                {
                    total += self.cuda_devices.len();
                }
                #[cfg(feature = "opencl")]
                {
                    total += self.opencl_devices.len();
                }
                #[cfg(all(target_os = "macos", feature = "metal"))]
                {
                    total += self.metal_devices.len();
                }
                total
            }
        }
    }

    /// Get overall system capabilities
    pub fn get_capabilities(&self) -> SystemCapabilities {
        let cuda_available = {
            #[cfg(feature = "cuda")]
            {
                !self.cuda_devices.is_empty()
            }
            #[cfg(not(feature = "cuda"))]
            {
                false
            }
        };

        let opencl_available = {
            #[cfg(feature = "opencl")]
            {
                !self.opencl_devices.is_empty()
            }
            #[cfg(not(feature = "opencl"))]
            {
                false
            }
        };

        let metal_available = {
            #[cfg(all(target_os = "macos", feature = "metal"))]
            {
                !self.metal_devices.is_empty()
            }
            #[cfg(not(all(target_os = "macos", feature = "metal")))]
            {
                false
            }
        };

        let gpu_available = cuda_available || opencl_available || metal_available;

        // Find the best GPU device for memory and compute unit estimates
        let mut total_memory_mb = 0;
        let mut max_compute_units = 0;

        #[cfg(feature = "cuda")]
        {
            for device in &self.cuda_devices {
                total_memory_mb = total_memory_mb.max(device.total_memory / (1024 * 1024));
                if let Some(mp_count) = device.multiprocessor_count {
                    max_compute_units = max_compute_units.max(mp_count as u32);
                }
            }
        }

        #[cfg(feature = "opencl")]
        {
            for device in &self.opencl_devices {
                total_memory_mb = total_memory_mb.max(device.total_memory / (1024 * 1024));
                if let Some(mp_count) = device.multiprocessor_count {
                    max_compute_units = max_compute_units.max(mp_count as u32);
                }
            }
        }

        #[cfg(all(target_os = "macos", feature = "metal"))]
        {
            for device in &self.metal_devices {
                total_memory_mb = total_memory_mb.max(device.total_memory / (1024 * 1024));
                if let Some(mp_count) = device.multiprocessor_count {
                    max_compute_units = max_compute_units.max(mp_count as u32);
                }
            }
        }

        SystemCapabilities {
            cuda_available,
            opencl_available,
            metal_available,
            gpu_available,
            gpu_memory_mb: total_memory_mb,
            compute_units: max_compute_units,
        }
    }
}

// Global device manager instance
static DEVICE_MANAGER: OnceLock<Arc<Mutex<DeviceManager>>> = OnceLock::new();

/// Get the global device manager instance
#[allow(dead_code)]
pub fn get_device_manager() -> NdimageResult<Arc<Mutex<DeviceManager>>> {
    let result = DEVICE_MANAGER.get_or_init(|| {
        match DeviceManager::new() {
            Ok(manager) => Arc::new(Mutex::new(manager)),
            Err(_) => {
                // Fallback to empty manager on error
                Arc::new(Mutex::new(DeviceManager {
                    #[cfg(feature = "cuda")]
                    cuda_devices: Vec::new(),
                    #[cfg(feature = "opencl")]
                    opencl_devices: Vec::new(),
                    #[cfg(all(target_os = "macos", feature = "metal"))]
                    metal_devices: Vec::new(),
                }))
            }
        }
    });
    Ok(result.clone())
}

/// Detect CUDA devices
#[cfg(feature = "cuda")]
#[allow(dead_code)]
fn detect_cuda_devices() -> NdimageResult<Vec<DeviceCapability>> {
    // For a production implementation, this would use proper CUDA bindings
    // like cudarc, candle-core, or similar. This is a simplified fallback
    // that provides basic detection without actual CUDA calls.

    // Check if CUDA library is available by looking for common paths
    let cuda_available = std::path::Path::new("/usr/local/cuda/lib64/libcudart.so").exists()
        || std::path::Path::new("/usr/lib/x86_64-linux-gnu/libcudart.so").exists()
        || std::env::var("CUDA_PATH").is_ok();

    if !cuda_available {
        return Ok(Vec::new());
    }

    // Enumerate real CUDA hardware by querying the NVIDIA driver via
    // `nvidia-smi`. Architecture-dependent figures that the tool does not
    // report (compute capability, SM count, clock, bandwidth) are estimated
    // from the reported device name. If no device can be enumerated this
    // returns an empty list (honest "no GPU detected") rather than
    // fabricating a device that may not exist.
    let mut devices = Vec::new();

    // Check for NVIDIA GPUs via nvidia-ml-py or similar approaches
    if let Ok(output) = std::process::Command::new("nvidia-smi")
        .arg("--query-gpu=name,memory.total,memory.free")
        .arg("--format=csv,noheader,nounits")
        .output()
    {
        if output.status.success() {
            let output_str = String::from_utf8_lossy(&output.stdout);
            for (i, line) in output_str.lines().enumerate() {
                let parts: Vec<&str> = line.split(',').map(|s| s.trim()).collect();
                if parts.len() >= 3 {
                    let name = parts[0].to_string();
                    let total_memory = parts[1].parse::<usize>().unwrap_or(0) * 1024 * 1024; // Convert MB to bytes
                    let available_memory = parts[2].parse::<usize>().unwrap_or(0) * 1024 * 1024; // Convert MB to bytes

                    // Estimate other capabilities based on common GPU architectures
                    let (compute_capability, multiprocessor_count, clock_rate) =
                        estimate_gpu_capabilities(&name);

                    let memory_bandwidth = estimate_memory_bandwidth(&name);

                    let capability = DeviceCapability {
                        name: format!("{} (CUDA Device {})", name, i),
                        total_memory,
                        available_memory,
                        compute_capability,
                        max_threads_per_block: Some(1024),
                        max_block_dims: Some([1024, 1024, 64]),
                        max_grid_dims: Some([65535, 65535, 65535]),
                        shared_memory_per_block: Some(49152), // 48KB typical
                        multiprocessor_count,
                        clock_rate,
                        memory_bandwidth,
                    };

                    devices.push(capability);
                }
            }
        }
    }

    // If `nvidia-smi` produced no usable output we deliberately do NOT
    // synthesise a placeholder device: returning an empty list reports the
    // honest "no enumerable CUDA device" state instead of advertising
    // hardware (and capacities) that may not actually be present.
    Ok(devices)
}

#[cfg(feature = "cuda")]
#[allow(dead_code)]
fn estimate_gpu_capabilities(name: &str) -> (Option<(u32, u32)>, Option<usize>, Option<usize>) {
    let name_lower = name.to_lowercase();

    // Common GPU architectures and their capabilities
    if name_lower.contains("rtx 40") || name_lower.contains("ada lovelace") {
        // RTX 4000 series (Ada Lovelace)
        (Some((8, 9)), Some(128), Some(2_500_000))
    } else if name_lower.contains("rtx 30") || name_lower.contains("ampere") {
        // RTX 3000 series (Ampere)
        (Some((8, 6)), Some(104), Some(1_700_000))
    } else if name_lower.contains("rtx 20") || name_lower.contains("turing") {
        // RTX 2000 series (Turing)
        (Some((7, 5)), Some(72), Some(1_500_000))
    } else if name_lower.contains("gtx 16") || name_lower.contains("gtx 10") {
        // GTX 1000/1600 series (Pascal/Turing)
        (Some((6, 1)), Some(20), Some(1_400_000))
    } else if name_lower.contains("tesla") || name_lower.contains("quadro") {
        // Professional cards
        (Some((7, 0)), Some(80), Some(1_300_000))
    } else {
        // Default/unknown
        (Some((6, 0)), Some(32), Some(1_000_000))
    }
}

#[cfg(feature = "cuda")]
#[allow(dead_code)]
fn estimate_memory_bandwidth(name: &str) -> Option<f64> {
    let name_lower = name.to_lowercase();

    if name_lower.contains("rtx 4090") {
        Some(1008.0)
    } else if name_lower.contains("rtx 4080") {
        Some(717.0)
    } else if name_lower.contains("rtx 3090") {
        Some(936.0)
    } else if name_lower.contains("rtx 3080") {
        Some(760.0)
    } else if name_lower.contains("rtx 3070") {
        Some(448.0)
    } else if name_lower.contains("rtx 2080") {
        Some(448.0)
    } else if name_lower.contains("tesla v100") {
        Some(900.0)
    } else if name_lower.contains("tesla a100") {
        Some(1555.0)
    } else {
        Some(320.0) // Conservative default
    }
}

/// Detect OpenCL devices
#[cfg(feature = "opencl")]
#[allow(dead_code)]
fn detect_opencl_devices() -> NdimageResult<Vec<DeviceCapability>> {
    // For a production implementation, this would use proper OpenCL bindings
    // like opencl3, ocl, or similar. This is a simplified fallback.

    // Check if OpenCL library is available
    let opencl_available = std::path::Path::new("/usr/lib/x86_64-linux-gnu/libOpenCL.so.1")
        .exists()
        || std::path::Path::new("/usr/local/lib/libOpenCL.so").exists()
        || std::env::var("OPENCL_ROOT").is_ok();

    if !opencl_available {
        return Ok(Vec::new());
    }

    let mut devices = Vec::new();

    // Try to use clinfo command if available for basic device detection
    if let Ok(output) = std::process::Command::new("clinfo").arg("--list").output() {
        if output.status.success() {
            let output_str = String::from_utf8_lossy(&output.stdout);
            for (i, line) in output_str.lines().enumerate() {
                if line.contains("Device") && !line.contains("Platform") {
                    let device_name = line
                        .split("Device")
                        .nth(1)
                        .unwrap_or("Unknown OpenCL Device")
                        .trim()
                        .to_string();

                    // Estimate capabilities based on device name
                    let (memory_size, compute_units, clock_freq) =
                        estimate_opencl_capabilities(&device_name);

                    let capability = DeviceCapability {
                        name: format!("{} (OpenCL Device {})", device_name, i),
                        total_memory: memory_size,
                        available_memory: (memory_size as f64 * 0.8) as usize,
                        compute_capability: None, // OpenCL doesn't have compute capability
                        max_threads_per_block: Some(1024),
                        max_block_dims: Some([1024, 1024, 1024]),
                        max_grid_dims: None, // Not directly applicable to OpenCL
                        shared_memory_per_block: Some(32768), // 32KB typical
                        multiprocessor_count: Some(compute_units),
                        clock_rate: Some(clock_freq),
                        memory_bandwidth: estimate_opencl_bandwidth(&device_name),
                    };

                    devices.push(capability);
                }
            }
        }
    }

    // If no devices found via clinfo, provide common fallback devices
    if devices.is_empty() {
        // Check for Intel integrated graphics
        if std::path::Path::new("/sys/class/drm/card0").exists() {
            devices.push(DeviceCapability {
                name: "Intel Integrated Graphics (OpenCL)".to_string(),
                total_memory: 2_147_483_648,     // 2GB shared memory
                available_memory: 1_717_986_918, // 80% available
                compute_capability: None,
                max_threads_per_block: Some(512),
                max_block_dims: Some([512, 512, 512]),
                max_grid_dims: None,
                shared_memory_per_block: Some(32768),
                multiprocessor_count: Some(24),
                clock_rate: Some(1_000_000),  // 1GHz
                memory_bandwidth: Some(25.6), // GB/s
            });
        }

        // Check for AMD GPU
        if std::env::var("HSA_ENABLE_SDMA").is_ok() || std::path::Path::new("/opt/rocm").exists() {
            devices.push(DeviceCapability {
                name: "AMD Discrete Graphics (OpenCL)".to_string(),
                total_memory: 8_589_934_592,     // 8GB
                available_memory: 6_871_947_674, // 80% available
                compute_capability: None,
                max_threads_per_block: Some(1024),
                max_block_dims: Some([1024, 1024, 1024]),
                max_grid_dims: None,
                shared_memory_per_block: Some(65536), // 64KB
                multiprocessor_count: Some(64),
                clock_rate: Some(1_500_000),   // 1.5GHz
                memory_bandwidth: Some(448.0), // GB/s
            });
        }
    }

    Ok(devices)
}

#[cfg(feature = "opencl")]
#[allow(dead_code)]
fn estimate_opencl_capabilities(name: &str) -> (usize, usize, usize) {
    let name_lower = name.to_lowercase();

    if name_lower.contains("intel") {
        // Intel integrated graphics
        if name_lower.contains("iris") || name_lower.contains("xe") {
            (4_294_967_296, 96, 1_300_000) // 4GB, 96 EUs, 1.3GHz
        } else {
            (2_147_483_648, 24, 1_000_000) // 2GB, 24 EUs, 1GHz
        }
    } else if name_lower.contains("amd") || name_lower.contains("radeon") {
        // AMD discrete graphics
        if name_lower.contains("rx 7") || name_lower.contains("rx 6") {
            (16_106_127_360, 80, 2_000_000) // 15GB, 80 CUs, 2GHz
        } else if name_lower.contains("rx 5") {
            (8_589_934_592, 64, 1_800_000) // 8GB, 64 CUs, 1.8GHz
        } else {
            (4_294_967_296, 36, 1_500_000) // 4GB, 36 CUs, 1.5GHz
        }
    } else if name_lower.contains("nvidia")
        || name_lower.contains("geforce")
        || name_lower.contains("quadro")
    {
        // NVIDIA cards via OpenCL
        if name_lower.contains("rtx") {
            (12_884_901_888, 84, 1_700_000) // 12GB, 84 SMs, 1.7GHz
        } else {
            (8_589_934_592, 56, 1_500_000) // 8GB, 56 SMs, 1.5GHz
        }
    } else {
        // Generic/unknown device
        (2_147_483_648, 16, 1_000_000) // 2GB, 16 units, 1GHz
    }
}

#[cfg(feature = "opencl")]
#[allow(dead_code)]
fn estimate_opencl_bandwidth(name: &str) -> Option<f64> {
    let name_lower = name.to_lowercase();

    if name_lower.contains("intel iris") || name_lower.contains("intel xe") {
        Some(68.0) // GB/s for modern Intel integrated
    } else if name_lower.contains("intel") {
        Some(25.6) // GB/s for basic Intel integrated
    } else if name_lower.contains("rx 7") {
        Some(960.0) // GB/s for RX 7000 series
    } else if name_lower.contains("rx 6") {
        Some(512.0) // GB/s for RX 6000 series
    } else if name_lower.contains("rx 5") {
        Some(448.0) // GB/s for RX 5000 series
    } else if name_lower.contains("nvidia") {
        Some(760.0) // GB/s for modern NVIDIA
    } else {
        Some(100.0) // Conservative default
    }
}

/// Detect Metal devices (macOS only)
#[cfg(all(target_os = "macos", feature = "metal"))]
#[allow(dead_code)]
fn detect_metal_devices() -> NdimageResult<Vec<DeviceCapability>> {
    use std::ffi::{c_char, c_int, c_uint, c_ulong, c_void, CStr};
    use std::ptr;

    // Metal FFI bindings - these would normally be from a proper Metal crate
    // For simplicity, we'll provide a basic implementation that can detect
    // common Metal GPU configurations on macOS

    let mut devices = Vec::new();

    // On macOS, we can try to detect common GPU configurations
    // This is a simplified implementation - a full Metal implementation
    // would use proper Metal framework bindings

    // Try to detect integrated Intel/AMD GPUs
    if let Ok(gpu_info) = detect_macos_integrated_gpu() {
        devices.push(gpu_info);
    }

    // Try to detect discrete AMD/NVIDIA GPUs
    if let Ok(discrete_gpus) = detect_macos_discrete_gpus() {
        devices.extend(discrete_gpus);
    }

    Ok(devices)
}

#[cfg(all(target_os = "macos", feature = "metal"))]
#[allow(dead_code)]
fn detect_macos_integrated_gpu() -> NdimageResult<DeviceCapability> {
    use std::process::Command;

    // Use system_profiler to get GPU information
    let output = Command::new("system_profiler")
        .arg("SPDisplaysDataType")
        .arg("-xml")
        .output()
        .map_err(|e| {
            NdimageError::ComputationError(format!("Failed to run systemprofiler: {}", e))
        })?;

    if !output.status.success() {
        return Err(NdimageError::ComputationError(
            "system_profiler failed".into(),
        ));
    }

    let output_str = String::from_utf8_lossy(&output.stdout);

    // Parse for integrated GPU info (simplified parsing)
    let mut capability = DeviceCapability::default();

    if output_str.contains("Intel") {
        capability.name = "Intel Integrated Graphics (Metal)".to_string();
        capability.total_memory = 1_073_741_824; // 1GB shared memory estimate
        capability.available_memory = 805_306_368; // 75% available
        capability.multiprocessor_count = Some(16); // Estimate for Intel integrated
        capability.clock_rate = Some(1_000_000); // 1GHz estimate
        capability.max_threads_per_block = Some(1024);
        capability.max_block_dims = Some([1024, 1024, 64]);
        capability.shared_memory_per_block = Some(32768); // 32KB estimate
    } else if output_str.contains("AMD") {
        capability.name = "AMD Integrated Graphics (Metal)".to_string();
        capability.total_memory = 2_147_483_648; // 2GB estimate
        capability.available_memory = 1_610_612_736; // 75% available
        capability.multiprocessor_count = Some(32); // Estimate for AMD integrated
        capability.clock_rate = Some(1200_000); // 1.2GHz estimate
        capability.max_threads_per_block = Some(1024);
        capability.max_block_dims = Some([1024, 1024, 64]);
        capability.shared_memory_per_block = Some(65536); // 64KB estimate
    } else {
        capability.name = "Unknown Integrated Graphics (Metal)".to_string();
        capability.total_memory = 1_073_741_824; // 1GB fallback
        capability.available_memory = 805_306_368; // 75% available
        capability.multiprocessor_count = Some(8);
        capability.clock_rate = Some(800_000); // 800MHz fallback
        capability.max_threads_per_block = Some(512);
        capability.max_block_dims = Some([512, 512, 64]);
        capability.shared_memory_per_block = Some(16384); // 16KB fallback
    }

    Ok(capability)
}

#[cfg(all(target_os = "macos", feature = "metal"))]
#[allow(dead_code)]
fn detect_macos_discrete_gpus() -> NdimageResult<Vec<DeviceCapability>> {
    use std::process::Command;

    let mut devices = Vec::new();

    // Use system_profiler to get discrete GPU information
    let output = Command::new("system_profiler")
        .arg("SPDisplaysDataType")
        .arg("-xml")
        .output()
        .map_err(|e| {
            NdimageError::ComputationError(format!("Failed to run systemprofiler: {}", e))
        })?;

    if !output.status.success() {
        return Ok(devices);
    }

    let output_str = String::from_utf8_lossy(&output.stdout);

    // Look for discrete GPUs
    if output_str.contains("Radeon") || output_str.contains("RX ") {
        let mut capability = DeviceCapability::default();

        if output_str.contains("RX 6800") || output_str.contains("RX 6900") {
            capability.name = "AMD Radeon RX 6000 Series (Metal)".to_string();
            capability.total_memory = 17_179_869_184; // 16GB estimate
            capability.available_memory = 15_032_385_536; // 87% available
            capability.multiprocessor_count = Some(80);
            capability.clock_rate = Some(2300_000); // 2.3GHz estimate
        } else if output_str.contains("RX 5") {
            capability.name = "AMD Radeon RX 5000 Series (Metal)".to_string();
            capability.total_memory = 8_589_934_592; // 8GB estimate
            capability.available_memory = 7_516_192_768; // 87% available
            capability.multiprocessor_count = Some(64);
            capability.clock_rate = Some(1900_000); // 1.9GHz estimate
        } else {
            capability.name = "AMD Discrete Graphics (Metal)".to_string();
            capability.total_memory = 4_294_967_296; // 4GB fallback
            capability.available_memory = 3_758_096_384; // 87% available
            capability.multiprocessor_count = Some(32);
            capability.clock_rate = Some(1_500_000); // 1.5GHz fallback
        }

        capability.max_threads_per_block = Some(1024);
        capability.max_block_dims = Some([1024, 1024, 1024]);
        capability.shared_memory_per_block = Some(65536); // 64KB

        devices.push(capability);
    }

    // Check for Apple Silicon GPUs
    if output_str.contains("Apple M") {
        let mut capability = DeviceCapability::default();

        if output_str.contains("M1 Advanced") {
            capability.name = "Apple M1 Advanced GPU (Metal)".to_string();
            capability.total_memory = 137_438_953_472; // 128GB unified memory
            capability.available_memory = 120_259_084_288; // 87% available
            capability.multiprocessor_count = Some(64); // 64-core GPU
            capability.clock_rate = Some(1300_000); // 1.3GHz estimate
        } else if output_str.contains("M1 Max") {
            capability.name = "Apple M1 Max GPU (Metal)".to_string();
            capability.total_memory = 68_719_476_736; // 64GB unified memory
            capability.available_memory = 60_129_542_144; // 87% available
            capability.multiprocessor_count = Some(32); // 32-core GPU
            capability.clock_rate = Some(1300_000); // 1.3GHz estimate
        } else if output_str.contains("M1 Pro") {
            capability.name = "Apple M1 Pro GPU (Metal)".to_string();
            capability.total_memory = 34_359_738_368; // 32GB unified memory
            capability.available_memory = 30_064_771_072; // 87% available
            capability.multiprocessor_count = Some(16); // 16-core GPU
            capability.clock_rate = Some(1300_000); // 1.3GHz estimate
        } else if output_str.contains("M1") {
            capability.name = "Apple M1 GPU (Metal)".to_string();
            capability.total_memory = 17_179_869_184; // 16GB unified memory
            capability.available_memory = 15_032_385_536; // 87% available
            capability.multiprocessor_count = Some(8); // 8-core GPU
            capability.clock_rate = Some(1300_000); // 1.3GHz estimate
        } else if output_str.contains("M2") {
            capability.name = "Apple M2 GPU (Metal)".to_string();
            capability.total_memory = 25_769_803_776; // 24GB unified memory estimate
            capability.available_memory = 22_548_578_304; // 87% available
            capability.multiprocessor_count = Some(10); // 10-core GPU estimate
            capability.clock_rate = Some(1400_000); // 1.4GHz estimate
        } else {
            capability.name = "Apple Silicon GPU (Metal)".to_string();
            capability.total_memory = 8_589_934_592; // 8GB fallback
            capability.available_memory = 7_516_192_768; // 87% available
            capability.multiprocessor_count = Some(8);
            capability.clock_rate = Some(1200_000); // 1.2GHz fallback
        }

        capability.max_threads_per_block = Some(1024);
        capability.max_block_dims = Some([1024, 1024, 1024]);
        capability.shared_memory_per_block = Some(32768); // 32KB threadgroup memory

        devices.push(capability);
    }

    Ok(devices)
}

/// Memory management utilities
pub struct MemoryManager {
    /// Memory usage tracking per device
    memory_usage: HashMap<(super::Backend, usize), usize>,
    /// Memory limits per device
    memory_limits: HashMap<(super::Backend, usize), usize>,
}

impl MemoryManager {
    pub fn new() -> Self {
        Self {
            memory_usage: HashMap::new(),
            memory_limits: HashMap::new(),
        }
    }

    /// Check if allocation is possible
    pub fn can_allocate(&self, backend: super::Backend, deviceid: usize, size: usize) -> bool {
        let key = (backend, deviceid);
        let current_usage = self.memory_usage.get(&key).unwrap_or(&0);
        let limit = self.memory_limits.get(&key).unwrap_or(&usize::MAX);

        current_usage + size <= *limit
    }

    /// Track memory allocation
    pub fn allocate(
        &mut self,
        backend: super::Backend,
        device_id: usize,
        size: usize,
    ) -> NdimageResult<()> {
        let key = (backend, device_id);

        if !self.can_allocate(backend, device_id, size) {
            return Err(NdimageError::ComputationError(
                "Insufficient GPU memory for allocation".into(),
            ));
        }

        *self.memory_usage.entry(key).or_insert(0) += size;
        Ok(())
    }

    /// Track memory deallocation
    pub fn deallocate(&mut self, backend: super::Backend, deviceid: usize, size: usize) {
        let key = (backend, deviceid);

        if let Some(usage) = self.memory_usage.get_mut(&key) {
            *usage = usage.saturating_sub(size);
        }
    }

    /// Set memory limit for a device
    pub fn set_memory_limit(&mut self, backend: super::Backend, deviceid: usize, limit: usize) {
        self.memory_limits.insert((backend, deviceid), limit);
    }

    /// Get current memory usage
    pub fn get_memory_usage(&self, backend: super::Backend, deviceid: usize) -> usize {
        let key = (backend, deviceid);
        *self.memory_usage.get(&key).unwrap_or(&0)
    }
}

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

    #[test]
    fn test_device_capability_default() {
        let cap = DeviceCapability::default();
        assert_eq!(cap.name, "Unknown Device");
        assert_eq!(cap.total_memory, 0);
    }

    #[test]
    fn test_memory_manager() {
        let mut manager = MemoryManager::new();

        // Test allocation tracking
        manager
            .allocate(super::super::Backend::Cpu, 0, 1000)
            .expect("Operation failed");
        assert_eq!(
            manager.get_memory_usage(super::super::Backend::Cpu, 0),
            1000
        );

        // Test deallocation
        manager.deallocate(super::super::Backend::Cpu, 0, 500);
        assert_eq!(manager.get_memory_usage(super::super::Backend::Cpu, 0), 500);

        // Test memory limits
        manager.set_memory_limit(super::super::Backend::Cpu, 0, 2000);
        assert!(manager.can_allocate(super::super::Backend::Cpu, 0, 1000));
        assert!(!manager.can_allocate(super::super::Backend::Cpu, 0, 2000));
    }
}
