// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use alloc::boxed::Box;
use alloc::ffi::CString;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{anyhow, bail, Result};
use core::ffi::{c_char, c_longlong, c_void, CStr};
use core::{mem, ptr};

/// Status of a call on `RegorusEngine`.
#[derive(Debug, PartialEq)]
#[repr(C)]
pub enum RegorusStatus {
    /// The operation was successful.
    Ok,

    /// The operation was unsuccessful.
    Error,

    /// Invalid data format provided.
    InvalidDataFormat,

    /// Invalid entrypoint rule specified.
    InvalidEntrypoint,

    /// Compilation failed.
    CompilationFailed,

    /// Invalid argument provided.
    InvalidArgument,

    /// Invalid module ID.
    InvalidModuleId,

    /// Invalid policy content.
    InvalidPolicy,

    /// The engine panicked and cannot be reused until reset.
    Panic,

    /// The engine remains poisoned because a previous panic was detected.
    Poisoned,
}

/// Type of data contained in RegorusResult
#[repr(C)]
#[allow(unused)]
pub enum RegorusDataType {
    /// No data / void
    None,
    /// String data (output field is valid)
    String,
    /// Boolean data (bool_value field is valid)  
    Boolean,
    /// Integer data (int_value field is valid)
    Integer,
    /// Pointer data (pointer_value field is valid)
    Pointer,
}

/// Result of a call on `RegorusEngine`.
///
/// Must be freed using `regorus_result_drop`.
#[repr(C)]
pub struct RegorusResult {
    /// Status
    pub(crate) status: RegorusStatus,

    /// Type of data contained in this result
    pub(crate) data_type: RegorusDataType,

    /// String output produced by the call.
    /// Valid when data_type is String. Owned by Rust.
    pub(crate) output: *mut c_char,

    /// Boolean value.
    /// Valid when data_type is Boolean.
    pub(crate) bool_value: bool,

    /// Integer value.
    /// Valid when data_type is Integer.
    pub(crate) int_value: c_longlong,

    /// Pointer value.
    /// Valid when data_type is Pointer.
    pub(crate) pointer_value: *mut c_void,

    /// Errors produced by the call.
    /// Owned by Rust.
    pub(crate) error_message: *mut c_char,
}

/// Byte buffer returned from FFI for binary payloads.
///
/// Must be freed using `regorus_buffer_drop`.
#[repr(C)]
pub struct RegorusBuffer {
    /// Pointer to byte buffer data.
    pub data: *mut u8,
    /// Number of bytes stored in `data`.
    pub len: usize,
    /// Capacity of the allocation backing `data`.
    pub capacity: usize,
}

impl RegorusResult {
    /// Create a successful result with no data.
    pub(crate) fn ok_void() -> Self {
        Self {
            status: RegorusStatus::Ok,
            data_type: RegorusDataType::None,
            output: ptr::null_mut(),
            bool_value: false,
            int_value: 0,
            pointer_value: ptr::null_mut(),
            error_message: ptr::null_mut(),
        }
    }

    /// Create a successful result with string output.
    pub(crate) fn ok_string(output: String) -> Self {
        Self {
            status: RegorusStatus::Ok,
            data_type: RegorusDataType::String,
            output: to_c_str(output),
            bool_value: false,
            int_value: 0,
            pointer_value: ptr::null_mut(),
            error_message: ptr::null_mut(),
        }
    }

    /// Create a successful result with boolean value.
    #[allow(unused)]
    pub(crate) fn ok_bool(value: bool) -> Self {
        Self {
            status: RegorusStatus::Ok,
            data_type: RegorusDataType::Boolean,
            output: ptr::null_mut(),
            bool_value: value,
            int_value: 0,
            pointer_value: ptr::null_mut(),
            error_message: ptr::null_mut(),
        }
    }

    /// Create a successful result with integer value.
    #[allow(unused)]
    pub(crate) fn ok_int(value: i64) -> Self {
        Self {
            status: RegorusStatus::Ok,
            data_type: RegorusDataType::Integer,
            output: ptr::null_mut(),
            bool_value: false,
            int_value: value as c_longlong,
            pointer_value: ptr::null_mut(),
            error_message: ptr::null_mut(),
        }
    }

    /// Create a successful result with pointer value.
    pub(crate) fn ok_pointer(pointer: *mut c_void) -> Self {
        Self {
            status: RegorusStatus::Ok,
            data_type: RegorusDataType::Pointer,
            output: ptr::null_mut(),
            bool_value: false,
            int_value: 0,
            pointer_value: pointer,
            error_message: ptr::null_mut(),
        }
    }

    /// Create an error result with specific status.
    pub(crate) fn err(status: RegorusStatus) -> Self {
        Self {
            status,
            data_type: RegorusDataType::None,
            output: ptr::null_mut(),
            bool_value: false,
            int_value: 0,
            pointer_value: ptr::null_mut(),
            error_message: ptr::null_mut(),
        }
    }

    /// Create an error result with status and message.
    pub(crate) fn err_with_message(status: RegorusStatus, message: String) -> Self {
        Self {
            status,
            data_type: RegorusDataType::None,
            output: ptr::null_mut(),
            bool_value: false,
            int_value: 0,
            pointer_value: ptr::null_mut(),
            error_message: to_c_str(message),
        }
    }
}

impl RegorusBuffer {
    pub(crate) fn from_vec(mut data: Vec<u8>) -> *mut RegorusBuffer {
        let buffer = RegorusBuffer {
            data: data.as_mut_ptr(),
            len: data.len(),
            capacity: data.capacity(),
        };
        mem::forget(data);
        Box::into_raw(Box::new(buffer))
    }
}

pub(crate) fn to_c_str(s: String) -> *mut c_char {
    match CString::new(s) {
        Ok(cs) => cs.into_raw(),
        _ => to_c_str("binding error: failed to create c-style string".to_string()),
    }
}

pub(crate) fn from_c_str(s: *const c_char) -> Result<String> {
    if s.is_null() {
        bail!("null pointer");
    }
    unsafe {
        CStr::from_ptr(s)
            .to_str()
            .map_err(|e| anyhow!("invalid utf8: {e}"))
            .map(|s| s.to_string())
    }
}

pub(crate) fn to_ref<'a, T>(t: *mut T) -> Result<&'a mut T> {
    unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) }
}

pub(crate) fn to_shared_ref<'a, T>(t: *const T) -> Result<&'a T> {
    unsafe { t.as_ref().ok_or_else(|| anyhow!("null pointer")) }
}

pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult {
    match r {
        Ok(()) => RegorusResult::ok_void(),
        Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
    }
}

pub(crate) fn to_regorus_string_result(r: Result<String>) -> RegorusResult {
    match r {
        Ok(s) => RegorusResult::ok_string(s),
        Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")),
    }
}

/// Drop a `RegorusBuffer`.
///
/// `data` is not valid after drop.
#[no_mangle]
pub extern "C" fn regorus_buffer_drop(buffer: *mut RegorusBuffer) {
    if let Ok(buffer) = to_ref(buffer) {
        unsafe {
            if !buffer.data.is_null() {
                let _ = Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity);
            }
            let _ = Box::from_raw(ptr::from_mut(buffer));
        }
    }
}

/// Drop a `RegorusResult`.
///
/// `output` and `error_message` strings are not valid after drop.
#[no_mangle]
pub extern "C" fn regorus_result_drop(r: RegorusResult) {
    unsafe {
        if !r.error_message.is_null() {
            let _ = CString::from_raw(r.error_message);
        }
        if !r.output.is_null() {
            let _ = CString::from_raw(r.output);
        }
    }
}
