use super::*;

#[test]
fn exported_signal_json_line_format() {
    let sig = ExportedSignal {
        entity_id: 42,
        signal_type: "view".to_string(),
        weight: 1.0,
        timestamp_ns: 1_700_000_000_000_000_000,
        user_id: None,
        session_id: None,
        annotation: None,
    };
    let line = sig.to_json_line();
    assert!(line.starts_with('{'));
    assert!(line.ends_with('}'));
    assert!(line.contains("\"entity_id\":42"));
    assert!(line.contains("\"signal_type\":\"view\""));
    assert!(line.contains("\"weight\":1"));
    assert!(line.contains("\"timestamp_ns\":1700000000000000000"));
    // Anonymous signals must NOT include user/session/annotation keys.
    assert!(!line.contains("user_id"));
    assert!(!line.contains("session_id"));
    assert!(!line.contains("annotation"));
}

#[test]
fn export_request_time_range_constructor() {
    let req = ExportRequest::time_range(100, 200);
    assert_eq!(req.since, Some(100));
    assert_eq!(req.until, Some(200));
    assert!(req.signal_types.is_empty());
    assert!(req.user_id.is_none());
    assert!(req.limit.is_none());
    assert_eq!(req.format, ExportFormat::JsonLines);
}

#[test]
fn export_request_signals_in_range_constructor() {
    let req = ExportRequest::signals_in_range(vec!["view".into(), "like".into()], 100, 200);
    assert_eq!(req.signal_types.len(), 2);
    assert_eq!(req.since, Some(100));
}

#[test]
fn exported_signal_json_line_weight_precision() {
    let sig = ExportedSignal {
        entity_id: 1,
        signal_type: "like".to_string(),
        weight: 2.5,
        timestamp_ns: 100,
        user_id: None,
        session_id: None,
        annotation: None,
    };
    let line = sig.to_json_line();
    assert!(line.contains("\"weight\":2.5"));
}

#[test]
fn exported_signal_json_line_with_session_context() {
    let sig = ExportedSignal {
        entity_id: 42,
        signal_type: "view".to_string(),
        weight: 1.0,
        timestamp_ns: 1234,
        user_id: Some(7),
        session_id: Some(99),
        annotation: Some("good content".to_string()),
    };
    let line = sig.to_json_line();
    assert!(line.contains("\"user_id\":7"));
    assert!(line.contains("\"session_id\":99"));
    assert!(line.contains("\"annotation\":\"good content\""));
}

#[test]
fn exported_signal_json_line_partial_context() {
    let sig = ExportedSignal {
        entity_id: 10,
        signal_type: "like".to_string(),
        weight: 1.0,
        timestamp_ns: 500,
        user_id: Some(3),
        session_id: None,
        annotation: None,
    };
    let line = sig.to_json_line();
    assert!(line.contains("\"user_id\":3"));
    assert!(!line.contains("session_id"));
    assert!(!line.contains("annotation"));
}

#[test]
fn cosine_distance_identical_vectors() {
    let a = vec![1.0_f32, 0.0, 0.0];
    let b = vec![1.0_f32, 0.0, 0.0];
    let dist = cosine_distance(&a, &b);
    assert!(dist.abs() < 1e-10);
}

#[test]
fn cosine_distance_orthogonal_vectors() {
    let a = vec![1.0_f32, 0.0];
    let b = vec![0.0_f32, 1.0];
    let dist = cosine_distance(&a, &b);
    assert!((dist - 1.0).abs() < 1e-10);
}

#[test]
fn cosine_distance_opposite_vectors() {
    let a = vec![1.0_f32, 0.0];
    let b = vec![-1.0_f32, 0.0];
    let dist = cosine_distance(&a, &b);
    assert!((dist - 2.0).abs() < 1e-10);
}

#[test]
fn cosine_distance_zero_vector() {
    let a = vec![0.0_f32, 0.0];
    let b = vec![1.0_f32, 0.0];
    let dist = cosine_distance(&a, &b);
    assert!(dist.abs() < 1e-10);
}

#[test]
fn export_limit_exceeds_max_returns_error() {
    use crate::TidalDb;

    let db = TidalDb::builder().ephemeral().open().unwrap();
    let req = ExportRequest {
        user_id: None,
        signal_types: Vec::new(),
        since: Some(0),
        until: Some(1),
        format: ExportFormat::JsonLines,
        limit: Some(ExportRequest::MAX_EXPORT_LIMIT + 1),
    };
    let err = db.export_signals(&req).unwrap_err();
    assert!(
        err.to_string().contains("exceeds maximum"),
        "expected limit error, got: {err}"
    );
    db.close().unwrap();
}
