//! Rocks DB implementation of the storage layer
use crate::{secondary_index::SecondaryIndex, iterator::WrapperIterator};
use nostr_rs_storage_base::{Error, Storage};
use nostr_rs_types::types::{Event, Filter, Tag};
use rocksdb::{
    BoundColumnFamily, ColumnFamilyDescriptor, IteratorMode, Options, SliceTransform, WriteBatch,
    DB,
};
use std::{collections::VecDeque, ops::Deref, path::Path, sync::Arc};

mod event_filter;
mod iterator;
mod secondary_index;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum ReferenceType {
    Events,
    Author,
    RefPublicKey,
    RefEvent,
    Kind,
    LocalEvents,
    Stream,
}

impl ReferenceType {
    #[inline]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Events => "events",
            Self::Author => "authors",
            Self::RefPublicKey => "refs_by_public_key",
            Self::RefEvent => "refs_by_ids",
            Self::Kind => "kinds",
            Self::LocalEvents => "local",
            Self::Stream => "stream",
        }
    }
}

/// Storage implementation using RocksDB
pub struct RocksDb {
    db: DB,
}

impl RocksDb {
    /// Creates a new instance passing rocksDb options and a path
    pub fn with_custom_db<T: AsRef<Path>>(options: Options, path: T) -> Result<Self, Error> {
        let db = DB::open_cf_descriptors(
            &options,
            path,
            vec![
                ColumnFamilyDescriptor::new(ReferenceType::Events.as_str(), options.clone()),
                ColumnFamilyDescriptor::new(ReferenceType::Author.as_str(), options.clone()),
                ColumnFamilyDescriptor::new(ReferenceType::RefEvent.as_str(), options.clone()),
                ColumnFamilyDescriptor::new(ReferenceType::RefPublicKey.as_str(), options.clone()),
                ColumnFamilyDescriptor::new(ReferenceType::Kind.as_str(), options.clone()),
                ColumnFamilyDescriptor::new(ReferenceType::LocalEvents.as_str(), options.clone()),
                ColumnFamilyDescriptor::new(ReferenceType::Stream.as_str(), options.clone()),
            ],
        )
        .map_err(|e| Error::Unknown(e.to_string()))?;
        Ok(Self { db })
    }

    /// Creates a new instance, passing just the path, the default options are
    /// passed
    pub fn new<T: AsRef<Path>>(path: T) -> Result<Self, Error> {
        let mut options = Options::default();
        options.create_if_missing(true);
        options.create_missing_column_families(true);
        options.set_compression_type(rocksdb::DBCompressionType::Snappy);
        options.optimize_for_point_lookup(256 * 1024 * 1024);

        let prefix_extractor = SliceTransform::create_fixed_prefix(1);
        options.set_prefix_extractor(prefix_extractor);

        Self::with_custom_db(options, path)
    }

    #[inline]
    fn reference_to_cf_handle(
        &self,
        namespace: ReferenceType,
    ) -> Result<Arc<BoundColumnFamily>, Error> {
        self.db
            .cf_handle(namespace.as_str())
            .ok_or(Error::InvalidColumnFamily)
    }
}

impl Storage for RocksDb {
    type Iterator<'a> = WrapperIterator<'a>;
    fn get_local_events(&self, limit: Option<usize>) -> Result<WrapperIterator<'_>, Error> {
        let cf_handle = self.reference_to_cf_handle(ReferenceType::LocalEvents)?;
        Ok(WrapperIterator {
            db: self,
            filter: None,
            namespace: None,
            secondary_index_iterator: Some(self.db.iterator_cf(&cf_handle, IteratorMode::Start)),
            current_prefix: vec![],
            prefixes: VecDeque::new(),
            limit,
            returned: 0,
        })
    }

    fn set_local_event(&self, event: &Event) -> Result<(), Error> {
        let event_id = &event.id;
        let secondary_index = SecondaryIndex::new(event_id, event.created_at());
        self.db.put_cf(
            &self.reference_to_cf_handle(ReferenceType::LocalEvents)?,
            secondary_index.index_by([]),
            event_id.deref(),
        ).map_err(|e| Error::Unknown(e.to_string()))?;
        Ok(())
    }

    fn store(&self, event: &Event) -> Result<bool, Error> {
        let event_id = &event.id;

        if let Ok(Some(_)) = self.db.get_cf(
            &self.reference_to_cf_handle(ReferenceType::Events)?,
            event_id.deref(),
        ) {
            return Ok(false);
        }
        let secondary_index = SecondaryIndex::new(event_id, event.created_at());
        let author_id = secondary_index.index_by(event.author());
        let json = serde_json::to_vec(event)?;
        let kind: u32 = event.kind().into();
        let kind_id = secondary_index.index_by(kind.to_be_bytes());

        let mut buffer = WriteBatch::default();

        buffer.put_cf(
            &self.reference_to_cf_handle(ReferenceType::Events)?,
            event_id.deref(),
            json,
        );
        buffer.put_cf(
            &self.reference_to_cf_handle(ReferenceType::Author)?,
            author_id,
            event_id.deref(),
        );
        buffer.put_cf(
            &self.reference_to_cf_handle(ReferenceType::Kind)?,
            kind_id,
            event_id.deref(),
        );

        buffer.put_cf(
            &self.reference_to_cf_handle(ReferenceType::Stream)?,
            secondary_index.index_by([]),
            event_id.deref(),
        );

        for tag in event.tags().iter() {
            match tag {
                Tag::PubKey(p) => {
                    let foreign_id = secondary_index.index_by(&p.id);
                    let local_id = secondary_index.index_by(event_id);

                    buffer.put_cf(
                        &self.reference_to_cf_handle(ReferenceType::RefPublicKey)?,
                        foreign_id,
                        event_id.deref(),
                    );
                    buffer.put_cf(
                        &self.reference_to_cf_handle(ReferenceType::RefEvent)?,
                        local_id,
                        &*p.id,
                    );
                }
                Tag::Event(e) => {
                    let foreign_id = secondary_index.index_by(&e.id);
                    let local_id = secondary_index.index_by(event_id);

                    buffer.put_cf(
                        &self.reference_to_cf_handle(ReferenceType::RefEvent)?,
                        foreign_id,
                        event_id.deref(),
                    );
                    buffer.put_cf(
                        &self.reference_to_cf_handle(ReferenceType::RefEvent)?,
                        local_id,
                        &*e.id,
                    );
                }
                _ => {}
            }
        }

        self.db.write(buffer).map_err(|e| Error::Unknown(e.to_string()))?;

        Ok(true)
    }

    fn get_event<T: AsRef<[u8]>>(&self, id: T) -> Result<Option<Event>, Error> {
        Ok(self
            .db
            .get_cf(&self.reference_to_cf_handle(ReferenceType::Events)?, id)
            .map_err(|e| Error::Unknown(e.to_string()))?
            .map(|event| serde_json::from_slice(&event))
            .transpose()?)
    }

    fn get_by_filter(&self, mut query: Filter) -> Result<WrapperIterator<'_>, Error> {
        let limit = if query.limit == 0 {
            None
        } else {
            Some(query.limit.try_into()?)
        };

        let (namespace, secondary_index_iterator, prefixes) =
            if !query.references_to_event.is_empty() {
                let ns: Arc<BoundColumnFamily<'_>> =
                    self.reference_to_cf_handle(ReferenceType::RefEvent)?;
                let keys = query
                    .references_to_event
                    .iter()
                    .map(|c| c.as_ref().to_vec())
                    .collect();
                query.references_to_event.clear();
                (Some(ns), None, keys)
            } else if !query.references_to_public_key.is_empty() {
                let ns = self.reference_to_cf_handle(ReferenceType::RefEvent)?;
                let keys = query
                    .references_to_public_key
                    .iter()
                    .map(|c| c.as_ref().to_vec())
                    .collect();
                query.references_to_public_key.clear();
                (Some(ns), None, keys)
            } else if !query.ids.is_empty() {
                let keys = query.ids.iter().map(|c| c.as_ref().to_vec()).collect();
                query.ids.clear();
                (None, None, keys)
            } else if !query.authors.is_empty() {
                let ns = self.reference_to_cf_handle(ReferenceType::Author)?;
                let keys = query.authors.iter().map(|c| c.as_ref().to_vec()).collect();
                query.authors.clear();
                (Some(ns), None, keys)
            } else if !query.kinds.is_empty() {
                let ns = self.reference_to_cf_handle(ReferenceType::Kind)?;
                let keys = query
                    .kinds
                    .iter()
                    .map(|kind| {
                        let kind: u32 = (*kind).into();
                        kind.to_be_bytes().to_vec()
                    })
                    .collect();
                query.kinds.clear();
                (Some(ns), None, keys)
            } else {
                let cf_handle = self.reference_to_cf_handle(ReferenceType::Stream)?;
                (
                    None,
                    Some(self.db.iterator_cf(&cf_handle, IteratorMode::Start)),
                    VecDeque::new(),
                )
            };

        Ok(WrapperIterator {
            db: self,
            filter: Some(query.into()),
            namespace,
            secondary_index_iterator,
            current_prefix: vec![],
            prefixes,
            returned: 0,
            limit,
        })

        //load_events_and_filter(self, query, event_ids, for_each)
    }
}

#[cfg(test)]
fn new_instance(path: &str) -> RocksDb {
    RocksDb::new(&path).expect("valid db")
}

#[cfg(test)]
fn destroy_instance(path: &str) {
    std::fs::remove_dir_all(path).expect("deleted file");
}

nostr_rs_storage_base::storage_test!(RocksDb, new_instance, destroy_instance);
