async fn search_bm25(
    State(state): State<AppState>,
    Json(req): Json<SearchRequest>,
) -> Result<Json<SearchResponse>, StatusCode> {
    let limit = req.limit.unwrap_or(10);
    let query_hash = generate_query_hash(&req.query, req.uuid.as_deref(), limit);
    let cache_key = keys::bm25_search(&query_hash);
    let ttl = state.mongo_cache.ttl_search();

    let response = state
        .mongo_cache
        .get_or_fetch(&cache_key, ttl, keys::CATEGORY_SEARCH, || async {
            let pg = PostgresDb::init()
                .await
                .map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?;

            let bm25_results = pg
                .search_bm25(&req.query, req.uuid.as_deref(), limit)
                .await?;

            let results: Vec<SearchResult> = bm25_results
                .into_iter()
                .map(|r| SearchResult {
                    uuid: r.uuid,
                    chunk_id: r.chunk_id,
                    chunk_type: r.chunk_type,
                    start_time: r.start_time,
                    end_time: r.end_time,
                    text: r.text,
                    score: r.bm25_score,
                })
                .collect();

            Ok::<SearchResponse, anyhow::Error>(SearchResponse {
                results,
                query: req.query.clone(),
            })
        })
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(response))
}

async fn n8n_search_bm25(
    State(state): State<AppState>,
    Json(req): Json<SearchRequest>,
) -> Result<Json<N8nSearchResponse>, StatusCode> {
    let limit = req.limit.unwrap_or(10);
    let query_hash = generate_query_hash(&req.query, req.uuid.as_deref(), limit);
    let cache_key = keys::n8n_bm25_search(&query_hash);
    let ttl = state.mongo_cache.ttl_search();

    let response = state
        .mongo_cache
        .get_or_fetch(&cache_key, ttl, keys::CATEGORY_N8N_SEARCH, || async {
            let pg = PostgresDb::init()
                .await
                .map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?;

            let bm25_results = pg
                .search_bm25(&req.query, req.uuid.as_deref(), limit)
                .await?;

            let mut hits = Vec::new();

            for r in bm25_results {
                if let Some(chunk) = pg
                    .get_chunk_by_chunk_id_and_uuid(&r.chunk_id, &r.uuid)
                    .await
                    .ok()
                    .flatten()
                {
                    let text = r.text; // Use text from BM25 result
                    let title = extract_title_from_content(&chunk.content);

                    let file_path = if chunk.uuid.is_empty() {
                        None
                    } else {
                        let video = pg.get_video_by_uuid(&chunk.uuid).await.ok().flatten();
                        video.map(|v| v.file_path)
                    };

                    hits.push(N8nSearchHit {
                        id: chunk.chunk_id.clone(),
                        vid: chunk.uuid.clone(),
                        start: chunk.start_time().seconds(),
                        end: chunk.end_time().seconds(),
                        title: if title.is_empty() {
                            format!("Chunk {}", chunk.chunk_id)
                        } else {
                            title
                        },
                        text,
                        score: r.bm25_score,
                        file_path,
                    });
                }
            }

            Ok::<N8nSearchResponse, anyhow::Error>(N8nSearchResponse {
                query: req.query.clone(),
                count: hits.len(),
                hits,
            })
        })
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok(Json(response))
}