// src/application/services/bookmark_service_impl.rs
use std::collections::HashSet;
use std::sync::Arc;

use crate::application::error::{ApplicationError, ApplicationResult};
use crate::application::services::bookmark_service::BookmarkService;
use crate::domain::bookmark::{Bookmark, BookmarkBuilder};
use crate::domain::embedding::Embedder;
use crate::domain::error_context::ApplicationErrorContext;
use crate::domain::repositories::import_repository::{
    FileImportData, ImportRepository,
};
use crate::domain::repositories::query::{BookmarkQuery, SortCriteria, SortDirection, SortField};
use crate::domain::repositories::repository::BookmarkRepository;
use crate::domain::repositories::vector_repository::VectorRepository;
use crate::domain::search::{
    HybridSearch, HybridSearchResult, RrfFusion, SemanticSearch, SemanticSearchResult,
};
use crate::domain::tag::Tag;
use crate::infrastructure::http;
use crate::util::helper::calc_content_hash;
use crate::util::validation::ValidationHelper;
use std::path::Path;
use tracing::{debug, info, instrument, warn};

#[derive(Debug)]
pub struct BookmarkServiceImpl<R: BookmarkRepository> {
    repository: Arc<R>,
    embedder: Arc<dyn Embedder>,
    vector_repository: Arc<dyn VectorRepository>,
    import_repository: Arc<dyn ImportRepository>,
}

impl<R: BookmarkRepository> BookmarkServiceImpl<R> {
    pub fn new(
        repository: Arc<R>,
        embedder: Arc<dyn Embedder>,
        vector_repository: Arc<dyn VectorRepository>,
        import_repository: Arc<dyn ImportRepository>,
    ) -> Self {
        Self {
            repository,
            embedder,
            vector_repository,
            import_repository,
        }
    }

    /// Generate and store embedding for a bookmark if content produces one.
    /// Silently succeeds if the embedder returns None (e.g., DummyEmbedding).
    ///
    /// Note: bookmark persistence and embedding storage use separate database
    /// connections (Diesel vs rusqlite). If embedding fails after the bookmark
    /// was committed, the bookmark survives without an embedding. This is an
    /// accepted trade-off — `bkmr backfill` will repair missing embeddings.
    fn upsert_embedding_for_bookmark(
        &self,
        bookmark_id: i32,
        content: &str,
    ) -> ApplicationResult<()> {
        match self.embedder.embed_document(content)? {
            Some(embedding) => {
                self.vector_repository
                    .upsert_embedding(bookmark_id, &embedding)
                    .app_context("upserting embedding into vector repository")?;
                debug!("Stored embedding for bookmark {}", bookmark_id);
            }
            None => {
                debug!(
                    "Embedder returned None for bookmark {} — skipping vector upsert",
                    bookmark_id
                );
            }
        }
        Ok(())
    }
}

impl<R: BookmarkRepository> BookmarkService for BookmarkServiceImpl<R> {
    #[instrument(skip(self, tags), level = "debug",
               fields(url = %url, title = %title.unwrap_or("None"), fetch_metadata = %fetch_metadata))]
    fn add_bookmark(
        &self,
        url: &str,
        title: Option<&str>,
        description: Option<&str>,
        tags: Option<&HashSet<Tag>>,
        fetch_metadata: bool,
        embeddable: bool,
    ) -> ApplicationResult<Bookmark> {
        // Check if bookmark with URL already exists
        let existing_id = self
            .repository
            .exists_by_url(url)
            .app_context("checking if bookmark with URL already exists")?;
        if existing_id != -1 {
            return Err(ApplicationError::BookmarkExists(
                existing_id,
                url.to_string(),
            ));
        }

        let (title_str, desc_str, _keywords) =
            if fetch_metadata && (url.starts_with("http://") || url.starts_with("https://")) {
                // Try to fetch metadata from web URLs
                match http::load_url_details(url) {
                    Ok((t, d, k)) => (
                        title.map_or(t, |user_title| user_title.to_string()),
                        description.map_or(d, |user_desc| user_desc.to_string()),
                        k,
                    ),
                    Err(e) => {
                        debug!("Failed to fetch URL metadata: {}", e);
                        (
                            title.map_or_else(|| "Untitled".to_string(), |t| t.to_string()),
                            description.map_or_else(String::new, ToString::to_string),
                            String::new(),
                        )
                    }
                }
            } else {
                // Use provided or default values for non-web URLs or when fetching is disabled
                (
                    title.map_or_else(|| "Untitled".to_string(), ToString::to_string),
                    description.map_or_else(String::new, ToString::to_string),
                    String::new(),
                )
            };

        let all_tags = tags.cloned().unwrap_or_default();

        // Create and save bookmark
        debug!(
            "Creating bookmark: '{}' with {} tags",
            title_str,
            all_tags.len()
        );
        let mut bookmark =
            Bookmark::new(url, &title_str, &desc_str, all_tags)
                .app_context("creating new bookmark from provided data")?;
        bookmark.set_embeddable(embeddable);

        self.repository
            .add(&mut bookmark)
            .app_context("saving new bookmark to repository")?;

        // Generate and store embedding if bookmark is embeddable and has an ID
        if bookmark.embeddable {
            if let Some(id) = bookmark.id {
                let content = bookmark.get_content_for_embedding();
                self.upsert_embedding_for_bookmark(id, &content)?;
            }
        }

        info!(bookmark_id = ?bookmark.id, title = %bookmark.title, "Bookmark created");
        Ok(bookmark)
    }

    #[instrument(skip(self), level = "debug")]
    fn delete_bookmark(&self, id: i32) -> ApplicationResult<bool> {
        ValidationHelper::validate_bookmark_id(id)
            .app_context("validating bookmark ID for deletion")?;

        let result = self
            .repository
            .delete(id)
            .with_app_context(|| format!("deleting bookmark with ID {}", id))?;

        // Best-effort: remove embedding from vector store (may not exist)
        if let Err(e) = self.vector_repository.delete_embedding(id) {
            debug!("Could not delete embedding for bookmark {}: {} (may not exist)", id, e);
        }

        if result {
            info!(bookmark_id = id, "Bookmark deleted");
        }
        Ok(result)
    }

    #[instrument(skip(self), level = "debug")]
    fn get_bookmark(&self, id: i32) -> ApplicationResult<Option<Bookmark>> {
        ValidationHelper::validate_bookmark_id(id)
            .app_context("validating bookmark ID for retrieval")?;

        let bookmark = self
            .repository
            .get_by_id(id)
            .with_app_context(|| format!("retrieving bookmark with ID {}", id))?;
        Ok(bookmark)
    }

    #[instrument(skip(self), level = "debug")]
    fn set_bookmark_embeddable(&self, id: i32, embeddable: bool) -> ApplicationResult<Bookmark> {
        let mut bookmark = ValidationHelper::validate_and_get_bookmark(id, &*self.repository)
            .with_app_context(|| {
                format!(
                    "validating and retrieving bookmark with ID {} for embeddable setting",
                    id
                )
            })?;

        bookmark.set_embeddable(embeddable);

        // If embeddable is being turned off, explicitly clear the embeddings
        if !embeddable {
            debug!("Setting bookmark {} to non-embeddable", id);
            bookmark.embedding = None;
            bookmark.content_hash = None;

            // Remove from vector store
            if let Err(e) = self.vector_repository.delete_embedding(id) {
                debug!("Could not delete embedding for bookmark {}: {} (may not exist)", id, e);
            }

            // No need to force embedding creation since we're turning it off
            self.update_bookmark(bookmark, false)
        } else {
            // If embeddable is being turned on, force the creation of embeddings
            self.update_bookmark(bookmark, true)
        }
    }

    // Note: This embedding logic could be moved to a dedicated domain service
    // to better separate concerns between bookmark persistence and embedding computation
    #[instrument(skip(self), level = "debug")]
    fn update_bookmark(
        &self,
        mut bookmark: Bookmark,
        force_embedding: bool,
    ) -> ApplicationResult<Bookmark> {
        ValidationHelper::validate_bookmark_id(bookmark.id.ok_or_else(|| {
            ApplicationError::Validation("Bookmark ID is required for update".to_string())
        })?)
        .app_context("validating bookmark ID for update operation")?;

        let content = bookmark.get_content_for_embedding();
        let new_hash = calc_content_hash(&content);

        // Only update embedding if embeddable flag is true
        if bookmark.embeddable {
            // Generate new embedding if forced or content has changed
            if force_embedding || bookmark.content_hash.as_ref() != Some(&new_hash) {
                debug!(
                    "Generating new embedding (force={}, content_changed={})",
                    force_embedding,
                    bookmark.content_hash.as_ref() != Some(&new_hash)
                );

                // Generate embedding and store in vector repository
                if let Some(id) = bookmark.id {
                    self.upsert_embedding_for_bookmark(id, &content)?;
                }
                bookmark.content_hash = Some(new_hash);
                bookmark.embedding = None;
            } else {
                debug!("Skipping embedding generation - content unchanged and not forced");
            }
        } else {
            // Clear embedding if not embeddable
            bookmark.embedding = None;
            bookmark.content_hash = None;
        }

        self.repository
            .update(&bookmark)
            .with_app_context(|| format!("updating bookmark with ID {:?}", bookmark.id))?;
        info!(bookmark_id = ?bookmark.id, "Bookmark updated");
        Ok(bookmark)
    }

    #[instrument(skip(self, tags), level = "debug")]
    fn add_tags_to_bookmark(&self, id: i32, tags: &HashSet<Tag>) -> ApplicationResult<Bookmark> {
        let mut bookmark = ValidationHelper::validate_and_get_bookmark(id, &*self.repository)
            .with_app_context(|| {
                format!(
                    "validating and retrieving bookmark with ID {} for adding tags",
                    id
                )
            })?;

        for tag in tags {
            bookmark
                .add_tag(tag.clone())
                .with_app_context(|| format!("adding tag '{}' to bookmark", tag.value()))?;
        }
        self.update_bookmark(bookmark, false)
            .app_context("updating bookmark after adding tags")
    }

    #[instrument(skip(self, tags), level = "debug")]
    fn remove_tags_from_bookmark(
        &self,
        id: i32,
        tags: &HashSet<Tag>,
    ) -> ApplicationResult<Bookmark> {
        let mut bookmark = ValidationHelper::validate_and_get_bookmark(id, &*self.repository)?;

        for tag in tags {
            let _ = bookmark.remove_tag(tag);
        }
        self.update_bookmark(bookmark, false)
    }

    #[instrument(skip(self, tags), level = "debug")]
    fn replace_bookmark_tags(&self, id: i32, tags: &HashSet<Tag>) -> ApplicationResult<Bookmark> {
        let mut bookmark = ValidationHelper::validate_and_get_bookmark(id, &*self.repository)?;

        bookmark.set_tags(tags.clone())?;
        self.update_bookmark(bookmark, false)
    }

    // Implement the convenience method for text search
    #[instrument(skip_all, level = "debug")]
    fn search_bookmarks_by_text(&self, query: &str) -> ApplicationResult<Vec<Bookmark>> {
        let query = BookmarkQuery::new()
            .with_text_query(Some(query))
            .with_sort(SortCriteria::new(SortField::Modified, SortDirection::Descending));

        self.search_bookmarks(&query)
    }

    #[instrument(skip_all, level = "debug")]
    fn search_bookmarks(&self, query: &BookmarkQuery) -> ApplicationResult<Vec<Bookmark>> {
        debug!("Searching bookmarks with query: {:?}", query);

        let bookmarks = self.repository.search(query)?;
        debug!(result_count = bookmarks.len(), "Search complete");
        Ok(bookmarks)
    }

    #[instrument(skip(self, search), level = "debug")]
    fn semantic_search(
        &self,
        search: &SemanticSearch,
    ) -> ApplicationResult<Vec<SemanticSearchResult>> {
        // 1. Embed the query
        let query_embedding = match self.embedder.embed_query(&search.query)? {
            Some(emb) => emb,
            None => {
                debug!("Embedder returned None for query — returning empty results");
                return Ok(Vec::new());
            }
        };

        // 2. Dimension mismatch detection
        let embedder_dims = self.embedder.dimensions();
        if let Ok(Some(stored_dims)) = self.vector_repository.get_dimensions() {
            if stored_dims != embedder_dims {
                warn!(
                    "Dimension mismatch: embedder produces {} dims but vector store has {} dims. \
                     Run `bkmr backfill --force` to regenerate embeddings.",
                    embedder_dims, stored_dims
                );
                return Err(ApplicationError::Other(format!(
                    "Embedding dimension mismatch: model={}, stored={}. Run `bkmr backfill --force` to regenerate.",
                    embedder_dims, stored_dims
                )));
            }
        }

        // 3. Search nearest neighbors
        let limit = search.limit.unwrap_or(10);
        let nearest = self
            .vector_repository
            .search_nearest(&query_embedding, limit)
            .app_context("searching nearest embeddings in vector repository")?;

        // 4. Fetch bookmarks and build results
        let mut results = Vec::with_capacity(nearest.len());
        for (bookmark_id, distance) in nearest {
            match self.repository.get_by_id(bookmark_id)? {
                Some(bookmark) => {
                    // Convert distance to similarity: 1 / (1 + distance)
                    let similarity = 1.0 / (1.0 + distance);
                    results.push(SemanticSearchResult::new(bookmark, similarity));
                }
                None => {
                    debug!(
                        "Bookmark {} found in vector store but not in bookmarks table — skipping",
                        bookmark_id
                    );
                }
            }
        }

        debug!(query = %search.query, result_count = results.len(), "Semantic search complete");
        Ok(results)
    }

    #[instrument(skip(self, search), level = "debug", fields(query = %search.query, mode = ?search.mode))]
    fn hybrid_search(
        &self,
        search: &HybridSearch,
    ) -> ApplicationResult<Vec<HybridSearchResult>> {
        use crate::domain::search::{RankedResult, SearchMode};

        let limit = search.effective_limit();
        let internal_limit = std::cmp::max(limit * 4, 20);
        let k = 60.0;

        // Step 0: Tag pre-filtering — get allowed ID set if tags are specified
        let filter_ids = if search.has_tag_filters() {
            let all_bookmarks = self.repository.get_all()?;
            let filtered = search.apply_tag_filters(&all_bookmarks);
            let ids: std::collections::HashSet<i32> = filtered
                .into_iter()
                .filter_map(|b| b.id)
                .collect();
            if ids.is_empty() {
                return Ok(vec![]);
            }
            Some(ids)
        } else {
            None
        };

        // Step 1: FTS ranked search (always runs)
        let fts_ranked = self
            .repository
            .get_bookmarks_fts_ranked(&search.query, filter_ids.as_ref())?;

        // Step 2: Semantic search (skip if exact mode or no embeddings)
        let sem_ranked = if search.mode == SearchMode::Exact
            || self.embedder.dimensions() == 0
            || !self.vector_repository.has_embeddings().unwrap_or(false)
        {
            vec![]
        } else {
            let query_embedding = self.embedder.embed_query(&search.query)?;
            match query_embedding {
                Some(embedding) => {
                    let vec_results = self
                        .vector_repository
                        .search_nearest_filtered(
                            &embedding,
                            internal_limit,
                            filter_ids.as_ref(),
                        )?;
                    vec_results
                        .into_iter()
                        .enumerate()
                        .map(|(rank, (id, _distance))| RankedResult {
                            bookmark_id: id,
                            rank,
                        })
                        .collect()
                }
                None => vec![],
            }
        };

        // Step 3: RRF fusion
        let fts_for_fusion: Vec<_> = fts_ranked.into_iter().take(internal_limit).collect();
        let fused = RrfFusion::fuse(&fts_for_fusion, &sem_ranked, k, limit);

        // Step 4: Hydrate bookmarks
        let mut results = Vec::with_capacity(fused.len());
        for (bookmark_id, rrf_score) in fused {
            if let Some(bookmark) = self.repository.get_by_id(bookmark_id)? {
                results.push(HybridSearchResult::new(bookmark, rrf_score));
            }
        }

        debug!(result_count = results.len(), "Hybrid search complete");
        Ok(results)
    }

    #[instrument(skip(self), level = "debug")]
    fn get_bookmark_by_url(&self, url: &str) -> ApplicationResult<Option<Bookmark>> {
        let bookmark = self.repository.get_by_url(url)?;
        Ok(bookmark)
    }

    #[instrument(skip(self), level = "debug")]
    fn get_all_bookmarks(
        &self,
        sort_direction: Option<SortDirection>,
        limit: Option<usize>,
    ) -> ApplicationResult<Vec<Bookmark>> {
        let bookmarks = match sort_direction {
            Some(direction) => self.repository.get_by_access_date(direction, limit)?,
            None => {
                let mut query = BookmarkQuery::new();
                if let Some(limit_val) = limit {
                    query = query.with_limit(Some(limit_val));
                }
                self.repository.search(&query)?
            }
        };

        Ok(bookmarks)
    }

    #[instrument(skip(self), level = "debug")]
    fn get_random_bookmarks(&self, count: usize) -> ApplicationResult<Vec<Bookmark>> {
        let bookmarks = self.repository.get_random(count)?;
        Ok(bookmarks)
    }

    #[instrument(skip(self), level = "debug")]
    fn get_bookmarks_for_forced_backfill(&self) -> ApplicationResult<Vec<Bookmark>> {
        let all_bookmarks = self.repository.get_all()?;
        let filtered_bookmarks = all_bookmarks
            .into_iter()
            .filter(|bookmark| bookmark.embeddable)
            .collect();
        Ok(filtered_bookmarks)
    }

    #[instrument(skip(self), level = "debug")]
    fn get_bookmarks_without_embeddings(&self) -> ApplicationResult<Vec<Bookmark>> {
        let embedded_ids = self.vector_repository.get_embedded_ids()?;
        // Use SQL-level filter for embeddable bookmarks, then exclude already-embedded
        let bookmarks = self.repository.get_embeddable_without_embeddings()?;
        Ok(bookmarks
            .into_iter()
            .filter(|b| b.id.map_or(true, |id| !embedded_ids.contains(&id)))
            .collect())
    }

    #[instrument(skip(self), level = "debug")]
    fn record_bookmark_access(&self, id: i32) -> ApplicationResult<Bookmark> {
        let mut bookmark = ValidationHelper::validate_and_get_bookmark(id, &*self.repository)?;

        bookmark.record_access();

        self.repository.update_access(&bookmark)?;

        Ok(bookmark)
    }

    /// Bulk-create bookmarks from JSON array. Creates full Bookmark objects (url, title,
    /// description, tags) and generates embeddings via `Bookmark::get_content_for_embedding()`
    /// (type-aware dispatch). Skips URLs that already exist — no update support.
    #[instrument(skip(self), level = "debug")]
    fn load_json_bookmarks(&self, path: &str, dry_run: bool, embeddable: bool) -> ApplicationResult<usize> {
        let imports = self
            .import_repository
            .import_json_bookmarks(path)
            .map_err(|e| ApplicationError::Other(format!("Failed to import data: {}", e)))?;

        if dry_run {
            return Ok(imports.len());
        }

        let mut processed_count = 0;

        for import in imports {
            // Check if bookmark with URL already exists
            let existing_id = self.repository.exists_by_url(&import.url)?;
            if existing_id != -1 {
                debug!(
                    "Bookmark with URL {} already exists (ID: {}), skipping",
                    import.url, existing_id
                );
                continue;
            }

            debug!("Processing import: {}", import.url);

            // Create the bookmark
            let mut bookmark = Bookmark::new(
                &import.url,
                &import.title,
                &import.content,
                import.tags,
            )?;
            bookmark.set_embeddable(embeddable);

            self.repository.add(&mut bookmark)?;

            // Generate embedding after bookmark has an ID
            if bookmark.embeddable {
                if let Some(id) = bookmark.id {
                    let content = bookmark.get_content_for_embedding();
                    self.upsert_embedding_for_bookmark(id, &content)?;
                }
            }

            processed_count += 1;
        }

        info!(count = processed_count, path = %path, "JSON import complete");
        Ok(processed_count)
    }

    /// Import files from directories with frontmatter parsing. Stores full content in url,
    /// tracks source file (file_path, file_mtime, file_hash) for smart editing. Generates
    /// embeddings via `Bookmark::get_content_for_embedding()` (type-aware dispatch).
    /// Supports incremental updates (--update) and orphan cleanup (--delete-missing).
    #[instrument(skip(self), level = "debug")]
    fn import_files(
        &self,
        paths: &[String],
        update: bool,
        delete_missing: bool,
        dry_run: bool,
        verbose: bool,
        base_path_name: Option<&str>,
        embeddable: bool,
    ) -> ApplicationResult<(usize, usize, usize)> {
        use crate::domain::repositories::import_repository::ImportOptions;

        debug!("Starting file import: paths={:?}, update={}, delete_missing={}, dry_run={}, verbose={}, base_path={:?}", 
               paths, update, delete_missing, dry_run, verbose, base_path_name);

        // Load settings for base path resolution
        let settings = crate::config::load_settings(None)
            .map_err(|e| ApplicationError::Other(format!("Failed to load settings: {}", e)))?;

        // Resolve actual scan paths based on base path
        let actual_scan_paths = if let Some(base_name) = base_path_name {
            if let Some(base_value) = settings.base_paths.get(base_name) {
                let expanded_base = crate::config::resolve_file_path(&settings, base_value);
                // Convert relative paths to absolute paths under the base
                paths
                    .iter()
                    .map(|relative_path| {
                        let full_path = std::path::Path::new(&expanded_base).join(relative_path);
                        full_path.to_string_lossy().to_string()
                    })
                    .collect()
            } else {
                return Err(ApplicationError::Other(format!(
                    "Base path '{}' not found in configuration",
                    base_name
                )));
            }
        } else {
            // No base path - use paths as provided
            paths.to_vec()
        };

        let options = ImportOptions {
            update,
            delete_missing,
            dry_run,
            verbose,
        };

        // Get file data from repository using resolved paths
        let file_imports = self
            .import_repository
            .import_files(&actual_scan_paths, &options)
            .map_err(|e| ApplicationError::Other(format!("Failed to scan files: {}", e)))?;

        debug!("Found {} files to process", file_imports.len());

        let mut added_count = 0;
        let mut updated_count = 0;
        let mut deleted_count = 0;

        // Process each file import
        for file_data in &file_imports {
            // Check for duplicate names
            if let Some(existing) = self.find_bookmark_by_name(&file_data.name)? {
                if !update {
                    // Exit with code 65 - duplicate name without --update flag
                    return Err(ApplicationError::DuplicateName {
                        name: file_data.name.clone(),
                        existing_id: existing.id.unwrap_or(-1),
                        file_path: file_data.file_path.display().to_string(),
                    });
                }

                // Check if content or metadata has changed
                let content_changed = existing.file_hash.as_ref() != Some(&file_data.file_hash);
                let metadata_changed = self.has_metadata_changed(&existing, file_data)?;

                if !content_changed && !metadata_changed {
                    debug!("Skipping {}: no changes detected", file_data.name);
                    continue;
                }

                // Report what changed
                if content_changed {
                    println!("Content changed: {}", file_data.name);
                }
                if metadata_changed {
                    println!("Metadata changed: {}", file_data.name);
                }

                // Update existing bookmark
                if !dry_run {
                    self.update_bookmark_from_file(
                        &existing,
                        file_data,
                        &settings,
                        base_path_name,
                    )?;
                }
                updated_count += 1;
                println!("Updated bookmark: {}", file_data.name);
            } else {
                // Create new bookmark
                if !dry_run {
                    self.create_bookmark_from_file(file_data, &settings, base_path_name, embeddable)?;
                }
                added_count += 1;
                println!("Added bookmark: {}", file_data.name);
            }
        }

        // Handle delete missing functionality
        if delete_missing {
            let orphaned = self.find_orphaned_bookmarks(&actual_scan_paths, &file_imports)?;
            for bookmark in orphaned {
                if !dry_run {
                    if let Some(id) = bookmark.id {
                        self.repository.delete(id)?;
                        // Best-effort: remove embedding from vector store
                        let _ = self.vector_repository.delete_embedding(id);
                    }
                }
                deleted_count += 1;
                println!(
                    "Deleted orphaned bookmark: {} ({:?})",
                    bookmark.title, bookmark.id
                );
            }
        }

        info!(added = added_count, updated = updated_count, deleted = deleted_count, "File import complete");
        Ok((added_count, updated_count, deleted_count))
    }
}

impl<R: BookmarkRepository> BookmarkServiceImpl<R> {
    /// Find bookmark by name (for duplicate detection)
    fn find_bookmark_by_name(&self, name: &str) -> ApplicationResult<Option<Bookmark>> {
        // Search for bookmarks with matching title (name is stored as title)
        let mut query = BookmarkQuery::new();
        // Quote the search term to handle special characters like hyphens
        query.text_query = Some(format!("\"{}\"", name));

        let results = self.repository.search(&query)?;

        // Find exact title match (case-sensitive)
        for bookmark in results {
            if bookmark.title == name {
                return Ok(Some(bookmark));
            }
        }

        Ok(None)
    }

    /// Create a new bookmark from file import data. Stores file content in url field,
    /// sets file_path/file_mtime/file_hash for source tracking, and generates embeddings
    /// via `Bookmark::get_content_for_embedding()`.
    fn create_bookmark_from_file(
        &self,
        file_data: &FileImportData,
        settings: &crate::config::Settings,
        base_path_name: Option<&str>,
        embeddable: bool,
    ) -> ApplicationResult<Bookmark> {
        use crate::domain::system_tag::SystemTag;

        // Convert content type to system tag
        let system_tag = match file_data.content_type.as_str() {
            "_snip_" => SystemTag::Snippet,
            "_imported_" => SystemTag::Text,
            "_shell_" => SystemTag::Shell,
            "_md_" => SystemTag::Markdown,
            "_env_" => SystemTag::Env,
            "_mem_" => SystemTag::Memory,
            _ => SystemTag::Shell, // Default for unknown types
        };

        // Prepare tags (including system tag)
        let mut all_tags = file_data.tags.clone();
        all_tags.insert(system_tag.to_tag()?);

        // Store file content in URL column as required
        let mut bookmark = BookmarkBuilder::default()
            .id(None)
            .url(file_data.content.clone())
            .title(file_data.name.clone())
            .description(String::new())
            .tags(all_tags)
            .access_count(0)
            .created_at(Some(chrono::Utc::now()))
            .updated_at(chrono::Utc::now())
            .embedding(None)
            .content_hash(None)
            .embeddable(embeddable)
            .file_path(None)
            .file_mtime(None)
            .file_hash(None)
            .build()
            .map_err(|e| ApplicationError::Other(format!("Failed to build bookmark: {}", e)))?;

        // Set file metadata with base path handling
        let file_path_str = if let Some(base_name) = base_path_name {
            // User provided base path - store as relative path with base path variable
            if let Some(base_value) = settings.base_paths.get(base_name) {
                let expanded_base = crate::config::resolve_file_path(settings, base_value);
                let absolute_file_path = file_data.file_path.display().to_string();

                // Since we resolved the scan paths, files should be under the base path
                if let Some(relative_path) = absolute_file_path.strip_prefix(&expanded_base) {
                    let relative_path = relative_path.strip_prefix('/').unwrap_or(relative_path);
                    crate::config::create_file_path_with_base(base_name, relative_path)
                } else {
                    return Err(ApplicationError::Other(format!(
                        "File {} is not under base path {} ({})",
                        absolute_file_path, base_name, expanded_base
                    )));
                }
            } else {
                return Err(ApplicationError::Other(format!(
                    "Base path '{}' not found in configuration",
                    base_name
                )));
            }
        } else {
            // No base path specified - store absolute path
            file_data.file_path.display().to_string()
        };

        bookmark.file_path = Some(file_path_str);
        bookmark.file_mtime = Some(file_data.file_mtime as i32);
        bookmark.file_hash = Some(file_data.file_hash.clone());

        // Calculate content hash from the same string that gets embedded
        let embedding_content = bookmark.get_content_for_embedding();
        bookmark.content_hash = Some(calc_content_hash(&embedding_content));

        bookmark.embedding = None;

        self.repository.add(&mut bookmark)?;

        // Generate and store embedding after bookmark has an ID
        if bookmark.embeddable {
            if let Some(id) = bookmark.id {
                self.upsert_embedding_for_bookmark(id, &embedding_content)?;
            }
        }

        Ok(bookmark)
    }

    /// Update existing bookmark from changed file data. Preserves system tags from existing
    /// bookmark, recalculates content hash, and regenerates embeddings.
    fn update_bookmark_from_file(
        &self,
        existing: &Bookmark,
        file_data: &FileImportData,
        settings: &crate::config::Settings,
        base_path_name: Option<&str>,
    ) -> ApplicationResult<Bookmark> {
        let mut updated = existing.clone();

        // Update content and metadata (content goes in url column for file imports)
        updated.url = file_data.content.clone();
        updated.title = file_data.name.clone();

        // Update file path with base path handling
        let file_path_str = if let Some(base_name) = base_path_name {
            // User provided base path - store as relative path with base path variable
            if let Some(base_value) = settings.base_paths.get(base_name) {
                let expanded_base = crate::config::resolve_file_path(settings, base_value);
                let absolute_file_path = file_data.file_path.display().to_string();

                // Since we resolved the scan paths, files should be under the base path
                if let Some(relative_path) = absolute_file_path.strip_prefix(&expanded_base) {
                    let relative_path = relative_path.strip_prefix('/').unwrap_or(relative_path);
                    crate::config::create_file_path_with_base(base_name, relative_path)
                } else {
                    return Err(ApplicationError::Other(format!(
                        "File {} is not under base path {} ({})",
                        absolute_file_path, base_name, expanded_base
                    )));
                }
            } else {
                return Err(ApplicationError::Other(format!(
                    "Base path '{}' not found in configuration",
                    base_name
                )));
            }
        } else {
            // No base path specified - store absolute path
            file_data.file_path.display().to_string()
        };

        updated.file_path = Some(file_path_str);
        updated.file_mtime = Some(file_data.file_mtime as i32);
        updated.file_hash = Some(file_data.file_hash.clone());

        // Update tags (merge with existing, keeping system tags)
        let mut new_tags = file_data.tags.clone();
        // Preserve system tags from existing bookmark
        for tag in &existing.tags {
            if tag.value().starts_with('_') && tag.value().ends_with('_') {
                new_tags.insert(tag.clone());
            }
        }
        updated.tags = new_tags;

        // Calculate content hash from the same string that gets embedded (after tags are set)
        let embedding_content = updated.get_content_for_embedding();
        updated.content_hash = Some(calc_content_hash(&embedding_content));

        updated.embedding = None;

        self.repository.update(&updated)?;

        // Regenerate embedding in vector repository
        if updated.embeddable {
            if let Some(id) = updated.id {
                self.upsert_embedding_for_bookmark(id, &embedding_content)?;
            }
        }

        Ok(updated)
    }

    /// Find orphaned bookmarks (file_path set but file no longer exists or not found in current scan)
    fn find_orphaned_bookmarks(
        &self,
        import_paths: &[String],
        current_imports: &[FileImportData],
    ) -> ApplicationResult<Vec<Bookmark>> {
        let all_bookmarks = self.repository.get_all()?;
        let mut orphaned = Vec::new();

        // Create a set of currently imported file paths for quick lookup
        let current_file_paths: HashSet<_> = current_imports
            .iter()
            .map(|import| {
                import
                    .file_path
                    .canonicalize()
                    .unwrap_or_else(|_| import.file_path.clone())
            })
            .collect();

        for bookmark in all_bookmarks {
            if let Some(file_path_str) = &bookmark.file_path {
                // Handle base path variables and resolve to absolute path
                let settings = crate::config::load_settings(None).map_err(|e| {
                    ApplicationError::Other(format!("Failed to load settings: {}", e))
                })?;
                let resolved_path = crate::config::resolve_file_path(&settings, file_path_str);
                let path = Path::new(&resolved_path);

                // Check if file still exists at the stored path
                let file_exists = path.exists();

                // Check if this file was found in the current import scan
                let canonical_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
                let found_in_scan = current_file_paths.contains(&canonical_path);

                // If the file doesn't exist OR it wasn't found in the current scan, it's orphaned
                if !file_exists || !found_in_scan {
                    // Verify the file was under one of the import paths
                    let should_delete = import_paths.iter().any(|import_path| {
                        path.starts_with(import_path)
                            || path
                                .canonicalize()
                                .unwrap_or_else(|_| path.to_path_buf())
                                .starts_with(
                                    Path::new(import_path)
                                        .canonicalize()
                                        .unwrap_or_else(|_| Path::new(import_path).to_path_buf()),
                                )
                    });

                    if should_delete {
                        orphaned.push(bookmark);
                    }
                }
            }
        }

        Ok(orphaned)
    }

    /// Check if metadata (tags, name, type) has changed
    fn has_metadata_changed(
        &self,
        existing: &Bookmark,
        file_data: &FileImportData,
    ) -> ApplicationResult<bool> {
        // Check if title changed
        if existing.title != file_data.name {
            return Ok(true);
        }

        // Check if tags changed (ignore system tags for comparison)
        let existing_user_tags: HashSet<_> = existing
            .tags
            .iter()
            .filter(|tag| !tag.value().starts_with('_') || !tag.value().ends_with('_'))
            .cloned()
            .collect();
        let file_user_tags: HashSet<_> = file_data
            .tags
            .iter()
            .filter(|tag| !tag.value().starts_with('_') || !tag.value().ends_with('_'))
            .cloned()
            .collect();

        if existing_user_tags != file_user_tags {
            return Ok(true);
        }

        // Check if content type changed (check system tags)
        let existing_has_shell = existing.tags.iter().any(|tag| tag.value() == "_shell_");
        let existing_has_md = existing.tags.iter().any(|tag| tag.value() == "_md_");
        let file_is_shell = file_data.content_type == "_shell_";
        let file_is_md = file_data.content_type == "_md_";

        if (existing_has_shell != file_is_shell) || (existing_has_md != file_is_md) {
            return Ok(true);
        }

        Ok(false)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::infrastructure::embeddings::dummy_provider::DummyEmbedding;
    use crate::infrastructure::repositories::json_import_repository::JsonImportRepository;
    use crate::util::testing::{init_test_env, setup_test_db, EnvGuard};
    use std::collections::HashSet;

    // Helper function to create a BookmarkServiceImpl with a test repository
    fn create_test_service() -> impl BookmarkService {
        use crate::infrastructure::repositories::null_vector_repository::NullVectorRepository;
        let repository = setup_test_db();
        let arc_repository = Arc::new(repository);
        let embedder = Arc::new(DummyEmbedding);
        let vector_repository = Arc::new(NullVectorRepository);
        BookmarkServiceImpl::new(
            arc_repository,
            embedder,
            vector_repository,
            Arc::new(JsonImportRepository::new()),
        )
    }

    #[test]
    fn given_valid_id_when_get_bookmark_then_returns_correct_bookmark() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let bookmark = service.get_bookmark(1).unwrap();

        // Assert
        assert!(bookmark.is_some(), "Should find bookmark with ID 1");
        let bookmark = bookmark.unwrap();
        assert_eq!(bookmark.id, Some(1));
        assert_eq!(bookmark.url, "https://www.google.com");
        assert_eq!(bookmark.title, "Google");
    }

    #[test]
    fn given_invalid_id_when_get_bookmark_then_returns_none() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let result = service.get_bookmark(999).unwrap();

        // Assert
        assert!(result.is_none(), "Should not find non-existent bookmark");
    }

    #[test]
    fn given_negative_id_when_get_bookmark_then_returns_error() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let result = service.get_bookmark(-1);

        // Assert
        assert!(result.is_err(), "Negative ID should return error");
        match result {
            Err(ApplicationError::Validation(msg)) => {
                assert!(
                    msg.contains("Invalid bookmark ID"),
                    "Error should mention invalid ID"
                );
            }
            _ => panic!("Expected a Validation error"),
        }
    }

    #[test]
    fn given_valid_url_when_get_bookmark_by_url_then_returns_correct_bookmark() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let result = service
            .get_bookmark_by_url("https://www.google.com")
            .unwrap();

        // Assert
        assert!(result.is_some(), "Should find bookmark with URL");
        let bookmark = result.unwrap();
        assert_eq!(bookmark.url, "https://www.google.com");
        assert_eq!(bookmark.title, "Google");
    }

    #[test]
    fn given_new_bookmark_when_add_bookmark_then_creates_and_returns_bookmark() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let url = "https://newbookmark.example.com";
        let title = "New Bookmark";
        let description = "Test description";
        let mut tags = HashSet::new();
        tags.insert(Tag::new("test").unwrap());

        // Act
        let bookmark = service
            .add_bookmark(url, Some(title), Some(description), Some(&tags), false, true)
            .unwrap();

        // Assert
        assert!(
            bookmark.id.is_some(),
            "Bookmark should have ID after adding"
        );
        assert_eq!(bookmark.url, url);
        assert_eq!(bookmark.title, title);
        assert_eq!(bookmark.description, description);
        assert_eq!(bookmark.tags.len(), 1);
        assert!(bookmark.tags.contains(&Tag::new("test").unwrap()));

        // Verify it can be retrieved
        let retrieved = service.get_bookmark(bookmark.id.unwrap()).unwrap().unwrap();
        assert_eq!(retrieved.url, url);
    }

    #[test]
    fn given_existing_url_when_add_bookmark_then_returns_error() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let existing_url = "https://www.google.com";

        // Act
        let result = service.add_bookmark(
            existing_url,
            Some("Title"),
            Some("Description"),
            None,
            false,
            true,
        );

        // Assert
        assert!(result.is_err(), "Adding duplicate URL should fail");
        match result {
            Err(ApplicationError::BookmarkExists(_, url)) => {
                assert_eq!(
                    url, existing_url,
                    "Error message should contain the existing URL"
                );
            }
            _ => panic!("Expected a BookmarkExists error"),
        }
    }

    #[test]
    fn given_existing_bookmark_when_update_fields_then_persists_changes() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let id = 1; // Existing ID from test database

        // Act: fetch, mutate title/description/url, save
        let mut bookmark = service.get_bookmark(id).unwrap().unwrap();
        bookmark.title = "Updated Title".to_string();
        bookmark.description = "Updated Description".to_string();
        bookmark.url = "https://updated-url.example.com".to_string();
        let updated = service.update_bookmark(bookmark, false).unwrap();

        // Assert
        assert_eq!(updated.title, "Updated Title");
        assert_eq!(updated.description, "Updated Description");
        assert_eq!(updated.url, "https://updated-url.example.com");

        // Verify persistence
        let retrieved = service.get_bookmark(id).unwrap().unwrap();
        assert_eq!(retrieved.title, "Updated Title");
        assert_eq!(retrieved.description, "Updated Description");
        assert_eq!(retrieved.url, "https://updated-url.example.com");
    }

    // #[test]
    // #[serial]
    // fn given_non_existent_bookmark_when_update_content_then_returns_error() {
    //     // Arrange
    //     let _env = init_test_env();
    //     let _guard = EnvGuard::new();
    //     let service = create_test_service();
    //
    //     // Act
    //     let result = service.update_bookmark_content(999, "Title", "Description");
    //
    //     // Assert
    //     assert!(
    //         result.is_err(),
    //         "Updating non-existent bookmark should fail"
    //     );
    //     match result {
    //         Err(ApplicationError::BookmarkNotFound(id)) => {
    //             assert_eq!(id, 999);
    //         }
    //         _ => panic!("Expected a BookmarkNotFound error"),
    //     }
    // }

    #[test]
    fn given_existing_bookmark_when_add_tags_then_adds_tags_correctly() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let id = 1; // Using an existing ID from the test database
        let mut tags = HashSet::new();
        tags.insert(Tag::new("newtag").unwrap());

        // Get original tags
        let original = service.get_bookmark(id).unwrap().unwrap();
        let original_tag_count = original.tags.len();

        // Act
        let updated = service.add_tags_to_bookmark(id, &tags).unwrap();

        // Assert
        assert!(updated.tags.contains(&Tag::new("newtag").unwrap()));
        assert_eq!(updated.tags.len(), original_tag_count + 1);

        // Verify changes were persisted
        let retrieved = service.get_bookmark(id).unwrap().unwrap();
        assert!(retrieved.tags.contains(&Tag::new("newtag").unwrap()));
    }

    #[test]
    fn given_existing_bookmark_when_remove_tags_then_removes_tags_correctly() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Find a bookmark with known tags
        let bookmark = service.get_bookmark(1).unwrap().unwrap();
        let tag_to_remove = bookmark.tags.iter().next().unwrap().clone();
        let original_tag_count = bookmark.tags.len();

        // Skip test if no tags to remove
        if original_tag_count == 0 {
            return;
        }

        let mut tags_to_remove = HashSet::new();
        tags_to_remove.insert(tag_to_remove.clone());

        // Act
        let updated = service
            .remove_tags_from_bookmark(1, &tags_to_remove)
            .unwrap();

        // Assert
        assert!(!updated.tags.contains(&tag_to_remove));
        assert_eq!(updated.tags.len(), original_tag_count - 1);

        // Verify changes were persisted
        let retrieved = service.get_bookmark(1).unwrap().unwrap();
        assert!(!retrieved.tags.contains(&tag_to_remove));
    }

    #[test]
    fn given_existing_bookmark_when_replace_tags_then_replaces_all_tags() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let id = 1;

        let mut new_tags = HashSet::new();
        new_tags.insert(Tag::new("replaced1").unwrap());
        new_tags.insert(Tag::new("replaced2").unwrap());

        // Act
        let updated = service.replace_bookmark_tags(id, &new_tags).unwrap();

        // Assert
        assert_eq!(updated.tags.len(), 2);
        assert!(updated.tags.contains(&Tag::new("replaced1").unwrap()));
        assert!(updated.tags.contains(&Tag::new("replaced2").unwrap()));

        // Verify changes were persisted
        let retrieved = service.get_bookmark(id).unwrap().unwrap();
        assert_eq!(retrieved.tags.len(), 2);
        assert!(retrieved.tags.contains(&Tag::new("replaced1").unwrap()));
        assert!(retrieved.tags.contains(&Tag::new("replaced2").unwrap()));
    }

    #[test]
    fn given_existing_bookmark_when_record_access_then_increments_access_count() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let id = 1;

        // Get original access count
        let original = service.get_bookmark(id).unwrap().unwrap();
        let original_count = original.access_count;

        // Act
        let updated = service.record_bookmark_access(id).unwrap();

        // Assert
        assert_eq!(updated.access_count, original_count + 1);

        // Verify changes were persisted
        let retrieved = service.get_bookmark(id).unwrap().unwrap();
        assert_eq!(retrieved.access_count, original_count + 1);
    }

    #[test]
    fn given_test_database_when_delete_bookmark_then_removes_bookmark() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // First add a test bookmark that we can delete
        let url = "https://todelete.example.com";
        let bookmark = service
            .add_bookmark(url, Some("To Delete"), Some("Description"), None, false, true)
            .unwrap();
        let id = bookmark.id.unwrap();

        // Verify it exists
        assert!(service.get_bookmark(id).unwrap().is_some());

        // Act
        let result = service.delete_bookmark(id).unwrap();

        // Assert
        assert!(result, "Delete should return true on success");

        // Verify it was deleted
        assert!(service.get_bookmark(id).unwrap().is_none());
    }

    // #[test]
    // #[serial]
    // fn given_tags_when_search_by_all_tags_then_returns_matching_bookmarks() {
    //     // Arrange
    //     let _env = init_test_env();
    //     let _guard = EnvGuard::new();
    //     let service = create_test_service();
    //
    //     // Create tags that exist in test data
    //     let mut tags = HashSet::new();
    //     tags.insert(Tag::new("aaa").unwrap());
    //     tags.insert(Tag::new("bbb").unwrap());
    //
    //     // Act
    //     let results = service.search_bookmarks_by_all_tags(&tags).unwrap();
    //
    //     // Assert
    //     assert!(
    //         !results.is_empty(),
    //         "Should find bookmarks with all specified tags"
    //     );
    //
    //     // Every result should have ALL the specified tags
    //     for bookmark in &results {
    //         assert!(bookmark.tags.contains(&Tag::new("aaa").unwrap()));
    //         assert!(bookmark.tags.contains(&Tag::new("bbb").unwrap()));
    //     }
    // }
    //
    // #[test]
    // #[serial]
    // fn given_tags_when_search_by_any_tag_then_returns_matching_bookmarks() {
    //     // Arrange
    //     let _env = init_test_env();
    //     let _guard = EnvGuard::new();
    //     let service = create_test_service();
    //
    //     // Create tags that exist in test data
    //     let mut tags = HashSet::new();
    //     tags.insert(Tag::new("aaa").unwrap());
    //     tags.insert(Tag::new("xxx").unwrap()); // different tag
    //
    //     // Act
    //     let results = service.search_bookmarks_by_any_tag(&tags).unwrap();
    //
    //     // Assert
    //     assert!(
    //         !results.is_empty(),
    //         "Should find bookmarks with any of the specified tags"
    //     );
    //
    //     // Every result should have AT LEAST ONE of the specified tags
    //     for bookmark in &results {
    //         assert!(
    //             bookmark.tags.contains(&Tag::new("aaa").unwrap())
    //                 || bookmark.tags.contains(&Tag::new("xxx").unwrap())
    //         );
    //     }
    //
    //     // Results should include more bookmarks than when searching for all tags
    //     let all_tag_results = service.search_bookmarks_by_all_tags(&tags).unwrap();
    //     assert!(results.len() >= all_tag_results.len());
    // }

    #[test]
    fn given_text_query_when_search_by_text_then_returns_matching_bookmarks() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let results = service.search_bookmarks_by_text("Google").unwrap();

        // Assert
        assert!(
            !results.is_empty(),
            "Should find bookmarks containing the text"
        );

        // At least one result should contain the search text
        let has_match = results.iter().any(|b| {
            b.title.contains("Google")
                || b.description.contains("Google")
                || b.url.contains("Google")
        });
        assert!(
            has_match,
            "At least one result should match the search text"
        );
    }

    #[test]
    fn given_test_database_when_get_all_bookmarks_then_returns_all_bookmarks() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let bookmarks = service.get_all_bookmarks(None, None).unwrap();

        // Assert
        assert!(
            !bookmarks.is_empty(),
            "Should return all bookmarks from test database"
        );

        // Check that we get the expected number based on up.sql
        // The test database from up.sql has 11 sample bookmarks
        assert!(
            bookmarks.len() >= 11,
            "Should return at least the bookmarks from up.sql"
        );
    }

    #[test]
    fn given_count_when_get_random_bookmarks_then_returns_random_selection() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();
        let count = 3;

        // Act
        let bookmarks = service.get_random_bookmarks(count).unwrap();

        // Assert
        assert_eq!(
            bookmarks.len(),
            count,
            "Should return exactly the requested number of bookmarks"
        );

        // Get another random selection and verify it's likely different
        // (This is probabilistic, so there's a small chance it could be the same)
        let another_set = service.get_random_bookmarks(count).unwrap();

        // Convert to sets of IDs for comparison
        let first_ids: HashSet<_> = bookmarks.iter().filter_map(|b| b.id).collect();
        let second_ids: HashSet<_> = another_set.iter().filter_map(|b| b.id).collect();

        // With a decent number of bookmarks, it's very unlikely to get the same random selection twice
        // Only assert if we have enough bookmarks in the test database
        let all_bookmarks = service.get_all_bookmarks(None, None).unwrap();
        if all_bookmarks.len() > count * 3 {
            assert_ne!(
                first_ids, second_ids,
                "Random selections should typically be different"
            );
        }
    }

    #[test]
    fn given_test_database_when_get_bookmarks_without_embeddings_then_returns_correct_bookmarks() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Act
        let results = service.get_bookmarks_without_embeddings().unwrap();

        // Assert
        // Verify that all returned bookmarks actually don't have embeddings
        for bookmark in &results {
            assert!(
                bookmark.embedding.is_none(),
                "Returned bookmarks should not have embeddings"
            );
        }
    }

    #[test]
    fn given_bookmark_when_set_embeddable_then_updates_flag() {
        // Arrange
        let _env = init_test_env();
        let _guard = EnvGuard::new();
        let service = create_test_service();

        // Create a test bookmark
        let url = "https://embeddingtest.example.com";
        let bookmark = service
            .add_bookmark(
                url,
                Some("Test Embeddable"),
                Some("Description"),
                None,
                false,
                true,
            )
            .unwrap();
        let id = bookmark.id.unwrap();

        // Verify initial state — default is now true
        assert!(bookmark.embeddable, "Default should be true");

        // Act - Disable embedding
        let updated = service.set_bookmark_embeddable(id, false).unwrap();

        // Assert
        assert!(!updated.embeddable, "Flag should be updated to false");

        // Verify persistence
        let retrieved = service.get_bookmark(id).unwrap().unwrap();
        assert!(!retrieved.embeddable, "Flag should be persisted as false");

        // Act - Re-enable embedding
        let updated_again = service.set_bookmark_embeddable(id, true).unwrap();

        // Assert
        assert!(updated_again.embeddable, "Flag should be updated to true");

        // Verify persistence
        let retrieved_again = service.get_bookmark(id).unwrap().unwrap();
        assert!(
            retrieved_again.embeddable,
            "Flag should be persisted as true"
        );
    }
}
