use super::*;
use crate::storage::indexes::filter::FilterExpr;

// ── SearchBuilder ────────────────────────────────────────────────────

#[test]
fn builder_defaults() {
    let s = Search::builder().query("jazz").build().unwrap();
    assert_eq!(s.entity_kind, EntityKind::Item);
    assert_eq!(s.limit, 20);
    assert_eq!(s.profile.name, "search");
    assert!(s.filters.is_empty());
    assert!(s.diversity.is_none());
    assert!(s.for_user.is_none());
    assert!(s.for_session.is_none());
    assert!(s.within_scope.is_none());
}

#[test]
fn builder_requires_query() {
    let result = Search::builder().build();
    assert!(
        matches!(result, Err(QueryError::InvalidFilter { .. })),
        "build() without query should fail"
    );
}

#[test]
fn builder_text_only() {
    let s = Search::builder().query("rust tutorial").build().unwrap();
    assert!(s.query_text.is_some());
    assert!(s.query_vector.is_none());
}

#[test]
fn builder_vector_only() {
    let vec = vec![0.1_f32, 0.2, 0.3];
    let s = Search::builder().vector(vec.clone()).build().unwrap();
    assert!(s.query_text.is_none());
    assert_eq!(s.query_vector.as_deref(), Some(vec.as_slice()));
}

#[test]
fn builder_hybrid() {
    let s = Search::builder()
        .query("jazz")
        .vector(vec![0.1_f32; 4])
        .build()
        .unwrap();
    assert!(s.query_text.is_some());
    assert!(s.query_vector.is_some());
}

#[test]
fn builder_limit_zero_rejected() {
    let result = Search::builder().query("x").limit(0).build();
    assert!(matches!(result, Err(QueryError::InvalidLimit { .. })));
}

#[test]
fn builder_limit_1001_rejected() {
    let result = Search::builder().query("x").limit(1001).build();
    assert!(matches!(result, Err(QueryError::InvalidLimit { .. })));
}

#[test]
fn builder_limit_1000_accepted() {
    let s = Search::builder().query("x").limit(1000).build().unwrap();
    assert_eq!(s.limit, 1000);
}

#[test]
fn builder_with_filter() {
    let s = Search::builder()
        .query("jazz")
        .filter(FilterExpr::eq("category", "music"))
        .build()
        .unwrap();
    assert_eq!(s.filters.len(), 1);
}

#[test]
fn builder_with_exclude() {
    let s = Search::builder()
        .query("jazz")
        .exclude(vec![EntityId::new(1), EntityId::new(2)])
        .build()
        .unwrap();
    assert_eq!(s.exclude.len(), 2);
}

#[test]
fn builder_with_for_user() {
    let s = Search::builder()
        .query("jazz")
        .for_user(42)
        .build()
        .unwrap();
    assert_eq!(s.for_user, Some(42));
}

#[test]
fn builder_custom_profile() {
    let s = Search::builder()
        .query("jazz")
        .using_profile("trending")
        .build()
        .unwrap();
    assert_eq!(s.profile.name, "trending");
}

// ── WithinScope builder ───────────────────────────────────────────────

#[test]
fn builder_with_within_scope_trending() {
    let s = Search::builder()
        .query("jazz")
        .within(WithinScope::Trending { window_hours: 24 })
        .build()
        .unwrap();
    assert!(matches!(
        s.within_scope,
        Some(WithinScope::Trending { window_hours: 24 })
    ));
}

#[test]
fn builder_with_within_scope_category() {
    let s = Search::builder()
        .query("jazz")
        .within(WithinScope::Category {
            name: "music".into(),
        })
        .build()
        .unwrap();
    assert!(matches!(s.within_scope, Some(WithinScope::Category { .. })));
}

#[test]
fn builder_with_within_scope_following() {
    let s = Search::builder()
        .query("jazz")
        .for_user(42)
        .within(WithinScope::Following)
        .build()
        .unwrap();
    assert!(matches!(s.within_scope, Some(WithinScope::Following)));
}

#[test]
fn builder_with_within_scope_collection() {
    use crate::entities::CollectionId;
    let s = Search::builder()
        .query("jazz")
        .within(WithinScope::Collection {
            id: CollectionId::new(7),
        })
        .build()
        .unwrap();
    assert!(matches!(
        s.within_scope,
        Some(WithinScope::Collection { .. })
    ));
}

#[test]
fn builder_with_within_scope_cohort_trending() {
    let s = Search::builder()
        .query("jazz")
        .within(WithinScope::CohortTrending {
            cohort: "tech".into(),
            window_hours: 12,
        })
        .build()
        .unwrap();
    assert!(matches!(
        s.within_scope,
        Some(WithinScope::CohortTrending { .. })
    ));
}

// ── combined_filter ──────────────────────────────────────────────────

#[test]
fn combined_filter_empty() {
    let s = Search::builder().query("jazz").build().unwrap();
    assert!(s.combined_filter().is_none());
}

#[test]
fn combined_filter_single() {
    let s = Search::builder()
        .query("jazz")
        .filter(FilterExpr::eq("category", "music"))
        .build()
        .unwrap();
    assert!(matches!(
        s.combined_filter(),
        Some(FilterExpr::CategoryEq(_))
    ));
}

#[test]
fn combined_filter_multiple() {
    let s = Search::builder()
        .query("jazz")
        .filter(FilterExpr::eq("category", "music"))
        .filter(FilterExpr::eq("format", "video"))
        .build()
        .unwrap();
    assert!(matches!(s.combined_filter(), Some(FilterExpr::And(_))));
}

// ── SearchResults ────────────────────────────────────────────────────

#[test]
fn results_len_and_empty() {
    let r = SearchResults {
        items: vec![],
        next_cursor: None,
        total_candidates: 0,
        constraints_satisfied: true,
        warnings: vec![],
        session_snapshot: None,
        degradation_level: crate::load::DegradationLevel::Full,
        stats: QueryStats::new("test".to_owned()),
    };
    assert_eq!(r.len(), 0);
    assert!(r.is_empty());
}
