use multiversx_chain_vm_executor::VMHooksEarlyExit;
use num_bigint::{BigInt, BigUint};
use num_traits::ToPrimitive;

use crate::host::vm_hooks::VMHooksContext;
use crate::host::vm_hooks::vh_early_exit::early_exit_vm_error;
use crate::vm_err_msg;

use crate::types::RawHandle;

use super::VMHooksHandler;

/// Interface to only be used by code generated by the macros.
/// The smart contract code doesn't have access to these methods directly.
impl<C: VMHooksContext> VMHooksHandler<C> {
    pub fn get_num_arguments(&mut self) -> Result<i32, VMHooksEarlyExit> {
        Ok(self.context.input_ref().args.len() as i32)
    }

    pub fn get_argument_len(&mut self, arg_index: i32) -> Result<usize, VMHooksEarlyExit> {
        let arg = self.context.input_ref().get_argument_vec_u8(arg_index);
        Ok(arg.len())
    }

    pub fn load_argument_managed_buffer(
        &mut self,
        arg_index: i32,
        dest: RawHandle,
    ) -> Result<(), VMHooksEarlyExit> {
        self.use_gas(
            self.gas_schedule()
                .managed_buffer_api_cost
                .m_buffer_set_bytes,
        )?;

        let arg_bytes = self.context.input_ref().get_argument_vec_u8(arg_index);
        self.context.m_types_lock().mb_set(dest, arg_bytes);

        Ok(())
    }

    pub fn load_argument_big_int_unsigned(
        &mut self,
        arg_index: i32,
        dest: RawHandle,
    ) -> Result<(), VMHooksEarlyExit> {
        self.use_gas(
            self.gas_schedule()
                .big_int_api_cost
                .big_int_set_unsigned_bytes,
        )?;

        let arg_bytes = self.context.input_ref().get_argument_vec_u8(arg_index);
        self.context
            .m_types_lock()
            .bi_set_unsigned_bytes(dest, &arg_bytes);

        Ok(())
    }

    pub fn load_argument_big_int_signed(
        &mut self,
        arg_index: i32,
        dest: RawHandle,
    ) -> Result<(), VMHooksEarlyExit> {
        self.use_gas(
            self.gas_schedule()
                .big_int_api_cost
                .big_int_set_signed_bytes,
        )?;

        let arg_bytes = self.context.input_ref().get_argument_vec_u8(arg_index);
        self.context
            .m_types_lock()
            .bi_set_signed_bytes(dest, &arg_bytes);

        Ok(())
    }

    pub fn get_argument_i64(&mut self, arg_index: i32) -> Result<i64, VMHooksEarlyExit> {
        // specific implementation provided, in order to simulate the VM error (status 10 instead of 4)
        let bytes = self.context.input_ref().get_argument_vec_u8(arg_index);
        let bi = BigInt::from_signed_bytes_be(&bytes);
        if let Some(v) = bi.to_i64() {
            Ok(v)
        } else {
            Err(early_exit_vm_error(vm_err_msg::ARG_OUT_OF_RANGE))
        }
    }

    pub fn get_argument_u64(&mut self, arg_index: i32) -> Result<u64, VMHooksEarlyExit> {
        // specific implementation provided, in order to simulate the VM error (status 10 instead of 4)
        let bytes = self.context.input_ref().get_argument_vec_u8(arg_index);
        let bu = BigUint::from_bytes_be(&bytes);
        if let Some(v) = bu.to_u64() {
            Ok(v)
        } else {
            Err(early_exit_vm_error(vm_err_msg::ARG_OUT_OF_RANGE))
        }
    }

    pub fn load_callback_closure_buffer(
        &mut self,
        dest: RawHandle,
    ) -> Result<(), VMHooksEarlyExit> {
        let has_closure_data = self
            .context
            .input_ref()
            .promise_callback_closure_data
            .is_some();

        if has_closure_data {
            self.use_gas(
                self.gas_schedule()
                    .managed_buffer_api_cost
                    .m_buffer_set_bytes,
            )?;

            let closure_data = self
                .context
                .input_ref()
                .promise_callback_closure_data
                .clone()
                .unwrap();

            self.context.m_types_lock().mb_set(dest, closure_data);
            Ok(())
        } else {
            Err(early_exit_vm_error(vm_err_msg::ERROR_NO_CALLBACK_CLOSURE))
        }
    }
}
