use crate::define::TH_COMMON_ETCFN_APICALL;
use crate::define::TH_ESStreamInfo;
use crate::define::ThCommon;
use anyhow::Result;
use anyhow::anyhow;
use cyberex::array_cstr_to_str;
use cyberex::void::HyVoid;
use cyberex::void::opacue_to_mut;
use cyberex::xffi::sto::cchar_to_string;
use std::ffi::*;
use xody::body::CallReq;
use xody::body::CallRet;

use super::define::ConfigRelay;

pub struct Helper {
    common: HyVoid<ThCommon>,
}

impl Helper {
    pub fn new(common_p: *const ThCommon) -> Self {
        Self {
            common: HyVoid::from_ptr(common_p as *mut _),
        }
    }

    fn ctx(&self) -> HyVoid<()> {
        HyVoid::<()>::from_ptr(self.common.as_ref().ctx)
    }

    pub fn get_event_cb(&self) -> impl Fn(String) + 'static + Sync + Send {
        let event_cb = self.common.as_ref().event_cb;
        let ctx = self.ctx();

        move |event_str: String| {
            let Some(event_cb) = event_cb else {
                return;
            };
            let Ok(event_str) = CString::new(event_str) else {
                return;
            };
            unsafe {
                event_cb(ctx.as_ptr(), event_str.as_ptr());
            }
        }
    }

    pub fn get_data_cb(&self) -> impl Fn(&str, &[u8], TH_ESStreamInfo) + Send + Sync + 'static {
        let data_cb = self.common.as_ref().data_cb;
        let ctx = self.ctx();

        move |access_id, data, info| {
            let Some(data_cb) = data_cb else {
                return;
            };
            let Ok(access_id) = CString::new(access_id) else {
                return;
            };
            unsafe {
                data_cb(
                    ctx.as_ptr(),
                    access_id.as_ptr().cast(),
                    data.as_ptr().cast(),
                    data.len() as _,
                    std::ptr::addr_of!(info),
                );
            }
        }
    }

    pub fn get_soul_relay(&self) -> Result<ConfigRelay> {
        let s = self.get_soul_scope_ex("relay")?;
        let config = serde_yaml::from_str(&s)?;
        Ok(config)
    }

    pub fn get_soul_scope_ex(&self, scope: &str) -> Result<String> {
        type RetType = Option<Result<String>>;

        let get_soul_scope_ex = self
            .common
            .as_ref()
            .get_soul_scope_ex
            .ok_or_else(|| anyhow!("get_soul_scope_ex must be set in common"))?;
        let mut sync_result: RetType = None;

        extern "C" fn result_cb(ret: c_int, result: *const c_char, _result_len: usize, user_data: c_longlong) {
            if ret != 0 {
                return;
            }
            let sync_result = opacue_to_mut::<RetType>(user_data as *mut _);

            let _ = sync_result.insert(Ok(cchar_to_string(result)));
        }

        let scope = CString::new(scope)?;
        unsafe {
            get_soul_scope_ex(
                self.common.as_ref().ctx,
                scope.as_ptr(),
                Some(result_cb),
                std::ptr::addr_of_mut!(sync_result) as _,
            )
        };
        sync_result.ok_or_else(|| anyhow!("Soul not get"))?
    }

    pub fn do_etc_ex(&self, req: CallReq) -> Result<CallRet> {
        type RetType = Option<Result<String>>;

        let do_etc_ex = self
            .common
            .as_ref()
            .do_etc_ex
            .ok_or_else(|| anyhow!("do_etc_ex must be set in common"))?;

        let mut sync_result: RetType = None;

        extern "C" fn result_cb(ret: c_int, result: *const c_char, _result_len: usize, user_data: c_longlong) {
            if ret != 0 {
                return;
            }
            let sync_result = opacue_to_mut::<RetType>(user_data as *mut _);
            let result_string = cchar_to_string(result);

            let _ = sync_result.insert(Ok(result_string));
        }
        let input_json_cstr = CString::new(serde_json::to_string(&req)?)?;
        unsafe {
            do_etc_ex(
                self.common.as_ref().ctx,
                input_json_cstr.as_ptr().cast(),
                Some(result_cb),
                std::ptr::addr_of_mut!(sync_result) as _,
            )
        };

        let re = sync_result.ok_or_else(|| anyhow!("Method no return"))??;
        let call_ret = serde_json::from_str(&re)?;
        Ok(call_ret)
    }

    pub fn logger_get_this_span(&self) -> Result<String> {
        let req = CallReq::new("logger_get_this_span", None);
        let ret = self.do_etc_ex(req)?.to_result()?;
        match ret {
            Some(this_span) => Ok(this_span.to_string()),
            None => Ok("".to_string()),
        }
    }
    pub fn get_plugin_installed_path(&self) -> Result<String> {
        let req = CallReq::new("get_plugin_installed_path", None);
        let ret = self.do_etc_ex(req)?.to_result()?;
        match ret {
            Some(this_span) => Ok(this_span.to_string()),
            None => Ok("".to_string()),
        }
    }

    pub fn api_call(&self, req: CallReq) -> Result<CallRet> {
        let req_json_value = serde_json::to_value(req)?;
        let req = CallReq::new_from_json(array_cstr_to_str!(TH_COMMON_ETCFN_APICALL), req_json_value)?;
        let ret = self.do_etc_ex(req)?;
        Ok(ret)
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_send() {
        fn test_fun<T: Sync + Send>() {}
        test_fun::<Helper>();
    }
}
