mod ffi_buffer;

use crate::{Error, LogLevel, Logger, MMKV};
use ffi_buffer::{Leakable, Releasable};
use mmkv_proc_macro_lib::Leakable;
use std::ffi::{CStr, c_void};
use std::fmt::Debug;
use std::os::raw::c_char;

pub(super) const LOG_TAG: &str = "MMKV:FFI";

pub type CByteArray = *const u8;
pub type CI32Array = *const i32;
pub type CI64Array = *const i64;
pub type CF32Array = *const f32;
pub type CF64Array = *const f64;

#[repr(C)]
#[derive(Debug)]
#[allow(dead_code)]
pub enum Types {
    I32,
    Str,
    Bool,
    I64,
    F32,
    F64,
    ByteArray,
    I32Array,
    I64Array,
    F32Array,
    F64Array,
}

#[repr(C)]
#[derive(Debug, Leakable)]
pub struct ByteSlice {
    pub bytes: *const u8,
    pub len: usize,
}

#[repr(C)]
#[derive(Debug, Leakable)]
pub struct RawTypedArray {
    pub array: *const c_void,
    pub type_token: Types,
    pub len: usize,
}

#[repr(C)]
#[derive(Debug)]
pub struct NativeLogger {
    obj: *mut c_void,
    callback: extern "C" fn(obj: *mut c_void, level: i32, content: *const ByteSlice),
    destroy: extern "C" fn(obj: *mut c_void),
}

unsafe impl Send for NativeLogger {}

unsafe impl Sync for NativeLogger {}

impl Drop for NativeLogger {
    fn drop(&mut self) {
        verbose!(LOG_TAG, "release {:?}", self);
        (self.destroy)(self.obj);
    }
}

impl NativeLogger {
    fn call_target(&self, log_level: LogLevel, log_str: String) {
        let mut ptr = ByteSlice::new(log_str).leak();
        (self.callback)(self.obj, log_level as i32, ptr);
        ptr.release();
    }
}

impl Logger for NativeLogger {
    fn verbose(&self, log_str: String) {
        self.call_target(LogLevel::Verbose, log_str);
    }

    fn info(&self, log_str: String) {
        self.call_target(LogLevel::Info, log_str);
    }

    fn debug(&self, log_str: String) {
        self.call_target(LogLevel::Debug, log_str);
    }

    fn warn(&self, log_str: String) {
        self.call_target(LogLevel::Warn, log_str);
    }

    fn error(&self, log_str: String) {
        self.call_target(LogLevel::Error, log_str);
    }
}

pub type RawCStr = *const c_char;

#[repr(C)]
#[derive(Debug, Leakable)]
pub struct RawBuffer {
    pub raw_data: *const c_void,
    pub type_token: Types,
    pub err: *const InternalError,
}

#[repr(C)]
#[derive(Debug, Leakable)]
pub struct InternalError {
    pub code: i32,
    pub reason: *const ByteSlice,
}

macro_rules! to_mmkv_value {
    ($value:expr, RawCStr) => {
        unsafe {
            // SAFETY: we assume ffi caller passed valid c_char
            CStr::from_ptr($value)
        }
        .to_str()
        .unwrap()
    };
    ($value:expr, bool) => {
        $value
    };
    ($value:expr, i32) => {
        $value
    };
    ($value:expr, i64) => {
        $value
    };
    ($value:expr, f32) => {
        $value
    };
    ($value:expr, f64) => {
        $value
    };
    ($value:expr, $len:expr, CByteArray) => {
        unsafe { std::slice::from_raw_parts($value, $len) }
    };
    ($value:expr, $len:expr, CI32Array) => {
        unsafe { std::slice::from_raw_parts($value, $len) }
    };
    ($value:expr, $len:expr, CI64Array) => {
        unsafe { std::slice::from_raw_parts($value, $len) }
    };
    ($value:expr, $len:expr, CF32Array) => {
        unsafe { std::slice::from_raw_parts($value, $len) }
    };
    ($value:expr, $len:expr, CF64Array) => {
        unsafe { std::slice::from_raw_parts($value, $len) }
    };
}

macro_rules! mmkv_get {
    ($mmkv:ident, $key:expr, ByteSlice) => {
        $mmkv.get($key).map(|value| ByteSlice::new(value))
    };
    ($mmkv:ident, $key:expr, bool) => {
        $mmkv.get::<bool>($key)
    };
    ($mmkv:ident, $key:expr, i32) => {
        $mmkv.get::<i32>($key)
    };
    ($mmkv:ident, $key:expr, i64) => {
        $mmkv.get::<i64>($key)
    };
    ($mmkv:ident, $key:expr, f32) => {
        $mmkv.get::<f32>($key)
    };
    ($mmkv:ident, $key:expr, f64) => {
        $mmkv.get::<f64>($key)
    };
    ($mmkv:ident, $key:expr, CByteArray) => {
        $mmkv
            .get::<Vec<u8>>($key)
            .map(|value| RawTypedArray::new(value, Types::ByteArray))
    };
    ($mmkv:ident, $key:expr, CI32Array) => {
        $mmkv
            .get::<Vec<i32>>($key)
            .map(|value| RawTypedArray::new(value, Types::I32Array))
    };
    ($mmkv:ident, $key:expr, CI64Array) => {
        $mmkv
            .get::<Vec<i64>>($key)
            .map(|value| RawTypedArray::new(value, Types::I64Array))
    };
    ($mmkv:ident, $key:expr, CF32Array) => {
        $mmkv
            .get::<Vec<f32>>($key)
            .map(|value| RawTypedArray::new(value, Types::F32Array))
    };
    ($mmkv:ident, $key:expr, CF64Array) => {
        $mmkv
            .get::<Vec<f64>>($key)
            .map(|value| RawTypedArray::new(value, Types::F64Array))
    };
}

fn map_error(key: &str, e: Error, log: &str) -> InternalError {
    error!(
        LOG_TAG,
        "{}",
        format!("failed to {} for key {}, reason {:?}", log, key, e)
    );
    e.try_into().unwrap()
}

macro_rules! impl_put {
    ($($name:ident, $value_type:tt, $type_token:expr, $log:literal;)+) => {
        $(
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn $name(
            ptr: *const c_void,
            key: RawCStr,
            value: $value_type,
        ) -> *const RawBuffer {
            let mmkv = unsafe {
                // SAFETY: we assume ffi caller passed valid mmkv pointer
                (ptr as *const MMKV).as_ref()
            }.unwrap();
            let key_str_result = unsafe {
                // SAFETY: we assume ffi caller passed valid c_char
                CStr::from_ptr(key)
            }.to_str();
            let mut result = RawBuffer::new($type_token);
            match key_str_result {
                Ok(key_str) => {
                    match mmkv.put(key_str, to_mmkv_value!(value, $value_type)) {
                        Err(e) => result.set_error(map_error(key_str, e, $log)),
                        Ok(()) => {
                            verbose!(LOG_TAG, "{} for key '{}' success", $log, key_str);
                        }
                    }
                },
                Err(e) => {
                    let log_str = format!("Invalid key: {:?}", e);
                    error!(LOG_TAG, "{}", &log_str);
                    result.set_error(InternalError::new(-1, Some(log_str)));
                }
            }
            result.leak()
        }
        )+
    };
}

macro_rules! impl_put_typed_array {
    ($($name:ident, $value_type:tt, $type_token:expr, $log:literal;)+) => {
        $(
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn $name(
            ptr: *const c_void,
            key: RawCStr,
            value: $value_type,
            len: usize,
        ) -> *const RawBuffer {
            let mmkv = unsafe {
                // SAFETY: we assume ffi caller passed valid mmkv pointer
                (ptr as *const MMKV).as_ref()
            }.unwrap();
            let key_str_result = unsafe {
                // SAFETY: we assume ffi caller passed valid c_char
                CStr::from_ptr(key)
            }.to_str();
            let mut result = RawBuffer::new($type_token);
            match key_str_result {
                Ok(key_str) => {
                    match mmkv.put(key_str, to_mmkv_value!(value, len, $value_type)) {
                        Err(e) => result.set_error(map_error(key_str, e, $log)),
                        Ok(()) => {
                            verbose!(LOG_TAG, "{} for key '{}' success", $log, key_str);
                        }
                    }
                },
                Err(e) => {
                    let log_str = format!("Invalid key: {:?}", e);
                    error!(LOG_TAG, "{}", &log_str);
                    result.set_error(InternalError::new(-1, Some(log_str)));
                }
            }
            result.leak()
        }
        )+
    };
}

macro_rules! impl_get {
    ($($name:ident, $value_type:tt, $type_token:expr, $log:literal;)+) => {
        $(
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn $name(ptr: *const c_void, key: RawCStr) -> *const RawBuffer {
            let mmkv = unsafe {
                // SAFETY: we assume ffi caller passed valid mmkv pointer
                (ptr as *const MMKV).as_ref()
            }.unwrap();
            let key_str_result = unsafe {
                // SAFETY: we assume ffi caller passed valid c_char
                CStr::from_ptr(key)
            }.to_str();
            let mut result = RawBuffer::new($type_token);
            match key_str_result {
                Ok(key_str) => {
                    match mmkv_get!(mmkv, key_str, $value_type) {
                        Err(e) => result.set_error(map_error(key_str, e, $log)),
                        Ok(value) => {
                            verbose!(LOG_TAG, "{} for key '{}' success", $log, key_str);
                            result.set_data(value);
                        }
                    }
                }
                Err(e) => {
                    let log_str = format!("Invalid key: {:?}", e);
                    error!(LOG_TAG, "{}", &log_str);
                    result.set_error(InternalError::new(-1, Some(log_str)));
                }
            }
            return result.leak();
        }
        )+
    };
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn new_instance(dir: *const c_char) -> *const c_void {
    let dir_str_result = unsafe {
        // SAFETY: we assume ffi caller passed valid c_char
        CStr::from_ptr(dir)
    }
    .to_str();
    let dir_str = match dir_str_result {
        Ok(dir_str) => dir_str,
        Err(e) => {
            error!(LOG_TAG, "Invalid dir name: {:?}", e);
            return std::ptr::null();
        }
    };
    match MMKV::new(dir_str) {
        Ok(mmkv) => Box::into_raw(Box::new(mmkv)) as *const c_void,
        Err(e) => {
            error!(
                LOG_TAG,
                "failed to create MMKV instance for '{}': {:?}", dir_str, e
            );
            std::ptr::null()
        }
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn set_logger(logger: NativeLogger) {
    MMKV::set_logger(Box::new(logger));
}

#[unsafe(no_mangle)]
pub extern "C" fn set_log_level(log_level: i32) {
    MMKV::set_log_level(log_level.try_into().unwrap())
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn free_buffer(ptr: *const c_void) {
    (ptr as *mut RawBuffer).release();
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn close_instance(ptr: *const c_void) {
    unsafe {
        // SAFETY: we assume ffi caller passed valid mmkv pointer
        // Drop ptr
        let _ = Box::from_raw(ptr as *mut MMKV);
    };
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn clear_data(ptr: *const c_void) {
    let mmkv = unsafe {
        // SAFETY: we assume ffi caller passed valid mmkv pointer
        (ptr as *const MMKV).as_ref()
    }
    .unwrap();
    if let Err(e) = mmkv.clear_data() {
        error!(LOG_TAG, "failed to clear MMKV data: {:?}", e);
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn delete(ptr: *const c_void, key: RawCStr) -> *const RawBuffer {
    let mmkv = unsafe {
        // SAFETY: we assume ffi caller passed valid mmkv pointer
        (ptr as *const MMKV).as_ref()
    }
    .unwrap();
    let mut result = RawBuffer::new(Types::Str);
    let key_str_result = unsafe {
        // SAFETY: we assume ffi caller passed valid c_char
        CStr::from_ptr(key)
    }
    .to_str();
    match key_str_result {
        Ok(key_str) => match mmkv.delete(key_str) {
            Err(e) => result.set_error(map_error(key_str, e, "delete")),
            Ok(()) => verbose!(LOG_TAG, "delete key {} success", key_str),
        },
        Err(e) => {
            let log_str = format!("Invalid key: {:?}", e);
            error!(LOG_TAG, "{}", &log_str);
            result.set_error(InternalError::new(-1, Some(log_str)));
        }
    }
    result.leak()
}

impl_put!(
    put_str, RawCStr, Types::Str, "put string";
    put_bool, bool, Types::Bool, "put bool";
    put_i32, i32, Types::I32, "put i32";
    put_i64, i64, Types::I64, "put i64";
    put_f32, f32, Types::F32, "put f32";
    put_f64, f64, Types::F64, "put f64";
);

impl_get!(
    get_str, ByteSlice, Types::Str, "get string";
    get_bool, bool, Types::Bool, "get bool";
    get_i32, i32, Types::I32, "get i32";
    get_i64, i64, Types::I64, "get i64";
    get_f32, f32, Types::F32, "get f32";
    get_f64, f64, Types::F64, "get f64";
    get_byte_array, CByteArray, Types::ByteArray, "get byte array";
    get_i32_array, CI32Array, Types::I32Array, "get i32 array";
    get_i64_array, CI64Array, Types::I64Array, "get i64 array";
    get_f32_array, CF32Array, Types::F32Array, "get f32 array";
    get_f64_array, CF64Array, Types::F64Array, "get f64 array";
);

impl_put_typed_array!(
    put_byte_array, CByteArray, Types::ByteArray, "put byte array";
    put_i32_array, CI32Array, Types::I32Array, "put i32 array";
    put_i64_array, CI64Array, Types::I64Array, "put i64 array";
    put_f32_array, CF32Array, Types::F32Array, "put f32 array";
    put_f64_array, CF64Array, Types::F64Array, "put f64 array";
);
