use std::ffi::c_void;

use crate::error::{Error, ErrorEvent, ErrorEventSet, Result};

pub const H5ES_NONE: u64 = 0;
pub const H5ES_WAIT_FOREVER: u64 = u64::MAX;
pub const H5ES_WAIT_NONE: u64 = 0;

#[allow(non_camel_case_types)]
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum H5ES_status_t {
    H5ES_STATUS_IN_PROGRESS = 0,
    H5ES_STATUS_SUCCEED = 1,
    H5ES_STATUS_CANCELED = 2,
    H5ES_STATUS_FAIL = 3,
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H5ES_op_info_t {
    pub api_name: String,
    pub api_args: String,
    pub app_file_name: String,
    pub app_func_name: String,
    pub app_line_num: u32,
    pub op_ins_count: u64,
    pub op_ins_ts: u64,
    pub op_exec_ts: u64,
    pub op_exec_time: u64,
}

#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H5ES_err_info_t {
    pub api_name: String,
    pub api_args: String,
    pub app_file_name: String,
    pub app_func_name: String,
    pub app_line_num: u32,
    pub op_ins_count: u64,
    pub op_ins_ts: u64,
    pub op_exec_ts: u64,
    pub op_exec_time: u64,
    pub err_stack_id: u64,
}

#[allow(non_camel_case_types)]
pub type H5ES_event_insert_func_t =
    Option<unsafe extern "C" fn(*const H5ES_op_info_t, *mut c_void) -> i32>;

#[allow(non_camel_case_types)]
pub type H5ES_event_complete_func_t =
    Option<unsafe extern "C" fn(*mut H5ES_op_info_t, H5ES_status_t, u64, *mut c_void) -> i32>;

fn unsupported_event_set(name: &str) -> Error {
    Error::Unsupported(format!(
        "{name} requires libhdf5 asynchronous VOL event-set behavior not implemented in pure-Rust mode"
    ))
}

#[allow(non_snake_case)]
pub fn H5ES__init_package() -> ErrorEventSet {
    ErrorEventSet::init_package()
}

#[allow(non_snake_case)]
pub fn H5ES_term_package(event_set: &mut ErrorEventSet) {
    event_set.term_package();
}

#[allow(non_snake_case)]
pub fn H5EScreate() -> ErrorEventSet {
    ErrorEventSet::create()
}

#[allow(non_snake_case)]
pub fn H5ES__create_api_common() -> ErrorEventSet {
    ErrorEventSet::create_internal()
}

#[allow(non_snake_case)]
pub fn H5ESclose(event_set: ErrorEventSet) {
    event_set.close_cb();
}

#[allow(non_snake_case)]
pub fn H5ES__close_cb(event_set: ErrorEventSet) {
    event_set.close_cb();
}

#[allow(non_snake_case)]
pub fn H5ES__insert_request(event_set: &mut ErrorEventSet, request: impl Into<String>) {
    event_set.insert_request_internal(request);
}

#[allow(non_snake_case)]
pub fn H5ESinsert_request(event_set: &mut ErrorEventSet, request: impl Into<String>) {
    event_set.insert_request(request);
}

#[allow(non_snake_case)]
pub fn H5ES_insert(event_set: &mut ErrorEventSet, request: impl Into<String>) {
    event_set.insert(request);
}

#[allow(non_snake_case)]
pub fn H5ES__list_append(event_set: &mut ErrorEventSet, event: ErrorEvent) {
    event_set.list_append(event);
}

#[allow(non_snake_case)]
pub fn H5ES__list_count(event_set: &ErrorEventSet) -> usize {
    event_set.list_count()
}

#[allow(non_snake_case)]
pub fn H5ESget_count(event_set: &ErrorEventSet) -> usize {
    event_set.get_count()
}

#[allow(non_snake_case)]
pub fn H5ESget_count_into(event_set: &ErrorEventSet, count: &mut usize) -> Result<()> {
    *count = event_set.get_count();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5ESget_op_counter(event_set: &ErrorEventSet) -> u64 {
    event_set.get_op_counter()
}

#[allow(non_snake_case)]
pub fn H5ESget_op_counter_into(event_set: &ErrorEventSet, counter: &mut u64) -> Result<()> {
    *counter = event_set.get_op_counter();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5ESget_op_info(
    _event_set: &ErrorEventSet,
    _op_counter: u64,
    _op_info: &mut H5ES_op_info_t,
) -> Result<()> {
    Err(unsupported_event_set("H5ESget_op_info"))
}

#[allow(non_snake_case)]
pub fn H5ESwait(
    event_set: &mut ErrorEventSet,
    timeout: u64,
    num_in_progress: &mut usize,
    err_occurred: &mut bool,
) -> Result<()> {
    if timeout != H5ES_WAIT_NONE && timeout != H5ES_WAIT_FOREVER {
        return Err(unsupported_event_set("H5ESwait finite timeout"));
    }

    *num_in_progress = event_set.wait();
    *err_occurred = event_set.get_err_status();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5EScancel(
    event_set: &mut ErrorEventSet,
    num_not_canceled: &mut usize,
    err_occurred: &mut bool,
) -> Result<()> {
    *num_not_canceled = event_set.cancel();
    *err_occurred = event_set.get_err_status();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5ESget_err_status(event_set: &ErrorEventSet) -> bool {
    event_set.get_err_status()
}

#[allow(non_snake_case)]
pub fn H5ESget_err_status_into(event_set: &ErrorEventSet, err_occurred: &mut bool) -> Result<()> {
    *err_occurred = event_set.get_err_status();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5ESget_err_count(event_set: &ErrorEventSet) -> usize {
    event_set.get_err_count()
}

#[allow(non_snake_case)]
pub fn H5ESget_err_count_into(event_set: &ErrorEventSet, err_count: &mut usize) -> Result<()> {
    *err_count = event_set.get_err_count();
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5ESget_requests_with<F>(event_set: &ErrorEventSet, callback: F)
where
    F: FnMut(&str),
{
    event_set.get_requests_with(callback);
}

#[allow(non_snake_case)]
pub fn H5ESget_requests_into(event_set: &ErrorEventSet, out: &mut Vec<String>) {
    out.clear();
    out.extend(event_set.requests().map(str::to_owned));
}

#[deprecated(note = "use H5ESget_requests_with or H5ESget_requests_into")]
#[allow(non_snake_case)]
pub fn H5ESget_requests(event_set: &ErrorEventSet) -> Vec<String> {
    let mut out = Vec::new();
    H5ESget_requests_into(event_set, &mut out);
    out
}

#[allow(non_snake_case)]
pub fn H5ESget_err_info_with<F>(event_set: &ErrorEventSet, callback: F)
where
    F: FnMut(&str),
{
    event_set.get_err_info_with(callback);
}

#[allow(non_snake_case)]
pub fn H5ESget_err_info_into(event_set: &ErrorEventSet, out: &mut Vec<String>) {
    out.clear();
    out.extend(event_set.err_info().map(str::to_owned));
}

#[allow(non_snake_case)]
pub fn H5ESfree_err_info(num_err_info: usize, err_info: &mut [H5ES_err_info_t]) -> Result<()> {
    let available = err_info.len();
    let infos = err_info.get_mut(..num_err_info).ok_or_else(|| {
        Error::Other(format!(
            "H5ESfree_err_info requested {num_err_info} records from {available}-record buffer"
        ))
    })?;

    for info in infos {
        info.api_name.clear();
        info.api_args.clear();
        info.app_file_name.clear();
        info.app_func_name.clear();
        info.app_line_num = 0;
        info.op_ins_count = 0;
        info.op_ins_ts = 0;
        info.op_exec_ts = 0;
        info.op_exec_time = 0;
        info.err_stack_id = 0;
    }
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5ESget_err_info(
    _event_set: &mut ErrorEventSet,
    _num_err_info: usize,
    _err_info: &mut [H5ES_err_info_t],
    _err_cleared: &mut usize,
) -> Result<()> {
    Err(unsupported_event_set("H5ESget_err_info"))
}

#[allow(non_snake_case)]
pub fn H5ES__get_err_info(
    _event_set: &mut ErrorEventSet,
    _num_err_info: usize,
    _err_info: &mut [H5ES_err_info_t],
    _err_cleared: &mut usize,
) -> Result<()> {
    Err(unsupported_event_set("H5ES__get_err_info"))
}

#[allow(non_snake_case)]
pub fn H5ES__close_failed_cb(event_set: &mut ErrorEventSet) {
    event_set.close_failed_cb();
}

#[allow(non_snake_case)]
pub fn H5ESclear(event_set: &mut ErrorEventSet) {
    event_set.term_package();
}

#[allow(non_snake_case)]
pub fn H5ESfail_request(
    event_set: &mut ErrorEventSet,
    request: impl Into<String>,
    error: impl Into<String>,
) {
    let mut event = ErrorEvent::event_new(request);
    event.handle_fail(error);
    event_set.list_append(event);
}

#[allow(non_snake_case)]
pub fn H5EScomplete_request(event_set: &mut ErrorEventSet, request: impl Into<String>) {
    let mut event = ErrorEvent::event_new(request);
    event.event_completed();
    event_set.list_append(event);
}

#[allow(non_snake_case)]
pub fn H5ESregister_insert_func(
    _event_set: &mut ErrorEventSet,
    _func: H5ES_event_insert_func_t,
    _ctx: *mut c_void,
) -> Result<()> {
    Err(unsupported_event_set("H5ESregister_insert_func"))
}

#[allow(non_snake_case)]
pub fn H5ESregister_complete_func(
    _event_set: &mut ErrorEventSet,
    _func: H5ES_event_complete_func_t,
    _ctx: *mut c_void,
) -> Result<()> {
    Err(unsupported_event_set("H5ESregister_complete_func"))
}

#[allow(non_snake_case)]
pub fn H5EShas_insert_func(event_set: &ErrorEventSet) -> bool {
    event_set.has_insert_callback()
}

#[allow(non_snake_case)]
pub fn H5EShas_complete_func(event_set: &ErrorEventSet) -> bool {
    event_set.has_complete_callback()
}

#[allow(non_snake_case)]
pub fn H5ESnoop() -> Result<()> {
    Ok(())
}

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

    #[test]
    fn h5es_aliases_preserve_event_set_semantics() {
        let mut set = H5EScreate();
        H5ESinsert_request(&mut set, "read");
        H5ES__insert_request(&mut set, "write");
        H5EScomplete_request(&mut set, "flush");
        H5ESfail_request(&mut set, "close", "failed");

        assert_eq!(H5ESget_count(&set), 4);
        assert_eq!(H5ES__list_count(&set), 4);
        assert_eq!(H5ESget_op_counter(&set), 4);
        assert!(H5ESget_err_status(&set));
        assert_eq!(H5ESget_err_count(&set), 1);

        let mut requests = Vec::new();
        H5ESget_requests_into(&set, &mut requests);
        assert_eq!(requests, ["read", "write", "flush", "close"]);

        let mut visited = Vec::new();
        H5ESget_requests_with(&set, |request| visited.push(request.to_owned()));
        assert_eq!(visited, requests);

        let mut errors = Vec::new();
        H5ESget_err_info_into(&set, &mut errors);
        assert_eq!(errors, ["failed"]);
        errors.clear();

        assert!(!H5EShas_insert_func(&set));
        assert!(!H5EShas_complete_func(&set));

        let mut num_in_progress = usize::MAX;
        let mut err_occurred = false;
        H5ESwait(
            &mut set,
            H5ES_WAIT_FOREVER,
            &mut num_in_progress,
            &mut err_occurred,
        )
        .unwrap();
        assert_eq!(num_in_progress, 2);
        assert!(err_occurred);

        let mut num_not_canceled = usize::MAX;
        err_occurred = false;
        H5EScancel(&mut set, &mut num_not_canceled, &mut err_occurred).unwrap();
        assert_eq!(num_not_canceled, 0);
        assert!(err_occurred);
        H5ES__close_failed_cb(&mut set);
        assert_eq!(H5ESget_err_count(&set), 0);
        H5ESclear(&mut set);
        assert_eq!(H5ESget_count(&set), 0);
    }

    #[test]
    fn h5es_wait_and_cancel_use_public_output_parameters() {
        let mut set = H5EScreate();
        H5ESinsert_request(&mut set, "read");
        H5ESfail_request(&mut set, "write", "failed");

        let mut num_in_progress = 99;
        let mut err_occurred = false;
        H5ESwait(
            &mut set,
            H5ES_WAIT_NONE,
            &mut num_in_progress,
            &mut err_occurred,
        )
        .unwrap();
        assert_eq!(num_in_progress, 1);
        assert!(err_occurred);

        let mut num_not_canceled = 99;
        err_occurred = false;
        H5EScancel(&mut set, &mut num_not_canceled, &mut err_occurred).unwrap();
        assert_eq!(num_not_canceled, 0);
        assert!(err_occurred);
    }

    #[test]
    fn h5es_wait_finite_timeout_is_explicit_unsupported_boundary() {
        let mut set = H5EScreate();
        H5ESinsert_request(&mut set, "read");

        let mut num_in_progress = usize::MAX;
        let mut err_occurred = true;
        let err = H5ESwait(&mut set, 1, &mut num_in_progress, &mut err_occurred).unwrap_err();

        assert_eq!(
            err.to_string(),
            "Unsupported: H5ESwait finite timeout requires libhdf5 asynchronous VOL event-set behavior not implemented in pure-Rust mode"
        );
        assert_eq!(num_in_progress, usize::MAX);
        assert!(err_occurred);

        H5ESwait(
            &mut set,
            H5ES_WAIT_FOREVER,
            &mut num_in_progress,
            &mut err_occurred,
        )
        .unwrap();
        assert_eq!(num_in_progress, 1);
        assert!(!err_occurred);
    }

    #[test]
    fn h5es_insert_entry_point_preserves_request_semantics() {
        let mut set = H5EScreate();

        H5ES_insert(&mut set, "read");
        H5ESinsert_request(&mut set, "write");

        let mut requests = Vec::new();
        H5ESget_requests_into(&set, &mut requests);
        assert_eq!(requests, ["read", "write"]);
        assert_eq!(H5ESget_count(&set), 2);
        assert_eq!(H5ESget_op_counter(&set), 2);
    }

    #[test]
    fn h5es_count_and_counter_use_public_output_parameters() {
        let mut set = H5EScreate();
        H5ESinsert_request(&mut set, "read");
        H5EScomplete_request(&mut set, "flush");
        H5ESfail_request(&mut set, "close", "failed");

        let mut count = usize::MAX;
        H5ESget_count_into(&set, &mut count).unwrap();
        assert_eq!(count, 3);

        let mut counter = u64::MAX;
        H5ESget_op_counter_into(&set, &mut counter).unwrap();
        assert_eq!(counter, 3);

        H5ESclear(&mut set);
        H5ESget_count_into(&set, &mut count).unwrap();
        assert_eq!(count, 0);
        H5ESget_op_counter_into(&set, &mut counter).unwrap();
        assert_eq!(counter, 3);
    }

    #[test]
    fn h5es_op_info_query_is_explicit_unsupported_boundary() {
        let mut set = H5EScreate();
        H5ESinsert_request(&mut set, "read");
        let mut op_info = H5ES_op_info_t {
            api_name: "sentinel".into(),
            api_args: "args".into(),
            app_file_name: "app.rs".into(),
            app_func_name: "caller".into(),
            app_line_num: 7,
            op_ins_count: 9,
            op_ins_ts: 11,
            op_exec_ts: 13,
            op_exec_time: 17,
        };
        let unchanged = op_info.clone();

        let err = H5ESget_op_info(&set, 1, &mut op_info).unwrap_err();
        assert_eq!(
            err.to_string(),
            "Unsupported: H5ESget_op_info requires libhdf5 asynchronous VOL event-set behavior not implemented in pure-Rust mode"
        );
        assert_eq!(op_info, unchanged);
        assert_eq!(H5ESget_count(&set), 1);
        assert_eq!(H5ESget_op_counter(&set), 1);

        let mut requests = Vec::new();
        H5ESget_requests_into(&set, &mut requests);
        assert_eq!(requests, ["read"]);
    }

    #[test]
    fn h5es_error_status_and_count_use_public_output_parameters() {
        let mut set = H5EScreate();

        let mut err_occurred = true;
        H5ESget_err_status_into(&set, &mut err_occurred).unwrap();
        assert!(!err_occurred);

        let mut err_count = usize::MAX;
        H5ESget_err_count_into(&set, &mut err_count).unwrap();
        assert_eq!(err_count, 0);

        H5ESfail_request(&mut set, "write", "disk");
        H5EScomplete_request(&mut set, "flush");
        H5ESfail_request(&mut set, "close", "metadata");

        H5ESget_err_status_into(&set, &mut err_occurred).unwrap();
        assert!(err_occurred);
        H5ESget_err_count_into(&set, &mut err_count).unwrap();
        assert_eq!(err_count, 2);

        H5ES__close_failed_cb(&mut set);
        H5ESget_err_status_into(&set, &mut err_occurred).unwrap();
        assert!(!err_occurred);
        H5ESget_err_count_into(&set, &mut err_count).unwrap();
        assert_eq!(err_count, 0);
    }

    #[test]
    fn h5es_public_constants_and_statuses_match_libhdf5_names() {
        assert_eq!(H5ES_NONE, 0);
        assert_eq!(H5ES_WAIT_NONE, 0);
        assert_eq!(H5ES_WAIT_FOREVER, u64::MAX);

        assert_eq!(
            H5ES_status_t::H5ES_STATUS_IN_PROGRESS,
            H5ES_status_t::H5ES_STATUS_IN_PROGRESS
        );
        assert_eq!(H5ES_status_t::H5ES_STATUS_IN_PROGRESS as i32, 0);
        assert_eq!(H5ES_status_t::H5ES_STATUS_SUCCEED as i32, 1);
        assert_eq!(H5ES_status_t::H5ES_STATUS_CANCELED as i32, 2);
        assert_eq!(H5ES_status_t::H5ES_STATUS_FAIL as i32, 3);
        assert_ne!(
            H5ES_status_t::H5ES_STATUS_SUCCEED,
            H5ES_status_t::H5ES_STATUS_FAIL
        );
        assert_ne!(
            H5ES_status_t::H5ES_STATUS_CANCELED,
            H5ES_status_t::H5ES_STATUS_FAIL
        );
    }

    #[test]
    fn h5es_error_info_records_are_explicit_unsupported_boundary() {
        let mut set = H5EScreate();
        H5ESfail_request(&mut set, "close", "failed");
        let mut records = vec![H5ES_err_info_t::default()];
        records[0].api_name = "sentinel".into();
        records[0].api_args = "args".into();
        records[0].app_file_name = "app.rs".into();
        records[0].app_func_name = "caller".into();
        records[0].app_line_num = 7;
        records[0].op_ins_count = 9;
        records[0].op_ins_ts = 11;
        records[0].op_exec_ts = 13;
        records[0].op_exec_time = 17;
        records[0].err_stack_id = 42;
        let sentinel = records[0].clone();
        let mut err_cleared = usize::MAX;

        let err =
            H5ESget_err_info(&mut set, records.len(), &mut records, &mut err_cleared).unwrap_err();
        assert_eq!(
            err.to_string(),
            "Unsupported: H5ESget_err_info requires libhdf5 asynchronous VOL event-set behavior not implemented in pure-Rust mode"
        );
        assert_eq!(records[0], sentinel);
        assert_eq!(err_cleared, usize::MAX);

        let err = H5ES__get_err_info(&mut set, records.len(), &mut records, &mut err_cleared)
            .unwrap_err();
        assert_eq!(
            err.to_string(),
            "Unsupported: H5ES__get_err_info requires libhdf5 asynchronous VOL event-set behavior not implemented in pure-Rust mode"
        );
        assert_eq!(records[0], sentinel);
        assert_eq!(err_cleared, usize::MAX);

        H5ESfree_err_info(records.len(), &mut records).unwrap();

        assert_eq!(records, [H5ES_err_info_t::default()]);
    }

    #[test]
    fn h5es_free_err_info_uses_public_count_parameter() {
        let mut records = vec![H5ES_err_info_t::default(); 2];
        records[0].api_name = "H5Dwrite_async".into();
        records[0].api_args = "first".into();
        records[0].err_stack_id = 1;
        records[1].api_name = "H5Dflush_async".into();
        records[1].api_args = "second".into();
        records[1].err_stack_id = 2;
        let second = records[1].clone();

        H5ESfree_err_info(1, &mut records).unwrap();

        assert_eq!(records[0], H5ES_err_info_t::default());
        assert_eq!(records[1], second);
        let before_error = records.clone();
        assert!(matches!(
            H5ESfree_err_info(3, &mut records),
            Err(Error::Other(_))
        ));
        assert_eq!(records, before_error);
    }

    #[test]
    fn h5es_callback_registration_is_explicit_unsupported_boundary() {
        unsafe extern "C" fn insert_cb(_op_info: *const H5ES_op_info_t, _ctx: *mut c_void) -> i32 {
            0
        }

        unsafe extern "C" fn complete_cb(
            _op_info: *mut H5ES_op_info_t,
            _status: H5ES_status_t,
            _err_stack_id: u64,
            _ctx: *mut c_void,
        ) -> i32 {
            0
        }

        let mut set = H5EScreate();
        H5ESinsert_request(&mut set, "read");

        let mut requests = Vec::new();
        H5ESget_requests_into(&set, &mut requests);
        assert_eq!(requests, ["read"]);

        assert!(matches!(
            H5ESregister_insert_func(&mut set, Some(insert_cb), ptr::null_mut()),
            Err(Error::Unsupported(_))
        ));
        assert!(!H5EShas_insert_func(&set));
        assert_eq!(H5ESget_count(&set), 1);
        assert_eq!(H5ESget_op_counter(&set), 1);
        H5ESget_requests_into(&set, &mut requests);
        assert_eq!(requests, ["read"]);

        assert!(matches!(
            H5ESregister_complete_func(&mut set, Some(complete_cb), ptr::null_mut()),
            Err(Error::Unsupported(_))
        ));
        assert!(!H5EShas_complete_func(&set));
        assert_eq!(H5ESget_count(&set), 1);
        assert_eq!(H5ESget_op_counter(&set), 1);
        H5ESget_requests_into(&set, &mut requests);
        assert_eq!(requests, ["read"]);
    }
}
