//! HTTP integration tests for CRUD operations.
//!
//! Coverage:
//! - POST /v1/assert - Assertion creation with signatures
//! - POST /v1/vote - Vote submission with weight validation
//! - Error responses (400 Bad Request)
//! - Response structure validation

#![allow(clippy::expect_used)]

mod common;

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

use stemedb_api::create_router;

// ============================================================================
// POST /v1/assert - Create Assertion Tests
// ============================================================================

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

    let subject = "Test_Entity";
    let predicate = "test_property";
    let message = format!("{}:{}", subject, predicate);
    let (agent_id, signature) = common::sign_message(&message);

    let assertion = json!({
        "subject": subject,
        "predicate": predicate,
        "object": {"type": "Number", "value": 42.0},
        "confidence": 0.95,
        "signatures": [{
            "agent_id": hex::encode(agent_id),
            "signature": hex::encode(signature),
            "timestamp": 1234567890
        }],
        "source_hash": hex::encode([0u8; 32])
    });

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&assertion).expect("JSON serialization")))
        .expect("Failed to build request");

    let response = app.oneshot(request).await.expect("Request failed");

    assert_eq!(response.status(), StatusCode::CREATED);

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

    assert_eq!(json["status"], "created");
    assert!(json["hash"].is_string());
    assert_eq!(json["hash"].as_str().expect("hash").len(), 64); // BLAKE3 = 32 bytes = 64 hex
}

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

    let (agent_id, signature) = common::sign_message("Test:test");

    let assertion = json!({
        "subject": "Test",
        "predicate": "test",
        "object": {"type": "Text", "value": "test_value"},
        "confidence": 0.8,
        "signatures": [{
            "agent_id": hex::encode(agent_id),
            "signature": hex::encode(signature),
            "timestamp": 1000000000
        }],
        "source_hash": hex::encode([1u8; 32])
    });

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

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

    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 parse");

    // Verify hash is present and correctly formatted
    let hash = json["hash"].as_str().expect("hash should be present");
    assert_eq!(hash.len(), 64);
    assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}

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

    let assertion = json!({
        "subject": "Test",
        "predicate": "test",
        "object": {"type": "Number", "value": 1.0},
        "confidence": 0.9,
        "signatures": [], // Empty signatures
        "source_hash": hex::encode([0u8; 32])
    });

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

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

    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!(json["error"]
        .as_str()
        .expect("error message")
        .contains("At least one signature is required"));
}

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

    let (agent_id, signature) = common::sign_message("test");

    let assertion = json!({
        "subject": "Test",
        "predicate": "test",
        "object": {"type": "Number", "value": 1.0},
        "confidence": 2.0, // Invalid: > 1.0
        "signatures": [{
            "agent_id": hex::encode(agent_id),
            "signature": hex::encode(signature),
            "timestamp": 1000
        }],
        "source_hash": hex::encode([0u8; 32])
    });

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

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

    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 response structure matches ErrorResponse DTO
    assert!(json.get("error").is_some());
    assert!(json.get("code").is_some());
    assert!(json["error"].as_str().expect("error message").contains("Confidence"));
}

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

    let (agent_id, signature) = common::sign_message("test");

    let assertion = json!({
        "subject": "Test",
        "predicate": "test",
        "object": {"type": "Number", "value": 1.0},
        "confidence": 0.9,
        "signatures": [{
            "agent_id": hex::encode(agent_id),
            "signature": hex::encode(signature),
            "timestamp": 1000
        }],
        "source_hash": "abc" // Too short - should be 64 hex chars
    });

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

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

    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!(json["error"].as_str().expect("error").contains("Expected 64 hex characters"));
}

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

    // Invalid JSON payload
    let invalid_json = b"{invalid json}";

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(invalid_json.to_vec()))
        .expect("Request");

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

// ============================================================================
// POST /v1/vote - Submit Vote Tests
// ============================================================================

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

    let (agent_id, signature) = common::sign_message("vote_message");

    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 0.75,
        "signature": hex::encode(signature)
    });

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

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

    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");
    assert!(json["hash"].is_string());
    assert_eq!(json["hash"].as_str().expect("hash").len(), 64);
}

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

    let (agent_id, signature) = common::sign_message("test");

    let vote = json!({
        "assertion_hash": hex::encode([1u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 1.0,
        "signature": hex::encode(signature)
    });

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

    let response = app.oneshot(request).await.expect("Request");
    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 response structure matches CreateResponse
    assert!(json.get("hash").is_some());
    assert!(json.get("status").is_some());
    assert_eq!(json.as_object().expect("object").len(), 2);
}

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

    let (agent_id, signature) = common::sign_message("test");

    // Test weight > 1.0
    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 1.5,
        "signature": hex::encode(signature)
    });

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

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

    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!(json["error"].as_str().expect("error").contains("Weight must be between 0.0 and 1.0"));
}

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

    let (agent_id, signature) = common::sign_message("vote_with_provenance");

    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 0.9,
        "signature": hex::encode(signature),
        "source_url": "https://example.com/article",
        "observed_context": "The study found that coffee consumption..."
    });

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

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

    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");
    assert!(json["hash"].is_string());
    assert_eq!(json["hash"].as_str().expect("hash").len(), 64);
}

#[tokio::test]
async fn test_vote_without_provenance_fields_backward_compat() {
    // Ensure votes without provenance fields still work (backward compatibility)
    let env = common::create_test_env().await;
    let app = create_router(env.state);

    let (agent_id, signature) = common::sign_message("vote_no_provenance");

    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 0.8,
        "signature": hex::encode(signature)
        // No source_url or observed_context
    });

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

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

    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");
    assert!(json["hash"].is_string());
}

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

    let (agent_id, signature) = common::sign_message("test");

    // Test source_url > 2048 characters
    let long_url = format!("https://example.com/{}", "x".repeat(2049));
    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 0.8,
        "signature": hex::encode(signature),
        "source_url": long_url
    });

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

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

    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!(json["error"].as_str().expect("error").contains("2048"));
}

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

    let (agent_id, signature) = common::sign_message("test");

    // Test empty source_url
    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 0.8,
        "signature": hex::encode(signature),
        "source_url": ""
    });

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

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

    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!(json["error"].as_str().expect("error").contains("empty"));
}

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

    let (agent_id, signature) = common::sign_message("test");

    // Test observed_context > 64KB
    let large_context = "c".repeat(65537);
    let vote = json!({
        "assertion_hash": hex::encode([0u8; 32]),
        "agent_id": hex::encode(agent_id),
        "weight": 0.8,
        "signature": hex::encode(signature),
        "observed_context": large_context
    });

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

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

    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!(json["error"].as_str().expect("error").contains("64KB"));
}
