//! HTTP integration tests for read-your-writes consistency.
//!
//! These tests verify that after POST /assert returns 201, an immediate
//! GET /query returns the assertion without waiting for background indexing.
//!
//! The MemTable provides this consistency by storing assertions after WAL
//! commit, and merging them with KVStore results during query.

#![allow(clippy::expect_used)]

mod common;

use axum::{
    body::Body,
    http::{Request, StatusCode},
};
use tower::ServiceExt;

use stemedb_api::create_router;

// ============================================================================
// Read-Your-Writes Consistency Tests
// ============================================================================

#[tokio::test]
async fn test_read_your_writes_immediate() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    // Create a unique subject for this test
    let subject = format!(
        "TestSubject_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time")
            .as_nanos()
    );
    let predicate = "test_predicate";

    // 1. POST /assert with unique subject
    let assertion_json = common::create_signed_assertion_json(&subject, predicate, 42.0);

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("Content-Type", "application/json")
        .body(Body::from(assertion_json.to_string()))
        .expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::CREATED, "POST /assert should return 201");

    // Parse the response to get the hash
    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let create_response: serde_json::Value = serde_json::from_slice(&body).expect("JSON");
    let created_hash = create_response["hash"].as_str().expect("hash field");

    // 2. IMMEDIATELY query for the same subject (no sleep!)
    let query_uri = format!("/v1/query?subject={}", subject);
    let request =
        Request::builder().uri(&query_uri).method("GET").body(Body::empty()).expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK, "GET /query should return 200");

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    // 3. Assert the assertion is returned immediately
    let assertions = query_result["assertions"].as_array().expect("assertions array");
    assert!(!assertions.is_empty(), "Query should return the just-created assertion immediately");

    // Verify we got the correct assertion
    let found = assertions.iter().any(|a| a["subject"].as_str() == Some(subject.as_str()));
    assert!(found, "The queried assertion should match the created subject");

    // Note: resolved_hash is only set for MV hits; for MemTable lookups it may not be set
    // So we just verify we found the assertion by subject
    let _ = created_hash; // Suppress unused variable warning
}

#[tokio::test]
async fn test_read_your_writes_with_predicate() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    // Create unique identifiers
    let subject = format!(
        "Company_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time")
            .as_nanos()
    );
    let predicate = "revenue";

    // 1. POST /assert
    let assertion_json = common::create_signed_assertion_json(&subject, predicate, 1_000_000.0);

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("Content-Type", "application/json")
        .body(Body::from(assertion_json.to_string()))
        .expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::CREATED);

    // 2. IMMEDIATELY query with both subject and predicate
    let query_uri = format!("/v1/query?subject={}&predicate={}", subject, predicate);
    let request =
        Request::builder().uri(&query_uri).method("GET").body(Body::empty()).expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    // 3. Verify assertion is found
    let assertions = query_result["assertions"].as_array().expect("assertions array");
    assert!(!assertions.is_empty(), "Query should find the assertion immediately");

    let found = assertions.iter().any(|a| {
        a["subject"].as_str() == Some(subject.as_str())
            && a["predicate"].as_str() == Some(predicate)
    });
    assert!(found, "The assertion should match subject and predicate");
}

#[tokio::test]
async fn test_read_your_writes_multiple_assertions() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    // Create unique subject
    let subject = format!(
        "MultiAssert_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time")
            .as_nanos()
    );

    // Create multiple assertions with different predicates
    let predicates = vec!["revenue", "profit", "employees"];
    let values = vec![100.0, 20.0, 50.0];

    for (predicate, value) in predicates.iter().zip(values.iter()) {
        let assertion_json = common::create_signed_assertion_json(&subject, predicate, *value);

        let request = Request::builder()
            .uri("/v1/assert")
            .method("POST")
            .header("Content-Type", "application/json")
            .body(Body::from(assertion_json.to_string()))
            .expect("Request");

        let response = app.clone().oneshot(request).await.expect("Request");
        assert_eq!(response.status(), StatusCode::CREATED);
    }

    // Query for all assertions with this subject
    let query_uri = format!("/v1/query?subject={}", subject);
    let request =
        Request::builder().uri(&query_uri).method("GET").body(Body::empty()).expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    let assertions = query_result["assertions"].as_array().expect("assertions array");
    assert_eq!(assertions.len(), 3, "Should find all 3 assertions immediately");

    // Verify each predicate was found
    for predicate in &predicates {
        let found = assertions.iter().any(|a| a["predicate"].as_str() == Some(*predicate));
        assert!(found, "Should find assertion with predicate: {}", predicate);
    }
}

#[tokio::test]
async fn test_read_your_writes_does_not_affect_other_subjects() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("time")
        .as_nanos();

    let subject1 = format!("Subject1_{}", timestamp);
    let subject2 = format!("Subject2_{}", timestamp);

    // Create assertion for subject1
    let assertion_json = common::create_signed_assertion_json(&subject1, "test", 1.0);

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("Content-Type", "application/json")
        .body(Body::from(assertion_json.to_string()))
        .expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::CREATED);

    // Query for subject2 (should be empty)
    let query_uri = format!("/v1/query?subject={}", subject2);
    let request =
        Request::builder().uri(&query_uri).method("GET").body(Body::empty()).expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    assert_eq!(response.status(), StatusCode::OK);

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    let assertions = query_result["assertions"].as_array().expect("assertions array");
    assert!(assertions.is_empty(), "Subject2 should have no assertions");

    // Query for subject1 (should have one assertion)
    let query_uri = format!("/v1/query?subject={}", subject1);
    let request =
        Request::builder().uri(&query_uri).method("GET").body(Body::empty()).expect("Request");

    let response = app.clone().oneshot(request).await.expect("Request");
    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let query_result: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    let assertions = query_result["assertions"].as_array().expect("assertions array");
    assert_eq!(assertions.len(), 1, "Subject1 should have one assertion");
}
