// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use crate::{Action, ActionResult};

use ordinary_config::{ActionAccessModelOps, ActionFfiSerialization};
use ordinary_utils::wasm::{read_mem, write_mem};

use anyhow::bail;
use bytes::{Bytes, BytesMut};
use http::StatusCode;
use ordinary_auth::{validate_account, validate_domain};
use ordinary_storage::RefDepth;
use parking_lot::Mutex;
use std::sync::Arc;
use tracing::Span;
use wasi_common::{
    WasiCtx,
    sync::{WasiCtxBuilder, add_to_linker},
};
use wasmtime::{Caller, Linker, Memory, MemoryType, Store};

pub enum TxCall {
    Insert(u8, Bytes),
    Update(u8, [u8; 16], Bytes),
    Delete(u8, [u8; 16]),
    Get(u8, u8, Bytes, RefDepth),
    Query(u8, u8, u8, Bytes, RefDepth),
    Search(u8, u8, Bytes, RefDepth),
}

#[allow(clippy::missing_panics_doc, clippy::too_many_lines)]
pub fn call(
    action: &Action,
    args: &[u8],
    actions: &Arc<Vec<Action>>,
) -> anyhow::Result<ActionResult> {
    let mut linker = Linker::new(&action.engine);
    add_to_linker(&mut linker, |s| s)?;

    let wasi = WasiCtxBuilder::new().build();

    let mut store = Store::new(&action.engine, wasi);

    let memory_ty = MemoryType::new(1, None);
    Memory::new(&mut store, memory_ty)?;

    let trace_span = tracing::info_span!("trc");

    let privileged_span =
        if action.config.privileged == Some(true) && action.privileged_components.is_some() {
            Some(tracing::info_span!("privileged"))
        } else {
            None
        };

    let auth_span = if action.can_auth_set_token_fields {
        Some(tracing::info_span!("auth"))
    } else {
        None
    };
    let storage_span = if action.can_model_delete
        || action.can_model_get
        || action.can_model_insert
        || action.can_model_query
        || action.can_model_search
        || action.can_model_update
        || action.has_content_defs
    {
        Some(tracing::info_span!("storage"))
    } else {
        None
    };
    let integration_span = if action.has_integrations {
        Some(Span::current())
    } else {
        None
    };
    let action_span = if action.has_actions {
        Some(Span::current())
    } else {
        None
    };

    let args = match action.config.ffi.serialization {
        ActionFfiSerialization::FlexBufferVector => Bytes::copy_from_slice(args),
    };

    linker.func_wrap(
        "env",
        "host_get_input",
        move |mut caller: Caller<'_, WasiCtx>| -> i64 {
            if let Some(res) = write_mem(&mut caller, args.as_ref()) {
                return res;
            }

            0
        },
    )?;

    let out = Arc::new(Mutex::new(Bytes::new()));
    let out_clone = Arc::clone(&out);

    linker.func_wrap(
        "env",
        "host_set_output",
        move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i32 {
            let mut buf = BytesMut::zeroed(len as usize);

            if read_mem(&mut caller, ptr, &mut buf).is_ok() {
                let buf: Bytes = match action.config.ffi.serialization {
                    ActionFfiSerialization::FlexBufferVector => buf.into(),
                };

                let mut out = out_clone.lock();
                *out = buf;

                drop(out);

                return 1;
            }

            0
        },
    )?;

    linker.func_wrap(
        "env",
        "host_trace",
        move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i32 {
            let mut payload = BytesMut::zeroed(len as usize);

            if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                let payload: Bytes = match action.config.ffi.serialization {
                    ActionFfiSerialization::FlexBufferVector => payload.into(),
                };

                match flexbuffers::Reader::get_root(payload.as_ref()) {
                    Ok(root) => {
                        return trace_span.in_scope(|| {
                            let level = root.as_vector().idx(0).as_u8();
                            let content = root.as_vector().idx(1).as_str();

                            match level {
                                0 => tracing::trace!("{:?}", content),
                                1 => tracing::debug!("{:?}", content),
                                2 => tracing::info!("{:?}", content),
                                3 => tracing::warn!("{:?}", content),
                                4 => tracing::error!("{:?}", content),
                                _ => (),
                            }

                            1
                        });
                    }
                    Err(err) => tracing::error!(%err),
                }
            }

            0
        },
    )?;

    if action.config.privileged == Some(true)
        && let Some(components) = &action.privileged_components
    {
        let privileged_auth = components.auth.clone();
        let apps_dir = components.apps_dir.clone();
        let app_domains = components.app_domains.clone();

        let privileged_span = privileged_span.expect("span not declared");

        if privileged_auth.config.invite.is_some() {
            linker.func_wrap(
                "env",
                "host_privileged_api_invite",
                move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                    let mut payload = BytesMut::zeroed(len as usize);

                    if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                        let src_claims: Bytes = match action.config.ffi.serialization {
                            ActionFfiSerialization::FlexBufferVector => payload.into(),
                        };

                        let claims = if src_claims.is_empty() {
                            None
                        } else if let Ok(src_claims) =
                            flexbuffers::Reader::get_root(src_claims.as_ref())
                        {
                            let src_claims = src_claims.as_vector();

                            let mut builder =
                                flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
                            let mut claims_builder = builder.start_vector();

                            // gap for system claims
                            let system_claims_gap = claims_builder.start_vector();
                            system_claims_gap.end_vector();

                            if let Some(invite_config) = &privileged_auth.config.invite
                                && let Some(claims) = &invite_config.claims
                            {
                                for claim in claims {
                                    if claim.name == "account" {
                                        let invited_account = src_claims.idx(claim.idx as usize).as_str();

                                        let Ok(invited_account) = validate_account(invited_account)
                                        else {
                                            privileged_span.in_scope(|| {
                                                tracing::error!(account = %invited_account, "invalid");
                                            });
                                            return 0;
                                        };

                                        match privileged_auth.account_exists(&invited_account) {
                                            Ok(exists) => {
                                                if exists {
                                                    tracing::warn!(account = %invited_account, "exists");
                                                    return 0;
                                                }
                                            }
                                            Err(err) => {
                                                tracing::error!(%err);
                                                return 0;
                                            }
                                        }

                                        claims_builder.push(invited_account.as_str());
                                    } else if claim.name == "domain" {
                                        let invited_domain = src_claims.idx(claim.idx as usize).as_str();

                                        if !validate_domain(&app_domains, invited_domain) {
                                            privileged_span.in_scope(|| {
                                                tracing::error!(domain = %invited_domain, "invalid");
                                            });
                                            return 0;
                                        }

                                        let domain_exists = apps_dir
                                            .join(invited_domain)
                                            .exists();
                                        if domain_exists {
                                            tracing::warn!(domain = %invited_domain, "exists");
                                            return 0;
                                        }

                                        claims_builder.push(invited_domain);
                                    } else if let Err(err) = claim.kind.copy_to(
                                        &src_claims.idx(claim.idx as usize),
                                        &mut claims_builder,
                                        None,
                                    ) {
                                        privileged_span.in_scope(|| tracing::error!(%err));
                                        return 0;
                                    }
                                }
                            }

                            claims_builder.end_vector();

                            Some(Bytes::copy_from_slice(builder.view()))
                        } else {
                            None
                        };

                        return privileged_span.in_scope(|| {
                            match privileged_auth.api_invite_get(
                                &privileged_auth.domain,
                                "root",
                                claims,
                            ) {
                                Ok(token) => {
                                    let token = match action.config.ffi.serialization {
                                        ActionFfiSerialization::FlexBufferVector => token,
                                    };

                                    if let Some(res) = write_mem(&mut caller, token.as_ref()) {
                                        return res;
                                    }
                                }
                                Err(err) => tracing::error!(%err),
                            }

                            0
                        });
                    }

                    0
                },
            )?;
        }
    }

    if action.can_auth_set_token_fields {
        let auth = action.auth.clone();
        let auth_span = auth_span.expect("span not declared");

        linker.func_wrap(
            "env",
            "host_auth_set_claims",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i32 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let account = root.as_vector().idx(0).as_str().as_bytes();
                            let src_claims = root.as_vector().idx(1).as_vector();

                            let mut builder =
                                flexbuffers::Builder::new(&flexbuffers::BuilderOptions::SHARE_NONE);
                            let mut claims_builder = builder.start_vector();

                            // gap for system claims
                            let system_claims_gap = claims_builder.start_vector();
                            system_claims_gap.end_vector();

                            for claim in &auth.config.access_token.claims {
                                if let Err(err) = claim.kind.copy_to(
                                    &src_claims.idx(claim.idx as usize - 1),
                                    &mut claims_builder,
                                    None,
                                ) {
                                    tracing::error!(%err);
                                    return 0;
                                }
                            }

                            claims_builder.end_vector();

                            return auth_span.in_scope(|| {
                                match auth.set_claims(account, builder.view()) {
                                    Ok(()) => 1,
                                    Err(err) => {
                                        tracing::error!(%err);
                                        0
                                    }
                                }
                            });
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;
    }

    if action.has_content_defs {
        let storage = action.storage.clone();
        let allowed_content_defs = action.allowed_content_defs.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_content_get",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let root_vec = root.as_vector();

                            let def_idx = root_vec.idx(0).as_u8();

                            if allowed_content_defs.contains(&def_idx) {
                                let key_or_idx_value = root_vec.idx(1).as_blob().0;

                                let field_idx = root_vec.idx(2).as_u8();

                                return storage_span_clone.in_scope(|| {
                                    let content_span = tracing::info_span!("content");

                                    content_span.in_scope(|| {
                                        if let Ok(val) = storage.content.get(
                                            def_idx,
                                            field_idx,
                                            key_or_idx_value,
                                        ) {
                                            let val = match action.config.ffi.serialization {
                                                ActionFfiSerialization::FlexBufferVector => val,
                                            };

                                            if let Some(res) = write_mem(&mut caller, val.as_ref())
                                            {
                                                return res;
                                            }
                                        }

                                        0
                                    })
                                });
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;

        let storage = action.storage.clone();
        let allowed_content_defs = action.allowed_content_defs.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_content_list",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let def_idx = root.as_vector().idx(0).as_u8();

                            if allowed_content_defs.contains(&def_idx) {
                                return storage_span_clone.in_scope(|| {
                                    let content_span = tracing::info_span!("content");

                                    content_span.in_scope(|| {
                                        if let Ok(val) = storage.content.list(def_idx) {
                                            let val = match action.config.ffi.serialization {
                                                ActionFfiSerialization::FlexBufferVector => val,
                                            };

                                            if let Some(res) = write_mem(&mut caller, val.as_ref())
                                            {
                                                return res;
                                            }
                                        }

                                        0
                                    })
                                });
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;

        let storage = action.storage.clone();
        let allowed_content_defs = action.allowed_content_defs.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_content_search",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let root_vec = root.as_vector();

                            let def_idx = root_vec.idx(0).as_u8();
                            let field_idx = root_vec.idx(1).as_u8();

                            if allowed_content_defs.contains(&def_idx) {
                                let value = root_vec.idx(2).as_blob().0;

                                return storage_span_clone.in_scope(|| {
                                    if let Ok(val) =
                                        storage.content.search_content(def_idx, field_idx, value)
                                    {
                                        let val = match action.config.ffi.serialization {
                                            ActionFfiSerialization::FlexBufferVector => val,
                                        };

                                        if let Some(res) = write_mem(&mut caller, val.as_ref()) {
                                            return res;
                                        }
                                    }

                                    0
                                });
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;
    }

    let call_tx = if action.config.transactional == Some(true) {
        let (tx, rx) = std::sync::mpsc::channel::<(TxCall, oneshot::Sender<Option<Bytes>>)>();
        let storage = action.storage.clone();

        let txn_span = storage_span
            .clone()
            .expect("span not declared")
            .in_scope(|| tracing::info_span!("txn"));

        std::thread::spawn(move || {
            txn_span.in_scope(|| match storage.write_txn() {
                Ok(mut txn) => {
                    while let Ok((call, send)) = rx.recv() {
                        if let Ok(child_txn) = txn.child_tx() {
                            let result = match call {
                                TxCall::Insert(idx, payload) => {
                                    if let Ok(res) = storage.model.insert_model(
                                        idx,
                                        &payload[..],
                                        Some(child_txn),
                                    ) {
                                        send.send(Some(Bytes::copy_from_slice(&res[..])))
                                    } else {
                                        send.send(None)
                                    }
                                }
                                TxCall::Update(idx, uuid, payload) => {
                                    if let Ok(()) = storage.model.update_model(
                                        idx,
                                        uuid,
                                        &payload[..],
                                        Some(child_txn),
                                    ) {
                                        send.send(Some(Bytes::new()))
                                    } else {
                                        send.send(None)
                                    }
                                }
                                TxCall::Delete(idx, payload) => {
                                    if let Ok(()) =
                                        storage.model.delete_model(idx, payload, Some(child_txn))
                                    {
                                        send.send(Some(Bytes::new()))
                                    } else {
                                        send.send(None)
                                    }
                                }
                                TxCall::Get(model, field, payload, ref_depth) => {
                                    if let Ok(res) = storage.model.get_model(
                                        model,
                                        field,
                                        &payload[..],
                                        &ref_depth,
                                        Some(child_txn),
                                    ) {
                                        send.send(Some(res))
                                    } else {
                                        send.send(None)
                                    }
                                }
                                TxCall::Query(model, field, expression, payload, ref_depth) => {
                                    if let Ok(res) = storage.model.query_model(
                                        model,
                                        field,
                                        expression,
                                        &payload[..],
                                        &ref_depth,
                                        Some(child_txn),
                                    ) {
                                        send.send(Some(res))
                                    } else {
                                        send.send(None)
                                    }
                                }
                                TxCall::Search(model, field, payload, ref_depth) => {
                                    if let Ok(res) = storage.model.search_model(
                                        model,
                                        field,
                                        &payload[..],
                                        &ref_depth,
                                        Some(child_txn),
                                    ) {
                                        send.send(Some(res))
                                    } else {
                                        send.send(None)
                                    }
                                }
                            };

                            if let Err(err) = result {
                                tracing::error!(%err);
                            }
                        }
                    }

                    if let Err(err) = txn.commit() {
                        tracing::error!(%err);
                    }
                }
                Err(err) => {
                    tracing::error!(%err);
                }
            });
        });

        Arc::new(Some(tx))
    } else {
        Arc::new(None)
    };

    if action.can_model_insert {
        let storage = action.storage.clone();
        let allowed_model_ops = action.allowed_model_ops.clone();
        let call_tx = call_tx.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_model_insert",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };
                            match flexbuffers::Reader::get_root(payload.as_ref()) {
                                Ok(root) => {
                                    let root_vec = root.as_vector();

                                    let model_idx = root_vec.idx(0).as_u8();

                                    if let Some((ops, fields)) = allowed_model_ops.get(&model_idx) {
                                        for op in ops {
                                            if let ActionAccessModelOps::Insert = op {
                                                let payload = root_vec.idx(1).as_vector();

                                                let mut builder = flexbuffers::Builder::new(
                                                    &flexbuffers::BuilderOptions::SHARE_NONE,
                                                );
                                                let mut item = builder.start_vector();

                                                for field in fields {
                                                    if field.idx != 0
                                                        && let Err(err) = field.kind.copy_to(
                                                        &payload.idx(field.idx as usize - 1),
                                                        &mut item,
                                                        None,
                                                    )
                                                    {
                                                        tracing::error!(%err);
                                                        return 0;
                                                    }
                                                }

                                                item.end_vector();

                                                return storage_span_clone.in_scope(|| {
                                                    if let Some(call_tx) = &*call_tx {
                                                        let (tx, rx) = oneshot::channel();

                                                        if let Err(err) = call_tx.send((
                                                            TxCall::Insert(
                                                                model_idx,
                                                                Bytes::copy_from_slice(
                                                                    builder.view(),
                                                                ),
                                                            ),
                                                            tx,
                                                        )) {
                                                            tracing::error!(%err);
                                                        }

                                                        match rx.recv() {
                                                            Ok(val) => match val {
                                                                Some(val) => {
                                                                    let val = match action.config.ffi.serialization {
                                                                        ActionFfiSerialization::FlexBufferVector => val,
                                                                    };

                                                                    if let Some(res) = write_mem(
                                                                        &mut caller,
                                                                        &val[0..16],
                                                                    ) {
                                                                        return res;
                                                                    }
                                                                }
                                                                None => {
                                                                    return 0;
                                                                }
                                                            },
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    } else {
                                                        match storage.model.insert_model(
                                                            model_idx,
                                                            builder.view(),
                                                            None,
                                                        ) {
                                                            Ok(val) => {
                                                                let val = match action.config.ffi.serialization {
                                                                    ActionFfiSerialization::FlexBufferVector => val,
                                                                };

                                                                if let Some(res) =
                                                                    write_mem(&mut caller, val.as_ref())
                                                                {
                                                                    return res;
                                                                }
                                                            }
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    }

                                                    0
                                                });
                                            }
                                        }
                                    }
                                }
                                Err(err) => tracing::error!(%err),
                            }

                }

                0
            },
        )?;
    }

    if action.can_model_update {
        let storage = action.storage.clone();
        let allowed_model_ops = action.allowed_model_ops.clone();
        let call_tx = call_tx.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_model_update",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i32 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let root_vec = root.as_vector();

                            let model_idx = root_vec.idx(0).as_u8();

                            if let Some((ops, fields)) = allowed_model_ops.get(&model_idx) {
                                for op in ops {
                                    if let ActionAccessModelOps::Update = op {
                                        let payload = root_vec.idx(1).as_vector();

                                        let mut builder = flexbuffers::Builder::new(
                                            &flexbuffers::BuilderOptions::SHARE_NONE,
                                        );
                                        let mut item = builder.start_vector();

                                        for field in fields {
                                            if field.idx != 0
                                                && let Err(err) = field.kind.copy_to(
                                                    &payload.idx(field.idx as usize - 1),
                                                    &mut item,
                                                    None,
                                                )
                                            {
                                                tracing::error!(%err);
                                                return 0;
                                            }
                                        }

                                        item.end_vector();

                                        let uuid = root_vec
                                            .idx(1)
                                            .as_blob()
                                            .0
                                            .try_into()
                                            .unwrap_or([0u8; 16]);

                                        return storage_span_clone.in_scope(|| {
                                            if let Some(call_tx) = &*call_tx {
                                                let (tx, rx) = oneshot::channel();

                                                if let Err(err) = call_tx.send((
                                                    TxCall::Update(
                                                        model_idx,
                                                        uuid,
                                                        Bytes::copy_from_slice(builder.view()),
                                                    ),
                                                    tx,
                                                )) {
                                                    tracing::error!(%err);
                                                }

                                                match rx.recv() {
                                                    Ok(_) => {
                                                        return 1;
                                                    }
                                                    Err(err) => tracing::error!(%err),
                                                }
                                            } else {
                                                match storage.model.update_model(
                                                    model_idx,
                                                    uuid,
                                                    builder.view(),
                                                    None,
                                                ) {
                                                    Ok(()) => {
                                                        return 1;
                                                    }
                                                    Err(err) => tracing::error!(%err),
                                                }
                                            }
                                            0
                                        });
                                    }
                                }
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;
    }

    if action.can_model_delete {
        let storage = action.storage.clone();
        let allowed_model_ops = action.allowed_model_ops.clone();
        let call_tx = call_tx.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_model_delete",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i32 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let root_vec = root.as_vector();

                            let model_idx = root_vec.idx(0).as_u8();

                            if let Some((ops, _)) = allowed_model_ops.get(&model_idx) {
                                for op in ops {
                                    if let ActionAccessModelOps::Delete = op {
                                        let uuid = root_vec
                                            .idx(1)
                                            .as_blob()
                                            .0
                                            .try_into()
                                            .unwrap_or([0u8; 16]);

                                        return storage_span_clone.in_scope(|| {
                                            if let Some(call_tx) = &*call_tx {
                                                let (tx, rx) = oneshot::channel();

                                                if let Err(err) = call_tx
                                                    .send((TxCall::Delete(model_idx, uuid), tx))
                                                {
                                                    tracing::error!(%err);
                                                }

                                                match rx.recv() {
                                                    Ok(_) => {
                                                        return 1;
                                                    }
                                                    Err(err) => tracing::error!(%err),
                                                }
                                            } else {
                                                match storage
                                                    .model
                                                    .delete_model(model_idx, uuid, None)
                                                {
                                                    Ok(()) => {
                                                        return 1;
                                                    }
                                                    Err(err) => tracing::error!(%err),
                                                }
                                            }
                                            0
                                        });
                                    }
                                }
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;
    }

    if action.can_model_get {
        let storage = action.storage.clone();
        let allowed_model_ops = action.allowed_model_ops.clone();
        let call_tx = call_tx.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_model_get",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                            match flexbuffers::Reader::get_root(payload.as_ref()) {
                                Ok(root) => {
                                    let root_vec = root.as_vector();

                                    let model_idx = root_vec.idx(0).as_u8();

                                    if let Some((ops, fields)) = allowed_model_ops.get(&model_idx) {
                                        for op in ops {
                                            if let ActionAccessModelOps::Get = op {
                                                let field_idx = root_vec.idx(1).as_u8();

                                                if let Some(idx_f) = fields.get(field_idx as usize)
                                                {
                                                    if idx_f.indexed != Some(true) {
                                                        tracing::error!(
                                                            i = field_idx,
                                                            "field is not indexed"
                                                        );
                                                        return 0;
                                                    }
                                                } else {
                                                    tracing::error!(
                                                        i = field_idx,
                                                        "field out of range"
                                                    );
                                                    return 0;
                                                }

                                                let key_or_idx_value = root_vec.idx(2).as_blob().0;

                                                return storage_span_clone.in_scope(|| {
                                                    if let Some(call_tx) = &*call_tx {
                                                        let (tx, rx) = oneshot::channel();

                                                        if let Err(err) = call_tx.send((
                                                            TxCall::Get(
                                                                model_idx,
                                                                field_idx,
                                                                Bytes::copy_from_slice(
                                                                    key_or_idx_value,
                                                                ),
                                                                RefDepth(vec![]), // todo: specify real depth
                                                            ),
                                                            tx,
                                                        )) {
                                                            tracing::error!(%err);
                                                        }

                                                        match rx.recv() {
                                                            Ok(val) => match val {
                                                                Some(val) => {
                                                                    let val = match action.config.ffi.serialization {
                                                                        ActionFfiSerialization::FlexBufferVector => val,
                                                                    };

                                                                    if let Some(res) =
                                                                        write_mem(&mut caller, val.as_ref())
                                                                    {
                                                                        return res;
                                                                    }

                                                                }
                                                                None => {
                                                                    return 0;
                                                                }
                                                            },
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    } else {
                                                        match storage.model.get_model(
                                                            model_idx,
                                                            field_idx,
                                                            key_or_idx_value,
                                                            &RefDepth(vec![]), // todo: specify real depth
                                                            None,
                                                        ) {
                                                            Ok(val) => {
                                                                let val = match action.config.ffi.serialization {
                                                                    ActionFfiSerialization::FlexBufferVector => val,
                                                                };

                                                                if let Some(res) =
                                                                    write_mem(&mut caller, val.as_ref())
                                                                {
                                                                    return res;
                                                                }
                                                            }
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    }
                                                    0
                                                });
                                            }
                                        }
                                    }
                                }
                                Err(err) => tracing::error!(%err),
                            }

                }

                0
            },
        )?;
    }

    if action.can_model_query {
        let storage = action.storage.clone();
        let allowed_model_ops = action.allowed_model_ops.clone();
        let call_tx = call_tx.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_model_query",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                            match flexbuffers::Reader::get_root(payload.as_ref()) {
                                Ok(root) => {
                                    let root_vec = root.as_vector();

                                    let model_idx = root_vec.idx(0).as_u8();

                                    if let Some((ops, fields)) = allowed_model_ops.get(&model_idx) {
                                        for op in ops {
                                            if let ActionAccessModelOps::Query = op {
                                                let queryable_field = root_vec.idx(1).as_u8();

                                                if let Some(qf) = fields.get(queryable_field as usize) {
                                                    if qf.queryable != Some(true) {
                                                        tracing::error!(
                                                    i = queryable_field,
                                                    "field is not queryable"
                                                );
                                                        return 0;
                                                    }
                                                } else {
                                                    tracing::error!(
                                                i = queryable_field,
                                                "field out of range"
                                            );
                                                    return 0;
                                                }

                                                let expression = root_vec.idx(2).as_u8();

                                                let value = root_vec.idx(3).as_blob().0;

                                                return storage_span_clone.in_scope(|| {
                                                    if let Some(call_tx) = &*call_tx {
                                                        let (tx, rx) = oneshot::channel();

                                                        if let Err(err) = call_tx.send((
                                                            TxCall::Query(
                                                                model_idx,
                                                                queryable_field,
                                                                expression,
                                                                Bytes::copy_from_slice(value),
                                                                RefDepth(vec![]), // todo: specify real depth
                                                            ),
                                                            tx,
                                                        )) {
                                                            tracing::error!(%err);
                                                        }

                                                        match rx.recv() {
                                                            Ok(val) => match val {
                                                                Some(val) => {
                                                                    let val = match action.config.ffi.serialization {
                                                                        ActionFfiSerialization::FlexBufferVector => val,
                                                                    };

                                                                    if let Some(res) =
                                                                        write_mem(&mut caller, val.as_ref())
                                                                    {
                                                                        return res;
                                                                    }
                                                                }
                                                                None => {
                                                                    return 0;
                                                                }
                                                            },
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    } else {
                                                        match storage.model.query_model(
                                                            model_idx,
                                                            queryable_field,
                                                            expression,
                                                            value,
                                                            &RefDepth(vec![]), // todo: specify real depth
                                                            None,
                                                        ) {
                                                            Ok(val) => {
                                                                let val = match action.config.ffi.serialization {
                                                                    ActionFfiSerialization::FlexBufferVector => val,
                                                                };

                                                                if let Some(res) =
                                                                    write_mem(&mut caller, val.as_ref())
                                                                {
                                                                    return res;
                                                                }
                                                            }
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    }
                                                    0
                                                });
                                            }
                                        }
                                    }
                                }
                                Err(err) => tracing::error!(%err),
                            }

                }

                0
            },
        )?;
    }

    if action.can_model_search {
        let storage = action.storage.clone();
        let allowed_model_ops = action.allowed_model_ops.clone();
        let storage_span_clone = storage_span.clone().expect("span not declared");

        linker.func_wrap(
            "env",
            "host_model_search",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                            match flexbuffers::Reader::get_root(payload.as_ref()) {
                                Ok(root) => {
                                    let root_vec = root.as_vector();
                                    let model_idx = root_vec.idx(0).as_u8();

                                    if let Some((ops, fields)) = allowed_model_ops.get(&model_idx) {
                                        for op in ops {
                                            if let ActionAccessModelOps::Search = op {
                                                let searchable_field = root_vec.idx(1).as_u8();

                                                if let Some(sf) = fields.get(searchable_field as usize) {
                                                    if sf.searchable != Some(true) {
                                                        tracing::error!(
                                                    i = searchable_field,
                                                    "field is not searchable"
                                                );
                                                        return 0;
                                                    }
                                                } else {
                                                    tracing::error!(
                                                i = searchable_field,
                                                "field out of range"
                                            );
                                                    return 0;
                                                }

                                                let value = root_vec.idx(2).as_blob().0;

                                                return storage_span_clone.in_scope(|| {
                                                    if let Some(call_tx) = &*call_tx {
                                                        let (tx, rx) = oneshot::channel();

                                                        if let Err(err) = call_tx.send((
                                                            TxCall::Search(
                                                                model_idx,
                                                                searchable_field,
                                                                Bytes::copy_from_slice(value),
                                                                RefDepth(vec![]), // todo: specify real depth
                                                            ),
                                                            tx,
                                                        )) {
                                                            tracing::error!(%err);
                                                        }

                                                        match rx.recv() {
                                                            Ok(val) => match val {
                                                                Some(val) => {
                                                                    let val = match action.config.ffi.serialization {
                                                                        ActionFfiSerialization::FlexBufferVector => val,
                                                                    };

                                                                    if let Some(res) =
                                                                        write_mem(&mut caller, val.as_ref())
                                                                    {
                                                                        return res;
                                                                    }
                                                                }
                                                                None => {
                                                                    return 0;
                                                                }
                                                            },
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    } else {
                                                        match storage.model.search_model(
                                                            model_idx,
                                                            searchable_field,
                                                            value,
                                                            &RefDepth(vec![]), // todo: specify real depth
                                                            None,
                                                        ) {
                                                            Ok(val) => {
                                                                let val = match action.config.ffi.serialization {
                                                                    ActionFfiSerialization::FlexBufferVector => val,
                                                                };

                                                                if let Some(res) =
                                                                    write_mem(&mut caller, val.as_ref())
                                                                {
                                                                    return res;
                                                                }
                                                            }
                                                            Err(err) => tracing::error!(%err),
                                                        }
                                                    }
                                                    0
                                                });
                                            }
                                        }
                                    }
                                }
                                Err(err) => tracing::error!(%err),
                            }

                }

                0
            },
        )?;
    }

    if action.has_integrations {
        let integrations = action.integrations.clone();
        let allowed_integrations = action.allowed_integrations.clone();
        let integration_span = integration_span.expect("span not declared");

        linker.func_wrap(
            "env",
            "host_integration",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let root_vec = root.as_vector();

                            let idx = root_vec.idx(0).as_u8();

                            if let Some(send_kind) = allowed_integrations.get(&idx) {
                                if let Some(integration) = integrations.get(idx as usize) {
                                    let payload = root_vec.idx(1);

                                    let mut builder = flexbuffers::Builder::new(
                                        &flexbuffers::BuilderOptions::SHARE_NONE,
                                    );
                                    let mut item = builder.start_vector();

                                    if let Err(err) = send_kind.copy_to(&payload, &mut item, None) {
                                        tracing::error!(%err);
                                        return 0;
                                    }

                                    item.end_vector();

                                    return {
                                        match integration.invoke_blocking(
                                            builder.view(),
                                            integration_span.clone(),
                                        ) {
                                            Ok(val) => {
                                                let val = match action.config.ffi.serialization {
                                                    ActionFfiSerialization::FlexBufferVector => val,
                                                };

                                                if let Some(res) =
                                                    write_mem(&mut caller, val.as_ref())
                                                {
                                                    return res;
                                                }
                                            }
                                            Err(err) => tracing::error!(%err),
                                        }

                                        0
                                    };
                                }
                                tracing::error!("no integration at index {}", idx);
                            } else {
                                tracing::error!("attempted to access integration not in perms.");
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;
    }

    if action.has_actions {
        let actions = actions.clone();
        let allowed_actions = action.allowed_actions.clone();
        let action_span = action_span.expect("span not declared");

        linker.func_wrap(
            "env",
            "host_action",
            move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| -> i64 {
                let mut payload = BytesMut::zeroed(len as usize);

                if read_mem(&mut caller, ptr, &mut payload).is_ok() {
                    let payload: Bytes = match action.config.ffi.serialization {
                        ActionFfiSerialization::FlexBufferVector => payload.into(),
                    };

                    match flexbuffers::Reader::get_root(payload.as_ref()) {
                        Ok(root) => {
                            let root_vec = root.as_vector();

                            let idx = root_vec.idx(0).as_u8();

                            if allowed_actions.contains(&idx) {
                                if let Some(action) = actions.get(idx as usize) {
                                    let payload = root_vec.idx(1);

                                    let mut builder = flexbuffers::Builder::new(
                                        &flexbuffers::BuilderOptions::SHARE_NONE,
                                    );
                                    let mut accepts = builder.start_vector();

                                    if let Err(err) =
                                        action.config.accepts.copy_to(&payload, &mut accepts, None)
                                    {
                                        tracing::error!(%err);
                                        return 0;
                                    }

                                    accepts.end_vector();

                                    return action_span.in_scope(|| {
                                        match action.call(builder.view(), &actions) {
                                            Ok(val) => {
                                                let val = match action.config.ffi.serialization {
                                                    ActionFfiSerialization::FlexBufferVector => val,
                                                };

                                                match val {
                                                    ActionResult::Result(val) => {
                                                        if let Some(res) =
                                                            write_mem(&mut caller, val.as_ref())
                                                        {
                                                            return res;
                                                        }
                                                    }
                                                    ActionResult::StatusCode(code) => {
                                                        tracing::error!(%code);
                                                        return 0;
                                                    }
                                                }
                                            }
                                            Err(err) => tracing::error!(%err),
                                        }
                                        0
                                    });
                                }
                                tracing::error!("action does not exist at index {}", &idx);
                            } else {
                                tracing::error!(
                                    "attempted to access action at index {} not in perms",
                                    &idx
                                );
                            }
                        }
                        Err(err) => tracing::error!(%err),
                    }
                }

                0
            },
        )?;
    }

    let lock = action.module.read();

    if let Some(module) = &*lock {
        linker.module(&mut store, "", module)?;
    } else {
        return Ok(ActionResult::StatusCode(StatusCode::SERVICE_UNAVAILABLE));
    }

    if linker
        .get_default(&mut store, "")?
        .typed::<(), ()>(&store)?
        .call(&mut store, ())
        .is_err()
    {
        bail!("wasm call failed");
    }

    let out = out.lock();
    Ok(ActionResult::Result(out.clone()))
}
