//! `wasmeld:kv/store@0.1.0` host implementation and backend boundary.

use std::{fmt, sync::Arc, time::Duration};

use thiserror::Error;
use wasmtime::component::Linker;

use crate::{RuntimeError, runtime::HostState};

wasmtime::component::bindgen!({
    path: "../../wit/kv",
    world: "kv-host",
});

pub(crate) const INTERFACE: &str = "wasmeld:kv/store@0.1.0";
pub(crate) const PACKAGE: &str = "wasmeld:kv";
pub(crate) const NAME: &str = "store";
pub(crate) const VERSION: &str = "0.1.0";

/// Maximum UTF-8 byte length accepted for one KV key.
pub const KV_MAX_KEY_BYTES: usize = 256;
/// Maximum byte length accepted for one KV value.
pub const KV_MAX_VALUE_BYTES: usize = 64 * 1024;

/// Synchronous storage boundary used by the Wasmtime host implementation.
///
/// Implementations must bound their own blocking time because calls execute on
/// the Component Actor thread.
pub trait KvBackend: fmt::Debug + Send + Sync {
    /// Returns the value stored under a service-scoped key.
    fn get(
        &self,
        service_id: &str,
        key: &str,
        timeout: Duration,
    ) -> Result<Option<Vec<u8>>, KvBackendError>;
    /// Creates or replaces a service-scoped value.
    fn set(
        &self,
        service_id: &str,
        key: &str,
        value: &[u8],
        timeout: Duration,
    ) -> Result<(), KvBackendError>;
    /// Removes a service-scoped value if it exists.
    fn delete(&self, service_id: &str, key: &str, timeout: Duration) -> Result<(), KvBackendError>;
}

/// Storage failures returned by a [`KvBackend`].
#[derive(Debug, Error)]
pub enum KvBackendError {
    /// Backend queue or connection pool is temporarily saturated.
    #[error("KV backend is busy")]
    Busy,
    /// Backend did not finish within the Host-call timeout.
    #[error("KV backend timed out")]
    Timeout,
    /// Backend rejected or failed the operation for another reason.
    #[error("KV backend operation failed: {0}")]
    Operation(String),
}

pub(crate) struct KvState {
    service_id: String,
    backend: Arc<dyn KvBackend>,
    timeout: Duration,
}

impl KvState {
    pub(crate) fn new(service_id: &str, backend: Arc<dyn KvBackend>, timeout: Duration) -> Self {
        Self {
            service_id: service_id.to_owned(),
            backend,
            timeout,
        }
    }

    fn get(&self, key: &str) -> Result<Option<Vec<u8>>, wasmeld::kv::store::KvError> {
        validate_key(key)?;
        self.backend
            .get(&self.service_id, key, self.timeout)
            .map_err(operation_failed)
            .and_then(|value| match value {
                Some(value) if value.len() > KV_MAX_VALUE_BYTES => {
                    Err(wasmeld::kv::store::KvError::ValueTooLarge(
                        u64::try_from(value.len()).unwrap_or(u64::MAX),
                    ))
                }
                value => Ok(value),
            })
    }

    fn set(&self, key: &str, value: &[u8]) -> Result<(), wasmeld::kv::store::KvError> {
        validate_key(key)?;
        if value.len() > KV_MAX_VALUE_BYTES {
            return Err(wasmeld::kv::store::KvError::ValueTooLarge(
                u64::try_from(value.len()).unwrap_or(u64::MAX),
            ));
        }
        self.backend
            .set(&self.service_id, key, value, self.timeout)
            .map_err(operation_failed)
    }

    fn delete(&self, key: &str) -> Result<(), wasmeld::kv::store::KvError> {
        validate_key(key)?;
        self.backend
            .delete(&self.service_id, key, self.timeout)
            .map_err(operation_failed)
    }
}

impl wasmeld::kv::store::Host for HostState {
    fn get(&mut self, key: String) -> Result<Option<Vec<u8>>, wasmeld::kv::store::KvError> {
        self.capabilities.kv().get(&key)
    }

    fn set(&mut self, key: String, value: Vec<u8>) -> Result<(), wasmeld::kv::store::KvError> {
        self.capabilities.kv().set(&key, &value)
    }

    fn delete(&mut self, key: String) -> Result<(), wasmeld::kv::store::KvError> {
        self.capabilities.kv().delete(&key)
    }
}

pub(crate) fn add_to_linker(linker: &mut Linker<HostState>) -> Result<(), RuntimeError> {
    wasmeld::kv::store::add_to_linker::<HostState, HostState>(linker, |state| state)
        .map_err(|error| RuntimeError::ActorInitialization(error.to_string()))
}

fn validate_key(key: &str) -> Result<(), wasmeld::kv::store::KvError> {
    if key.is_empty() {
        return Err(wasmeld::kv::store::KvError::InvalidKey(
            "key must not be empty".to_owned(),
        ));
    }
    if key.len() > KV_MAX_KEY_BYTES {
        return Err(wasmeld::kv::store::KvError::InvalidKey(format!(
            "key exceeds the {KV_MAX_KEY_BYTES}-byte limit"
        )));
    }
    Ok(())
}

fn operation_failed(error: KvBackendError) -> wasmeld::kv::store::KvError {
    wasmeld::kv::store::KvError::OperationFailed(error.to_string())
}
