//! LevelDB statistics.
use crate::binding::leveldb_approximate_sizes;
use crate::database::Database;
use crate::database::property::Property;
use crate::database::slice::Slice;
use std::ffi::NulError;
use std::os::raw::{c_char, c_int};

/// Represents a key range for approximate size calculation.
pub struct KeyRange {
    /// The start key of the range (inclusive).
    pub start: Vec<u8>,
    /// The end key of the range (exclusive).
    pub end: Vec<u8>,
}

impl KeyRange {
    /// Create a new key range from slices.
    pub fn new(start: Slice<'_>, end: Slice<'_>) -> Self {
        KeyRange {
            start: start.as_bytes().to_vec(),
            end: end.as_bytes().to_vec(),
        }
    }

    /// Get the start key as a slice.
    pub fn start(&self) -> Slice<'_> {
        Slice::from(self.start.as_slice())
    }

    /// Get the end key as a slice.
    pub fn end(&self) -> Slice<'_> {
        Slice::from(self.end.as_slice())
    }
}

/// Trait for accessing LevelDB statistics.
pub trait Statistics: Property {
    /// Get the number of files at a specific level.
    fn get_num_files_at_level(&self, level: u32) -> Result<Option<String>, NulError> {
        let property_name = format!(
            "{}{}",
            crate::database::property::property_name::NUM_FILES_AT_LEVEL,
            level
        );
        self.get_property(&property_name)
    }

    /// Get LevelDB statistics.
    fn get_stats(&self) -> Result<Option<String>, NulError> {
        self.get_property(crate::database::property::property_name::STATS)
    }

    /// Get sstables information.
    fn get_property_of_sstables(&self) -> Result<Option<String>, NulError> {
        self.get_property(crate::database::property::property_name::SSTABLES)
    }

    /// Get approximate memory usage.
    fn get_approximate_memory_usage(&self) -> Result<Option<String>, NulError> {
        self.get_property(crate::database::property::property_name::APPROXIMATE_MEMORY_USAGE)
    }

    /// Get approximate file system sizes for key ranges.
    ///
    /// This function returns the approximate number of bytes of file system space used by one or more key ranges.
    /// The sizes returned include data blocks, index blocks, and filter blocks for the specified ranges.
    ///
    /// # Arguments
    /// * `ranges` - A vector of key ranges to calculate sizes for (ownership is transferred)
    ///
    /// # Returns
    /// A vector of approximate sizes in bytes, corresponding to each input range.
    ///
    /// # Example
    /// ```rust,ignore
    /// let ranges = vec![
    ///     KeyRange::new(start_slice, end_slice),
    ///     KeyRange::new(start_slice2, end_slice2),
    /// ];
    /// let sizes = database.get_approximate_sizes(ranges);
    /// ```
    fn get_approximate_sizes(&self, ranges: Vec<KeyRange>) -> Vec<u64>;

    /// Get the approximate file system size for a single key range.
    ///
    /// This is a convenience method that wraps `get_approximate_sizes` for a single range.
    ///
    /// # Arguments
    /// * `start` - The start key of the range (inclusive)
    /// * `end` - The end key of the range (exclusive)
    ///
    /// # Returns
    /// The approximate size in bytes for the specified range.
    fn get_approximate_size_for_range(&self, start: Slice<'_>, end: Slice<'_>) -> u64 {
        let range = KeyRange::new(start, end);
        let sizes = self.get_approximate_sizes(vec![range]);
        assert!(sizes.len() == 1);
        sizes[0]
    }
}

impl Statistics for Database {
    fn get_approximate_sizes(&self, ranges: Vec<KeyRange>) -> Vec<u64> {
        let db_ptr = self.database.ptr;
        let num_ranges = ranges.len();
        if num_ranges == 0 {
            return vec![];
        }

        let mut start_keys = Vec::with_capacity(num_ranges);
        let mut start_key_lens = Vec::with_capacity(num_ranges);
        let mut limit_keys = Vec::with_capacity(num_ranges);
        let mut limit_key_lens = Vec::with_capacity(num_ranges);

        for range in &ranges {
            start_keys.push(range.start.as_ptr());
            start_key_lens.push(range.start.len());

            limit_keys.push(range.end.as_ptr());
            limit_key_lens.push(range.end.len());
        }

        let mut sizes = vec![0u64; num_ranges];
        unsafe {
            leveldb_approximate_sizes(
                db_ptr,
                num_ranges as c_int,
                start_keys.as_ptr() as *const *const c_char,
                start_key_lens.as_ptr(),
                limit_keys.as_ptr() as *const *const c_char,
                limit_key_lens.as_ptr(),
                sizes.as_mut_ptr(),
            );
        }

        sizes
    }
}
