//! Collection CRUD operations and rebuild logic on `TidalDb`.

use std::sync::Arc;

use crate::entities::{
    Collection, CollectionId, CollectionIndex, CollectionInfo, Visibility, deserialize_collection,
    serialize_collection,
};
use crate::schema::{EntityId, TidalError, Timestamp};
use crate::session::{
    SavedSearch, SavedSearchInfo, deserialize_saved_search, serialize_saved_search,
};
use crate::storage::{StorageEngine, Tag, encode_key, entity_tag_prefix};

use super::TidalDb;

impl TidalDb {
    /// Create a named collection for an owner user.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if no storage is available.
    pub fn create_collection(
        &self,
        owner: EntityId,
        name: &str,
        visibility: Visibility,
    ) -> crate::Result<CollectionId> {
        self.require_writeable("create_collection")?;
        let id = self.collection_index.next_collection_id();
        let collection = Collection {
            id,
            owner_id: owner.as_u64(),
            name: name.to_string(),
            visibility,
            created_at_ns: Timestamp::now().as_nanos(),
            item_ids: vec![],
        };

        // Persist to storage.
        self.persist_collection(&collection)?;

        // Update in-memory index.
        self.collection_index.create(collection);

        Ok(id)
    }

    /// Add an item to a collection. Idempotent.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if the collection does not exist or storage fails.
    pub fn add_to_collection(
        &self,
        collection_id: CollectionId,
        item_id: EntityId,
    ) -> crate::Result<()> {
        self.require_writeable("add_to_collection")?;
        #[allow(clippy::cast_possible_truncation)]
        let item_id_u32 = item_id.as_u64() as u32;

        // Update in-memory index.
        self.collection_index.add_item(collection_id, item_id_u32);

        // Re-persist the updated collection.
        let collection = self
            .collection_index
            .get_meta(collection_id)
            .ok_or_else(|| {
                TidalError::internal(
                    "add_to_collection",
                    format!("collection {} not found", collection_id.as_u64()),
                )
            })?;
        self.persist_collection(&collection)?;

        #[cfg(any(test, feature = "test-utils"))]
        crate::testing::crash_injector::check_crash_point(
            crate::testing::CrashPoint::CollectionIndexUpdate,
        );

        Ok(())
    }

    /// Remove an item from a collection. No-op if item not in collection.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if the collection does not exist or storage fails.
    pub fn remove_from_collection(
        &self,
        collection_id: CollectionId,
        item_id: EntityId,
    ) -> crate::Result<()> {
        self.require_writeable("remove_from_collection")?;
        #[allow(clippy::cast_possible_truncation)]
        let item_id_u32 = item_id.as_u64() as u32;

        // Update in-memory index.
        self.collection_index
            .remove_item(collection_id, item_id_u32);

        // Re-persist the updated collection (if it exists).
        if let Some(collection) = self.collection_index.get_meta(collection_id) {
            self.persist_collection(&collection)?;
        }

        Ok(())
    }

    /// List all collections owned by a user.
    ///
    /// # Errors
    ///
    /// This method currently does not produce errors but returns `Result` for
    /// API consistency and future storage-backed listing.
    pub fn list_collections(&self, owner: EntityId) -> crate::Result<Vec<CollectionInfo>> {
        Ok(self.collection_index.list_by_owner(owner.as_u64()))
    }

    /// Access the collection index (for use by the executor).
    #[must_use]
    #[allow(clippy::missing_const_for_fn)] // Arc prevents const in practice
    pub fn collection_index(&self) -> &Arc<CollectionIndex> {
        &self.collection_index
    }

    // ── Saved Search CRUD ────────────────────────────────────────────────

    /// Save a search configuration for later re-execution.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if storage is unavailable.
    pub fn save_search(
        &self,
        user: EntityId,
        name: &str,
        query_text: &str,
        _filter_expr: Option<crate::storage::indexes::filter::FilterExpr>,
    ) -> crate::Result<()> {
        self.require_writeable("save_search")?;
        let ss = SavedSearch {
            user_id: user.as_u64(),
            name: name.to_string(),
            query_text: query_text.to_string(),
            filter_expr_json: None, // Filter persistence deferred; query_text is always stored.
            created_at_ns: Timestamp::now().as_nanos(),
        };

        let key = encode_key(
            EntityId::new(user.as_u64()),
            Tag::SavedSearch,
            name.as_bytes(),
        );
        let value = serialize_saved_search(&ss);

        if let Some(storage) = self.storage.as_ref() {
            storage
                .items_engine()
                .put(&key, &value)
                .map_err(|e| TidalError::internal("save_search", format!("storage write: {e}")))?;
        }

        Ok(())
    }

    /// List all saved searches for a user.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if storage is unavailable.
    pub fn list_saved_searches(&self, user: EntityId) -> crate::Result<Vec<SavedSearchInfo>> {
        let prefix = entity_tag_prefix(EntityId::new(user.as_u64()), Tag::SavedSearch);
        let mut results = Vec::new();

        if let Some(storage) = self.storage.as_ref() {
            let iter = storage.items_engine().scan_prefix(&prefix);
            for (_key, value) in iter.flatten() {
                if let Some(ss) = deserialize_saved_search(&value) {
                    results.push(SavedSearchInfo {
                        name: ss.name,
                        query_text: ss.query_text,
                        created_at_ns: ss.created_at_ns,
                    });
                }
            }
        }

        Ok(results)
    }

    /// Delete a saved search by name.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if storage is unavailable.
    pub fn delete_saved_search(&self, user: EntityId, name: &str) -> crate::Result<()> {
        self.require_writeable("delete_saved_search")?;
        let key = encode_key(
            EntityId::new(user.as_u64()),
            Tag::SavedSearch,
            name.as_bytes(),
        );

        if let Some(storage) = self.storage.as_ref() {
            storage
                .items_engine()
                .delete(&key)
                .map_err(|e| TidalError::internal("delete_saved_search", format!("{e}")))?;
        }

        Ok(())
    }

    /// Re-execute a saved search, optionally filtering by creation time.
    ///
    /// Loads the saved search record, builds a `Search` query from its
    /// `query_text`, and calls `self.search()`. If `since` is provided,
    /// a `CreatedAfter` filter is added.
    ///
    /// # Errors
    ///
    /// Returns `TidalError::Internal` if the saved search is not found or
    /// search execution fails.
    pub fn retrieve_saved_search(
        &self,
        user: EntityId,
        name: &str,
        since: Option<u64>,
    ) -> crate::Result<crate::query::search::SearchResults> {
        let key = encode_key(
            EntityId::new(user.as_u64()),
            Tag::SavedSearch,
            name.as_bytes(),
        );

        let storage = self.storage.as_ref().ok_or_else(|| {
            TidalError::internal(
                "retrieve_saved_search",
                "no storage: open with with_schema()",
            )
        })?;

        let bytes = storage
            .items_engine()
            .get(&key)
            .map_err(|e| TidalError::internal("retrieve_saved_search", format!("read: {e}")))?
            .ok_or_else(|| {
                TidalError::internal(
                    "retrieve_saved_search",
                    format!("saved search '{name}' not found for user {}", user.as_u64()),
                )
            })?;

        let ss = deserialize_saved_search(&bytes).ok_or_else(|| {
            TidalError::internal(
                "retrieve_saved_search",
                format!("corrupted saved search '{name}'"),
            )
        })?;

        let mut builder = crate::query::search::Search::builder()
            .query(&ss.query_text)
            .for_user(user.as_u64());

        if let Some(ts) = since {
            builder = builder.filter(crate::storage::indexes::filter::FilterExpr::CreatedAfter(
                ts,
            ));
        }

        let search = builder.build().map_err(|e| {
            TidalError::internal(
                "retrieve_saved_search",
                format!("saved search build failed: {e}"),
            )
        })?;

        self.search(&search)
    }

    // ── Internal helpers ─────────────────────────────────────────────────

    /// Persist a collection record to fjall.
    fn persist_collection(&self, collection: &Collection) -> crate::Result<()> {
        let key = encode_key(
            EntityId::new(collection.owner_id),
            Tag::Collection,
            &collection.id.as_u64().to_le_bytes(),
        );
        let value = serialize_collection(collection);

        if let Some(storage) = self.storage.as_ref() {
            storage
                .items_engine()
                .put(&key, &value)
                .map_err(|e| TidalError::internal("collection_persist", format!("{e}")))?;
        }

        Ok(())
    }
}

/// Rebuild the collection index from durable storage.
///
/// Scans all keys with `Tag::Collection` and deserializes each collection record.
/// Sets the `next_id` counter to `max(collection_ids) + 1`.
pub fn rebuild_collections(index: &CollectionIndex, storage: &dyn StorageEngine) {
    // We need to scan all collection keys. Since collections are stored under
    // various owner entity IDs, we cannot use a single entity prefix scan.
    // Instead, we scan a broad prefix. Collection keys have the format:
    //   [entity_id: 8 BE][0x00][Tag::Collection (0x0A)][collection_id: 8 LE]
    //
    // We cannot iterate all keys without a prefix. Instead, rebuild is done
    // by scanning the full keyspace with the Tag::Collection byte.
    // For practical implementation, we scan prefix [0x00, ..., 0xFF] ranges.
    //
    // The simplest approach: iterate from 0 to u64::MAX as entity IDs.
    // That is infeasible. Instead, we rely on the fact that fjall supports
    // full-keyspace iteration. We will scan with an empty prefix and filter.
    //
    // Actually, looking at the StorageEngine trait, `scan_prefix` with an
    // empty prefix would return all keys. Let us use that and filter for
    // Tag::Collection.

    let mut max_id = 0u64;
    let iter = storage.scan_prefix(&[]);
    for (key, value) in iter.flatten() {
        if let Some((_eid, tag, _suffix)) = crate::storage::parse_key(&key)
            && tag == Tag::Collection
            && let Some(collection) = deserialize_collection(&value)
        {
            let cid = collection.id.as_u64();
            if cid >= max_id {
                max_id = cid + 1;
            }
            index.create(collection);
        }
    }

    if max_id > 0 {
        index.set_next_id(max_id);
    }
}
