//! HTTP integration tests for basic endpoint functionality.
//!
//! Coverage:
//! - GET /v1/health - Health check endpoint
//! - Basic response structure validation
//! - Server availability tests
//! - Real TCP listener tests (ConnectInfo injection)

#![allow(clippy::expect_used)]

mod common;

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

use stemedb_api::create_router;

// ============================================================================
// Health Check Tests
// ============================================================================

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

    let request =
        Request::builder().uri("/v1/health").method("GET").body(Body::empty()).expect("Request");

    let response = app.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 json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");

    assert_eq!(json["status"], "healthy");
    assert!(json["version"].is_string());
    assert_eq!(json["assertions_count"], 0);
}

/// Test health check over a real TCP connection.
///
/// This catches the ConnectInfo<SocketAddr> injection bug where
/// rate_limit_middleware requires ConnectInfo but the server didn't
/// provide it via into_make_service_with_connect_info().
#[tokio::test]
async fn test_health_check_over_tcp() {
    use std::net::SocketAddr;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};

    let env = common::create_test_env().await;
    let app = create_router(env.state);

    // Bind to a random available port
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind");
    let addr = listener.local_addr().expect("local_addr");

    // Serve with ConnectInfo injection (the fix for the 500 bug)
    tokio::spawn(async move {
        axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
            .await
            .expect("server");
    });

    // Give the server a moment to start
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;

    // Make a raw HTTP/1.1 request over TCP
    let mut stream = tokio::net::TcpStream::connect(addr).await.expect("connect");
    let request = format!("GET /v1/health HTTP/1.1\r\nHost: {}\r\nConnection: close\r\n\r\n", addr);
    stream.write_all(request.as_bytes()).await.expect("write");

    let mut response = String::new();
    stream.read_to_string(&mut response).await.expect("read");

    // Verify we got 200 OK, not 500
    assert!(
        response.starts_with("HTTP/1.1 200"),
        "health check over TCP should return 200, not 500 (ConnectInfo must be injected). Got: {}",
        response.lines().next().unwrap_or("empty")
    );

    // Extract JSON body (after the blank line separating headers from body)
    let body = response.split("\r\n\r\n").nth(1).expect("response body");
    let json: serde_json::Value = serde_json::from_str(body).expect("json parse");
    assert_eq!(json["status"], "healthy");
}

// ============================================================================
// Signature Verification Tests (pre-WAL validation)
// ============================================================================

/// Test: POST /v1/assert with invalid signatures returns 400 (not 201).
///
/// Regression test for the "assert returns 201 but data is silently dropped" bug.
/// Previously, the API accepted structurally valid but cryptographically invalid
/// signatures, wrote them to the WAL, and returned 201. The IngestWorker would
/// then silently reject them, permanently blocking the ingestion pipeline.
#[tokio::test]
async fn test_assert_invalid_signature_returns_400() {
    use serde_json::json;

    let env = common::create_test_env().await;
    let app = create_router(env.state);

    // Construct assertion with structurally valid but cryptographically invalid signature.
    // agent_id is a SHA-256 hash (not a valid Ed25519 public key).
    // signature is random 64 bytes.
    let body = json!({
        "subject": "test/bug_regression",
        "predicate": "has_value",
        "object": {"type": "Text", "value": "hello"},
        "confidence": 0.9,
        "source_hash": "0".repeat(64),
        "signatures": [{
            "agent_id": "a".repeat(64),
            "signature": "b".repeat(128),
            "timestamp": 1700000000
        }],
        "timestamp": 1700000000
    });

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

    let response = app.oneshot(request).await.expect("Request");
    assert_eq!(
        response.status(),
        StatusCode::BAD_REQUEST,
        "Invalid signature should return 400, not 201"
    );

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

    // Verify error message mentions signature
    let error_msg = json["error"].as_str().unwrap_or("");
    assert!(
        error_msg.contains("Signature") || error_msg.contains("signature"),
        "Error should mention signature failure, got: {}",
        error_msg
    );
}

/// Test: POST /v1/assert with valid Ed25519 signature returns 201.
#[tokio::test]
async fn test_assert_valid_signature_returns_201() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let body = common::create_signed_assertion_json("test/valid", "has_value", 42.0);

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

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

    let body = axum::body::to_bytes(response.into_body(), usize::MAX).await.expect("Body");
    let json: serde_json::Value = serde_json::from_slice(&body).expect("JSON");
    assert_eq!(json["status"], "created");
}

/// Test: POST /v1/assert with null byte in subject returns 400.
#[tokio::test]
async fn test_assert_null_byte_subject_returns_400() {
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    // Use a properly signed assertion but with null byte in subject
    let body = common::create_signed_assertion_json("test\x00injected", "has_value", 1.0);

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

    let response = app.oneshot(request).await.expect("Request");
    // Should fail with 400 due to null byte in subject
    assert_eq!(
        response.status(),
        StatusCode::BAD_REQUEST,
        "Null byte in subject should return 400"
    );
}
