//! Temporary file management with secure permissions

use std::fs::{self, File, OpenOptions};
use std::io;
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use super::error::ProfilingError;

/// Temporary file manager for benchmark operations
pub struct TempFileManager {
    base_dir: PathBuf,
    prefix: String,
    created_files: Vec<PathBuf>,
}

impl TempFileManager {
    /// Create new temporary file manager
    pub fn new(base_dir: &Path, prefix: &str) -> Result<Self, ProfilingError> {
        if !base_dir.exists() {
            return Err(ProfilingError::IoError(format!(
                "Base directory does not exist: {}",
                base_dir.display()
            )));
        }

        Ok(Self {
            base_dir: base_dir.to_path_buf(),
            prefix: prefix.to_string(),
            created_files: Vec::new(),
        })
    }

    /// Create a temporary file with secure permissions (0600)
    pub fn create_file(&mut self, size_bytes: u64) -> Result<PathBuf, ProfilingError> {
        let temp_path = self.generate_path();

        // Create file with secure permissions
        let file = OpenOptions::new()
            .write(true)
            .create_new(true)
            .mode(0o600) // Secure permissions
            .open(&temp_path)
            .map_err(|e| ProfilingError::IoError(format!("Failed to create temp file: {}", e)))?;

        // Set file size if specified
        if size_bytes > 0 {
            file.set_len(size_bytes)
                .map_err(|e| ProfilingError::IoError(format!("Failed to set file size: {}", e)))?;
        }

        // Verify file was created successfully
        if !temp_path.exists() {
            return Err(ProfilingError::IoError(
                "File creation verification failed".to_string(),
            ));
        }

        self.created_files.push(temp_path.clone());
        Ok(temp_path)
    }

    /// Create a temporary file with initial content
    pub fn create_file_with_content<F>(
        &mut self,
        size_bytes: u64,
        fill_fn: F,
    ) -> Result<PathBuf, ProfilingError>
    where
        F: FnOnce(&mut File) -> io::Result<()>,
    {
        let temp_path = self.create_file(size_bytes)?;

        if let Ok(mut file) = File::open(&temp_path) {
            fill_fn(&mut file)?;
        }

        Ok(temp_path)
    }

    /// Get a temporary directory path
    pub fn create_temp_dir(&mut self) -> Result<PathBuf, ProfilingError> {
        let dir_path = self.generate_path();

        fs::create_dir(&dir_path).map_err(|e| {
            ProfilingError::IoError(format!("Failed to create temp directory: {}", e))
        })?;

        self.created_files.push(dir_path.clone());
        Ok(dir_path)
    }

    /// Generate unique temporary file path
    fn generate_path(&self) -> PathBuf {
        let timestamp = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap_or_default()
            .as_nanos();

        // Use thread-safe random number generation
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};
        use std::thread;

        let mut hasher = DefaultHasher::new();
        thread::current().id().hash(&mut hasher);
        timestamp.hash(&mut hasher);

        let random = hasher.finish();
        let filename = format!("{}_{}_{}", self.prefix, timestamp, random);
        self.base_dir.join(filename)
    }

    /// Get the base directory
    pub fn base_dir(&self) -> &Path {
        &self.base_dir
    }

    /// Get list of created files
    pub fn created_files(&self) -> &[PathBuf] {
        &self.created_files
    }

    /// Clean up all created files and directories
    pub fn cleanup(&mut self) -> Result<(), ProfilingError> {
        let mut errors = Vec::new();

        for path in &self.created_files {
            if let Err(e) = self.remove_path(path) {
                errors.push(format!("Failed to remove {}: {}", path.display(), e));
            }
        }

        self.created_files.clear();

        if !errors.is_empty() {
            return Err(ProfilingError::IoError(errors.join("; ")));
        }

        Ok(())
    }
}

impl TempFileManager {
    /// Remove a path (file or directory)
    fn remove_path(&self, path: &Path) -> io::Result<()> {
        if path.is_dir() {
            fs::remove_dir_all(path)
        } else {
            fs::remove_file(path)
        }
    }
}

impl Drop for TempFileManager {
    fn drop(&mut self) {
        let _ = self.cleanup();
    }
}

/// Create a temporary file with a specific size
pub fn create_temp_file(
    base_dir: &Path,
    prefix: &str,
    size_bytes: u64,
) -> Result<(PathBuf, TempFileManager), ProfilingError> {
    let mut manager = TempFileManager::new(base_dir, prefix)?;
    let path = manager.create_file(size_bytes)?;
    Ok((path, manager))
}

/// Check available disk space
pub fn check_disk_space(path: &Path, required_bytes: u64) -> Result<u64, ProfilingError> {
    let metadata = fs::metadata(path)
        .map_err(|e| ProfilingError::IoError(format!("Failed to get path metadata: {}", e)))?;

    let statvfs = if metadata.is_dir() {
        nix::sys::statvfs::statvfs(path)
    } else if let Some(parent) = path.parent() {
        nix::sys::statvfs::statvfs(parent)
    } else {
        return Err(ProfilingError::IoError(
            "Cannot determine disk space for path".to_string(),
        ));
    };

    match statvfs {
        Ok(stat) => {
            let available: u64 = stat.blocks_available() as u64 * stat.fragment_size() as u64;
            if available < required_bytes {
                return Err(ProfilingError::IoError(format!(
                    "Insufficient disk space: {} bytes available, {} bytes required",
                    available, required_bytes
                )));
            }
            Ok(available)
        }
        Err(e) => Err(ProfilingError::IoError(format!(
            "Failed to check disk space: {}",
            e
        ))),
    }
}
