use std::collections::HashSet;

use crate::base::SomaAddress;
use crate::object::ObjectID;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct TransactionDenyConfig {
    /// A list of object IDs that are not allowed to be accessed/used in transactions.
    /// Note that since this is checked during transaction signing, only root object ids
    /// are supported here (i.e. no child-objects).
    /// Similarly this does not apply to wrapped objects as they are not directly accessible.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    object_deny_list: Vec<ObjectID>,

    /// A list of addresses that are not allowed to be used as the sender.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    address_deny_list: Vec<SomaAddress>,

    /// Whether usage of shared objects is disabled.
    #[serde(default)]
    shared_object_disabled: bool,

    /// Whether user transactions are disabled (i.e. only system transactions are allowed).
    /// This is essentially a kill switch for transactions processing to a degree.
    #[serde(default)]
    user_transaction_disabled: bool,

    /// In-memory maps for faster lookup of various lists.
    #[serde(skip)]
    object_deny_set: OnceCell<HashSet<ObjectID>>,

    #[serde(skip)]
    address_deny_set: OnceCell<HashSet<SomaAddress>>,

    /// Whether receiving objects transferred to other objects is allowed
    #[serde(default)]
    receiving_objects_disabled: bool,
}

impl TransactionDenyConfig {
    pub fn get_object_deny_set(&self) -> &HashSet<ObjectID> {
        self.object_deny_set.get_or_init(|| self.object_deny_list.iter().cloned().collect())
    }

    pub fn get_address_deny_set(&self) -> &HashSet<SomaAddress> {
        self.address_deny_set.get_or_init(|| self.address_deny_list.iter().cloned().collect())
    }

    pub fn shared_object_disabled(&self) -> bool {
        self.shared_object_disabled
    }

    pub fn user_transaction_disabled(&self) -> bool {
        self.user_transaction_disabled
    }

    pub fn receiving_objects_disabled(&self) -> bool {
        self.receiving_objects_disabled
    }
}

#[derive(Default)]
pub struct TransactionDenyConfigBuilder {
    config: TransactionDenyConfig,
}

impl TransactionDenyConfigBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn build(self) -> TransactionDenyConfig {
        self.config
    }

    pub fn disable_user_transaction(mut self) -> Self {
        self.config.user_transaction_disabled = true;
        self
    }

    pub fn disable_shared_object_transaction(mut self) -> Self {
        self.config.shared_object_disabled = true;
        self
    }

    pub fn disable_receiving_objects(mut self) -> Self {
        self.config.receiving_objects_disabled = true;
        self
    }

    pub fn add_denied_object(mut self, id: ObjectID) -> Self {
        self.config.object_deny_list.push(id);
        self
    }

    pub fn add_denied_address(mut self, address: SomaAddress) -> Self {
        self.config.address_deny_list.push(address);
        self
    }
}
