use crate::GCLogLevel;
use crate::GCPluginConfig;
use crate::error::GCPluginABIError;
use crate::raw;
use crate::{GCAbiConversion, GCDatapointID, GCDatapointValue};
use std::ffi::CStr;

pub struct GCCoreExportedFunctions {
    pub log_system: raw::GCSystemLogCoreExport,
    pub log_audit: raw::GCSystemLogAuditCoreExport,
    pub release_datapoint_value: raw::GCReleaseDatapointValueCoreExport,
    pub get_last_datapoint_value: raw::GCGetLastDatapointValueCoreExport,
    pub publish_datapoint_value: raw::GCPublishDatapointValueCoreExport,
}

/// Zero cost abstraction over the GCPluginInterface
/// Safety:
/// All the function calls are valid as long as the underlying GCPluginInterface pointer is valid
#[repr(transparent)]
pub struct GCPluginInterface(gc_abi_sys::sGCPluginInterface);

impl GCPluginInterface {
    /// Create a new GCPluginInterface
    ///
    /// # Safety
    /// The caller is responsible for ensuring the ctx and config are valid for the lifetime of the GCPluginInterface
    pub unsafe fn new<T>(config: GCPluginConfig, ctx: *const T, exports: GCCoreExportedFunctions, log_level: GCLogLevel) -> Self {
        GCPluginInterface(gc_abi_sys::sGCPluginInterface {
            config: config.into_c(),
            ctx: ctx as *mut std::ffi::c_void,
            logSystemCallback: exports.log_system,
            logLevel: log_level.into_c(),
            logAuditCallback: exports.log_audit,
            releaseDatapointValueCallback: exports.release_datapoint_value,
            getLastDatapointValueCallback: exports.get_last_datapoint_value,
            publishDatapointValueCallback: exports.publish_datapoint_value,
        })
    }

    /// Publishes a datapoint value
    #[inline]
    pub fn publish_datapoint(&self, datapoint_value: &GCDatapointValue) -> bool {
        // Safety:
        // Calling FFI functions is inherently unsafe, the gateway core guarantees that callbacks are always valid
        unsafe {
            let ptr = datapoint_value as *const GCDatapointValue;
            self.0.publishDatapointValueCallback.unwrap_unchecked()(self.0.ctx, ptr as *mut gc_abi_sys::sGCDatapointValue)
        }
    }

    /// Returns the configuration of the plugin
    #[inline]
    pub fn get_config(&self) -> &GCPluginConfig {
        GCPluginConfig::from_c_ref(&self.0.config)
    }

    /// Retrieve the last value of a datapoint from the realtime database
    ///
    /// # Safety
    /// The caller is responsible for freeing the memory of the returned value and also it has to ensure the pointer is not null
    #[inline]
    pub unsafe fn get_last_datapoint_value(&self, datapoint_id: GCDatapointID) -> *const GCDatapointValue {
        unsafe {
            let ptr: gc_abi_sys::GCDatapointValue = self.0.getLastDatapointValueCallback.unwrap()(self.0.ctx, datapoint_id);
            ptr as *const GCDatapointValue
        }
    }

    /// Log a message
    #[inline]
    pub fn log(&self, level: gc_abi_sys::eGCLogLevel, message: &CStr) {
        // Safety:
        // The pointer is guaranteed to be valid by the gateway core
        unsafe { self.0.logSystemCallback.unwrap_unchecked()(self.0.ctx, level, message.as_ptr()) }
    }

    /// Log a message audit
    #[inline]
    pub fn log_audit(&self, message: &CStr) {
        // Safety:
        // The pointer is guaranteed to be valid by the gateway core
        unsafe { self.0.logAuditCallback.unwrap_unchecked()(self.0.ctx, message.as_ptr()) }
    }

    /// Get the C log level of the plugin
    #[inline]
    pub fn get_raw_log_level(&self) -> gc_abi_sys::eGCLogLevel {
        self.0.logLevel
    }

    /// Get the log level of the plugin
    #[inline]
    pub fn get_log_level(&self) -> Result<GCLogLevel, GCPluginABIError> {
        GCLogLevel::from_c(self.get_raw_log_level())
    }
}

unsafe impl GCAbiConversion for GCPluginInterface {
    type CType = gc_abi_sys::sGCPluginInterface;

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

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

unsafe impl Send for GCPluginInterface {}
unsafe impl Sync for GCPluginInterface {}
