use std::fmt::Debug;

use crate::GCAbiConversion;
use crate::GCDatapointID;
use crate::GCDatapointValueQuality;
use gc_abi_sys::sGCDatapointValue__bindgen_ty_1 as BindagenValue;
use crate::error::GCPluginABIError;
use serde::Deserializer;
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
pub type GCTimestamp = u64;

/// Possible value types for a datapoint
#[derive(PartialEq, Debug, Deserialize, Serialize)]
#[serde(tag = "type", content = "data")]
pub enum GCDatapointValueType {
    Bool(bool),
    DoublePoint(u8),
    Int64(i64),
    Int32(i32),
    UInt32(u32),
    UInt64(u64),
    Double(f64),
    Float(f32),
    Bytes(Vec<u8>),
    TimestampNs(GCTimestamp),
}
/// Represents a datapoint value that can be published to the core
///
/// TODO: Investigate adding generic constructor and getter methods
#[repr(transparent)]
#[derive(Clone)]
pub struct GCDatapointValue(gc_abi_sys::sGCDatapointValue);
impl GCDatapointValue {
    /// Creates a new datapoint value with a boolean value
    pub fn new_boolean(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: bool, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_BOOLEAN,
            timestamp: timestamp_ns,
            value: BindagenValue { b: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a timestamp as value
    pub fn new_timestamp_ns(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: GCTimestamp, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_TIMESTAMPNS,
            timestamp: timestamp_ns,
            value: BindagenValue { timestampNs: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a double point value
    pub fn new_u64(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: u64, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT64,
            timestamp: timestamp_ns,
            value: BindagenValue { u64_: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a 32 bit unsigned integer value
    pub fn new_u32(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: u32, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT32,
            timestamp: timestamp_ns,
            value: BindagenValue { u32_: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a 64 bit signed integer value
    pub fn new_i64(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: i64, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT64,
            timestamp: timestamp_ns,
            value: BindagenValue { i64_: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a 32 bit signed integer value
    pub fn new_i32(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: i32, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32,
            timestamp: timestamp_ns,
            value: BindagenValue { i32_: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a 64 bit floating point value
    pub fn new_f64(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: f64, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT64,
            timestamp: timestamp_ns,
            value: BindagenValue { f64_: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a 32 bit floating point value
    pub fn new_f32(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: f32, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT32,
            timestamp: timestamp_ns,
            value: BindagenValue { f32_: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a double point value
    pub fn new_double_point(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: u8, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_DOUBLEPOINT,
            timestamp: timestamp_ns,
            value: BindagenValue { dp: value },
            quality: quality.into_c(),
        })
    }
    /// Creates a new datapoint value with a boolean value
    pub fn new_byte(datapoint_id: GCDatapointID, timestamp_ns: GCTimestamp, value: bool, quality: GCDatapointValueQuality) -> Self {
        GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id,
            type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_BOOLEAN,
            timestamp: timestamp_ns,
            value: BindagenValue { b: value },
            quality: quality.into_c(),
        })
    }
    /// Get the value of the current datapoint
    /// This method may add a slight overhead to the execution time, if the value is already known it is recommended to use the specific getter methods
    /// It returns an error if the value is not supported or unknown
    #[inline]
    pub fn get_value(&self) -> Result<GCDatapointValueType, GCPluginABIError> {
        // Safety:
        // All the options are tested in the unit tests
        match self.0.type_ {
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT32 => Ok(GCDatapointValueType::Float(unsafe { self.0.value.f32_ })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT64 => Ok(GCDatapointValueType::Double(unsafe { self.0.value.f64_ })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32 => Ok(GCDatapointValueType::Int32(unsafe { self.0.value.i32_ })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT64 => Ok(GCDatapointValueType::Int64(unsafe { self.0.value.i64_ })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT32 => Ok(GCDatapointValueType::UInt32(unsafe { self.0.value.u32_ })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT64 => Ok(GCDatapointValueType::UInt64(unsafe { self.0.value.u64_ })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_BOOLEAN => Ok(GCDatapointValueType::Bool(unsafe { self.0.value.b })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_DOUBLEPOINT => Ok(GCDatapointValueType::DoublePoint(unsafe { self.0.value.dp })),
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_TIMESTAMPNS => {
                Ok(GCDatapointValueType::TimestampNs(unsafe { self.0.value.timestampNs }))
            }
            gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_BYTE_ARRAY => Err(GCPluginABIError::UnsuportedType(String::from("Byte array"))),
            unknown => Err(GCPluginABIError::UnknownDatapointValue(unknown)),
        }
    }

    /// Set the value of the current datapoint
    /// TODO: This can maybe be optimized by using unsafe
    #[inline]
    pub fn set_value(&mut self, value: GCDatapointValueType) {
        self.0.value = match value {
            GCDatapointValueType::Double(v) => BindagenValue { f64_: v },
            GCDatapointValueType::UInt64(v) => BindagenValue { u64_: v },
            GCDatapointValueType::Int64(v) => BindagenValue { i64_: v },
            GCDatapointValueType::Int32(v) => BindagenValue { i32_: v },
            GCDatapointValueType::UInt32(v) => BindagenValue { u32_: v },
            GCDatapointValueType::Float(v) => BindagenValue { f32_: v },
            GCDatapointValueType::Bool(v) => BindagenValue { b: v },
            GCDatapointValueType::DoublePoint(v) => BindagenValue { dp: v },
            GCDatapointValueType::TimestampNs(v) => BindagenValue { timestampNs: v },
            GCDatapointValueType::Bytes(_) => panic!("Byte array not supported"),
        };
    }

    /// Get the value of the current datapoint as a boolean
    /// Returns None if the value is not a boolean
    #[inline]
    pub fn get_value_bool(&self) -> Option<bool> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_BOOLEAN {
            return Some(unsafe { self.0.value.b });
        }
        None
    }
    /// Get the value of the current datapoint as a double point
    /// Returns None if the value is not a double point
    #[inline]
    pub fn get_value_double_point(&self) -> Option<u8> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_DOUBLEPOINT {
            return Some(unsafe { self.0.value.dp });
        }
        None
    }
    /// Get the value of the current datapoint as a 64 bit signed integer
    /// Returns None if the value is not a 64 bit signed integer
    #[inline]
    pub fn get_value_i64(&self) -> Option<i64> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT64 {
            return Some(unsafe { self.0.value.i64_ });
        }
        None
    }
    /// Get the value of the current datapoint as a 32 bit signed integer
    /// Returns None if the value is not a 32 bit signed integer
    #[inline]
    pub fn get_value_i32(&self) -> Option<i32> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32 {
            return Some(unsafe { self.0.value.i32_ });
        }
        None
    }
    /// Get the value of the current datapoint as a 32 bit unsigned integer
    /// Returns None if the value is not a 32 bit unsigned integer
    #[inline]
    pub fn get_value_u32(&self) -> Option<u32> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT32 {
            return Some(unsafe { self.0.value.u32_ });
        }
        None
    }
    /// Get the value of the current datapoint as a 64 bit unsigned integer
    /// Returns None if the value is not a 64 bit unsigned integer
    #[inline]
    pub fn get_value_u64(&self) -> Option<u64> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT64 {
            return Some(unsafe { self.0.value.u64_ });
        }
        None
    }
    /// Get the value of the current datapoint as a 64 bit floating point
    /// Returns None if the value is not a 64 bit floating point
    #[inline]
    pub fn get_value_f64(&self) -> Option<f64> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT64 {
            return Some(unsafe { self.0.value.f64_ });
        }
        None
    }
    /// Get the value of the current datapoint as a 32 bit floating point
    /// Returns None if the value is not a 32 bit floating point
    #[inline]
    pub fn get_value_f32(&self) -> Option<f32> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT32 {
            return Some(unsafe { self.0.value.f32_ });
        }
        None
    }
    /// Get the value of the current datapoint as a timestamp in nanoseconds
    /// Returns None if the value is not a timestamp
    #[inline]
    pub fn get_value_timestamp_ns(&self) -> Option<GCTimestamp> {
        if self.0.type_ == gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_TIMESTAMPNS {
            return Some(unsafe { self.0.value.timestampNs });
        }
        None
    }
    /// Get the value of the current datapoint as a byte array
    /// Returns None if the value is not a byte array
    #[inline]
    pub fn get_value_bytes(&self) -> Option<Vec<u8>> {
        None
    }
    /// Returns the timestamp of the datapoint value
    #[inline(always)]
    pub fn get_timestamp(&self) -> GCTimestamp {
        self.0.timestamp
    }
    /// Returns the datapoint id of the datapoint value
    #[inline(always)]
    pub fn get_id(&self) -> GCDatapointID {
        self.0.datapoint_id
    }
    /// Returns the quality of the datapoint value
    #[inline(always)]
    pub fn get_quality(&self) -> GCDatapointValueQuality {
        GCDatapointValueQuality::from_c(self.0.quality)
    }

    /// Returns the raw representation of the datapoint value
    #[inline]
    pub fn as_raw(&self) -> gc_abi_sys::sGCDatapointValue {
        self.0
    }
}

unsafe impl GCAbiConversion for GCDatapointValue {
    type CType = gc_abi_sys::sGCDatapointValue;

    fn from_c(c_type: Self::CType) -> Self {
        GCDatapointValue(c_type)
    }

    fn into_c(self) -> Self::CType {
        self.0
    }
}

impl Debug for GCDatapointValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GCDatapointValue")
            .field("value", &self.get_value())
            .field("timestamp", &self.get_timestamp())
            .field("id", &self.get_id())
            .field("quality", &self.get_quality())
            .finish()
    }
}

impl Default for GCDatapointValue {
    fn default() -> Self {
        GCDatapointValue::new_i32(0, 0, 0, GCDatapointValueQuality::new_good())
    }
}

impl serde::ser::Serialize for GCDatapointValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let binding = self.get_value();
        let value = match &binding {
            Err(e) => Err(serde::ser::Error::custom(e.to_string())),
            Ok(v) => Ok(v),
        }?;
        let mut state = serializer.serialize_struct("GCDatapointValue", 4)?;
        state.serialize_field("value", value)?;
        state.serialize_field("timestamp", &self.get_timestamp())?;
        state.serialize_field("id", &self.get_id())?;
        state.serialize_field("quality", &self.get_quality())?;
        state.end()
    }
}

impl<'de> Deserialize<'de> for GCDatapointValue {
    fn deserialize<D>(deserializer: D) -> Result<GCDatapointValue, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct GCDatapointValueHelper {
            value: GCDatapointValueType,
            timestamp: GCTimestamp,
            id: GCDatapointID,
            quality: GCDatapointValueQuality,
        }

        let helper = GCDatapointValueHelper::deserialize(deserializer)?;
        let (value, type_) = match helper.value {
            GCDatapointValueType::Bool(v) => (BindagenValue { b: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_BOOLEAN),
            GCDatapointValueType::DoublePoint(v) => (BindagenValue { dp: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_DOUBLEPOINT),
            GCDatapointValueType::Int64(v) => (BindagenValue { i64_: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT64),
            GCDatapointValueType::Int32(v) => (BindagenValue { i32_: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32),
            GCDatapointValueType::UInt32(v) => (BindagenValue { u32_: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT32),
            GCDatapointValueType::UInt64(v) => (BindagenValue { u64_: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_UINT64),
            GCDatapointValueType::Double(v) => (BindagenValue { f64_: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT64),
            GCDatapointValueType::Float(v) => (BindagenValue { f32_: v }, gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_FLOAT32),
            GCDatapointValueType::TimestampNs(v) => (
                BindagenValue { timestampNs: v },
                gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_TIMESTAMPNS,
            ),
            GCDatapointValueType::Bytes(_) => return Err(serde::de::Error::custom("Byte array not supported")),
        };

        Ok(GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id: helper.id,
            type_,
            timestamp: helper.timestamp,
            value,
            quality: helper.quality.into_c(),
        }))
    }
}

impl PartialEq for GCDatapointValue {
    /// Compares two datapoint values, it returns true if the id, timestamp, quality and value are the same.
    ///
    /// If any of the values cannot be retrieved from the datapoint, it will return false.
    fn eq(&self, other: &Self) -> bool {
        let result = self.get_id() == other.get_id() && self.get_timestamp() == other.get_timestamp() && self.get_quality() == other.get_quality();
        if !result {
            return false;
        }
        if let Ok(self_value) = self.get_value() {
            if let Ok(other_value) = other.get_value() {
                return self_value == other_value;
            }
            return false;
        }
        false
    }
}

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

    use std::ptr;
    #[test]
    fn test_int_values() {
        let val_i32 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::Int32(1234), val_i32.get_value().unwrap());
        assert_eq!(1234, val_i32.get_value_i32().unwrap());
        let val_i64 = GCDatapointValue::new_i64(0, 10, -1234567891011, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::Int64(-1234567891011), val_i64.get_value().unwrap());
        assert_eq!(-1234567891011, val_i64.get_value_i64().unwrap());
        let val_u32 = GCDatapointValue::new_u32(0, 10, 12345, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::UInt32(12345), val_u32.get_value().unwrap());
        assert_eq!(12345, val_u32.get_value_u32().unwrap());
        let val_u64 = GCDatapointValue::new_u64(0, 10, 1234567891011, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::UInt64(1234567891011), val_u64.get_value().unwrap());
        assert_eq!(1234567891011, val_u64.get_value_u64().unwrap());
    }
    #[test]
    fn test_float_values() {
        let val_f32 = GCDatapointValue::new_f32(0, 10, 1234.0, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::Float(1234.0), val_f32.get_value().unwrap());
        assert_eq!(1234.0, val_f32.get_value_f32().unwrap());
        let val_f64 = GCDatapointValue::new_f64(0, 10, -1234567891011.0, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::Double(-1234567891011.0), val_f64.get_value().unwrap());
        assert_eq!(-1234567891011.0, val_f64.get_value_f64().unwrap());
    }
    #[test]
    fn test_bool_value() {
        let val = GCDatapointValue::new_byte(0, 10, true, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::Bool(true), val.get_value().unwrap());
        assert!(val.get_value_bool().unwrap());
    }
    #[test]
    fn test_dp_value() {
        let val = GCDatapointValue::new_double_point(0, 10, 23, GCDatapointValueQuality::new_good());
        assert_eq!(GCDatapointValueType::DoublePoint(23), val.get_value().unwrap());
        assert_eq!(23, val.get_value_double_point().unwrap());
    }
    #[test]
    fn test_quality() {
        let mut quality = GCDatapointValueQuality::new_good();
        quality.set_old_data();
        let datapoint_value: GCDatapointValue = GCDatapointValue::new_i32(0, 10, 1234, quality);
        assert!(datapoint_value.get_quality().is_good());
        assert!(datapoint_value.get_quality().is_old_data());
        assert!(!datapoint_value.get_quality().is_invalid());
    }
    #[test]
    fn test_value_timestamp() {
        let val = GCDatapointValue::new_timestamp_ns(0, 10, 1234, GCDatapointValueQuality::new_good());
        assert_eq!(1234, val.get_value_timestamp_ns().unwrap());
        assert_eq!(GCDatapointValueType::TimestampNs(1234), val.get_value().unwrap());
        let val = GCDatapointValue::new_i32(0, 0, 1234, GCDatapointValueQuality::new_good());
        assert_eq!(None, val.get_value_timestamp_ns());
    }
    #[test]
    fn test_unknown_value() {
        let val = GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            type_: 100,
            value: BindagenValue { dp: 23 },
            timestamp: 10,
            datapoint_id: 0,
            quality: 0,
        });
        assert!(matches!(val.get_value(), Err(GCPluginABIError::UnknownDatapointValue(100))));
    }
    #[test]
    fn test_clone() {
        let alloc_info = allocation_counter::measure(|| {
            let dp = Box::new(GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good()));
            let dp_clone = dp.clone();
            drop(dp);
            assert_eq!(dp_clone.get_id(), 0);
            assert_eq!(dp_clone.get_timestamp(), 10);
            assert!(dp_clone.get_quality().is_good());
            assert_eq!(dp_clone.get_value_i32().unwrap(), 1234);
        });
        assert_eq!(alloc_info.bytes_current, 0, "Memory leak detected");
    }
    #[test]
    fn test_conversion_to_c() {
        let alloc_info = allocation_counter::measure(|| {
            let dp = Box::new(GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good()));
            unsafe {
                let raw_dp_ptr = Box::into_raw(dp);
                let raw_dp = raw_dp_ptr.as_mut().unwrap();
                let c_dp: *mut gc_abi_sys::sGCDatapointValue = raw_dp.as_mut_ptr();
                let from_c_dp: &GCDatapointValue = GCDatapointValue::from_c_ptr(c_dp);
                let from_c_dp_mut: &GCDatapointValue = GCDatapointValue::from_c_ptr(c_dp as *const gc_abi_sys::sGCDatapointValue);

                assert!(ptr::addr_eq(from_c_dp, raw_dp_ptr), "The rust abstraction should be zero cost");
                assert!(ptr::addr_eq(from_c_dp_mut, raw_dp_ptr), "The rust abstraction should be zero cost");
                assert!(ptr::addr_eq(c_dp, raw_dp_ptr), "The rust abstraction should be zero cost");

                let dp = Box::from_raw(raw_dp_ptr);
                assert_eq!(dp.get_id(), 0);
                assert_eq!(dp.get_timestamp(), 10);
                assert!(dp.get_quality().is_good());
                assert_eq!(dp.get_value_i32().unwrap(), 1234);
            }
        });
        assert_eq!(alloc_info.bytes_current, 0, "Memory leak detected");
        assert_eq!(
            alloc_info.count_total, 1,
            "Additional memory allocation detected, the conversion shouldn't allocate any memory"
        );
    }

    #[test]
    fn test_pointer_ref_conversion() {
        let alloc_info = allocation_counter::measure(|| {
            let dp = Box::new(gc_abi_sys::sGCDatapointValue {
                datapoint_id: 0,
                type_: gc_abi_sys::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32,
                timestamp: 10,
                value: BindagenValue { i32_: 1234 },
                quality: GCDatapointValueQuality::new_good().into_c(),
            });

            unsafe {
                let raw_dp_ptr = Box::into_raw(dp);
                let converted: &GCDatapointValue = GCDatapointValue::from_c_ptr(raw_dp_ptr);

                assert!(ptr::addr_eq(converted, raw_dp_ptr), "The rust abstraction should be zero cost");

                assert_eq!(converted.get_id(), 0);
                assert_eq!(converted.get_timestamp(), 10);
                assert!(converted.get_quality().is_good());
                assert_eq!(converted.get_value_i32().unwrap(), 1234);
                drop(Box::from_raw(raw_dp_ptr));
            }
        });

        assert_eq!(alloc_info.bytes_current, 0, "Memory leak detected");
        assert_eq!(
            alloc_info.count_total, 1,
            "Additional memory allocation detected, the conversion shouldn't allocate any memory"
        );
    }

    #[test]
    fn test_serialize_deserialize() {
        let val = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let serialized = serde_json::to_string(&val).unwrap();
        let deserialized: GCDatapointValue = serde_json::from_str(&serialized).unwrap();
        assert_eq!(val.get_id(), deserialized.get_id());
        assert_eq!(val.get_timestamp(), deserialized.get_timestamp());
        assert_eq!(val.get_quality(), deserialized.get_quality());
        assert_eq!(val.get_value_i32().unwrap(), deserialized.get_value_i32().unwrap());
    }

    #[test]
    fn test_serialize_deserialize_bool() {
        let val = GCDatapointValue::new_boolean(0, 10, true, GCDatapointValueQuality::new_good());
        let serialized = serde_json::to_string(&val).unwrap();
        let deserialized: GCDatapointValue = serde_json::from_str(&serialized).unwrap();
        assert_eq!(val.get_id(), deserialized.get_id());
        assert_eq!(val.get_timestamp(), deserialized.get_timestamp());
        assert_eq!(val.get_quality(), deserialized.get_quality());
        assert_eq!(val.get_value_bool().unwrap(), deserialized.get_value_bool().unwrap());
    }

    #[test]
    fn test_serialize_deserialize_double_point() {
        let val = GCDatapointValue::new_double_point(0, 10, 23, GCDatapointValueQuality::new_good());
        let serialized = serde_json::to_string(&val).unwrap();
        let deserialized: GCDatapointValue = serde_json::from_str(&serialized).unwrap();
        assert_eq!(val.get_id(), deserialized.get_id());
        assert_eq!(val.get_timestamp(), deserialized.get_timestamp());
        assert_eq!(val.get_quality(), deserialized.get_quality());
        assert_eq!(val.get_value_double_point().unwrap(), deserialized.get_value_double_point().unwrap());
    }

    #[test]
    fn test_clone_no_leak() {
        let alloc_info = allocation_counter::measure(|| {
            let dp = Box::new(GCDatapointValue::default());
            let dp_clone = dp.clone();
            drop(dp);
            assert_eq!(dp_clone.get_id(), 0);
            assert_eq!(dp_clone.get_timestamp(), 0);
            assert!(dp_clone.get_quality().is_good());
            assert_eq!(dp_clone.get_value_i32().unwrap(), 0);
        });
        assert_eq!(alloc_info.bytes_current, 0, "Memory leak detected");
    }

    #[test]
    fn test_comparison() {
        let val1 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let val2 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let val3 = GCDatapointValue::new_i32(0, 10, 5678, GCDatapointValueQuality::new_good());
        let val4 = GCDatapointValue::new_i32(1, 10, 1234, GCDatapointValueQuality::new_good());

        assert_eq!(val1, val2);
        assert_ne!(val1, val3);
        assert_ne!(val1, val4);
    }

    #[test]
    fn test_comparison_different_types() {
        let val1 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let val2 = GCDatapointValue::new_f32(0, 10, 1234.0, GCDatapointValueQuality::new_good());

        assert_ne!(val1, val2);
    }

    #[test]
    fn test_comparison_different_quality() {
        let val1 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let mut quality = GCDatapointValueQuality::new_good();
        quality.set_old_data();
        let val2 = GCDatapointValue::new_i32(0, 10, 1234, quality);

        assert_ne!(val1, val2);
    }

    #[test]
    fn test_comparison_different_timestamp() {
        let val1 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let val2 = GCDatapointValue::new_i32(0, 20, 1234, GCDatapointValueQuality::new_good());

        assert_ne!(val1, val2);
    }

    #[test]
    fn test_comparison_different_id() {
        let val1 = GCDatapointValue::new_i32(0, 10, 1234, GCDatapointValueQuality::new_good());
        let val2 = GCDatapointValue::new_i32(1, 10, 1234, GCDatapointValueQuality::new_good());

        assert_ne!(val1, val2);
    }

    #[test]
    fn test_comparison_error() {
        let val1 = GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id: 1,
            type_: 90,
            timestamp: 10,
            value: BindagenValue { b: false },
            quality: 0,
        });

        let val2 = GCDatapointValue(gc_abi_sys::sGCDatapointValue {
            datapoint_id: 1,
            type_: 90,
            timestamp: 10,
            value: BindagenValue { b: false },
            quality: 0,
        });

        let val3 = GCDatapointValue::new_i32(1, 10, 1234, GCDatapointValueQuality::new_good());
        assert_ne!(val1, val2);
        assert_ne!(val1, val3);
    }
}
