//! HTTP integration tests for DTO validation.
//!
//! Coverage:
//! - Confidence validation (0.0 to 1.0 range)
//! - Weight validation (0.0 to 1.0 range)
//! - Hex string validation (length and character validation)
//! - Conflict score validation (0.0 to 1.0 range)
//! - Query parameter validation
//! - Error 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;

// ============================================================================
// Confidence Validation Tests
// ============================================================================

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

    // Test confidence > 1.0
    let invalid_assertion = json!({
        "subject": "Tesla",
        "predicate": "has_revenue",
        "object": {"type": "Number", "value": 100.0},
        "confidence": 1.5,
        "signatures": [{
            "agent_id": "a".repeat(64),
            "signature": "b".repeat(128),
            "timestamp": 1234567890
        }],
        "source_hash": "c".repeat(64)
    });

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

    let response = app.clone().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("Confidence must be between 0.0 and 1.0"));

    // Test confidence < 0.0
    let invalid_assertion = json!({
        "subject": "Tesla",
        "predicate": "has_revenue",
        "object": {"type": "Number", "value": 100.0},
        "confidence": -0.5,
        "signatures": [{
            "agent_id": "a".repeat(64),
            "signature": "b".repeat(128),
            "timestamp": 1234567890
        }],
        "source_hash": "c".repeat(64)
    });

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&invalid_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("Confidence must be between 0.0 and 1.0"));
}

// ============================================================================
// Weight Validation Tests
// ============================================================================

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

    // Test weight > 1.0
    let invalid_vote = json!({
        "assertion_hash": "a".repeat(64),
        "agent_id": "b".repeat(64),
        "weight": 1.5,
        "signature": "c".repeat(128)
    });

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

    let response = app.clone().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"));

    // Test weight < 0.0
    let invalid_vote = json!({
        "assertion_hash": "a".repeat(64),
        "agent_id": "b".repeat(64),
        "weight": -0.5,
        "signature": "c".repeat(128)
    });

    let request = Request::builder()
        .uri("/v1/vote")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&invalid_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"));
}

// ============================================================================
// Hex Validation Tests
// ============================================================================

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

    // Test assertion with wrong-length source_hash
    let invalid_assertion = json!({
        "subject": "Tesla",
        "predicate": "has_revenue",
        "object": {"type": "Number", "value": 100.0},
        "confidence": 0.9,
        "signatures": [{
            "agent_id": "a".repeat(64),
            "signature": "b".repeat(128),
            "timestamp": 1234567890
        }],
        "source_hash": "abc"  // Too short
    });

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&invalid_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");
    let error_msg = json["error"].as_str().expect("error");
    assert!(error_msg.contains("Expected 64 hex characters"));
    assert!(error_msg.contains("got 3"));
}

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

    // Test assertion with valid hex lengths
    // Note: This will succeed in parsing/validation but may fail in the ingest worker
    // We're primarily testing that hex validation accepts correct-length strings
    let valid_assertion = json!({
        "subject": "Tesla",
        "predicate": "has_revenue",
        "object": {"type": "Number", "value": 100.0},
        "confidence": 0.9,
        "signatures": [{
            "agent_id": "a".repeat(64),
            "signature": "b".repeat(128),
            "timestamp": 1234567890
        }],
        "source_hash": "c".repeat(64)
    });

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

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

    // Accept either 201 (success) or 500 (ingest worker not running in test)
    // We're primarily testing that the hex validation doesn't reject valid lengths
    assert!(
        response.status() == StatusCode::CREATED
            || response.status() == StatusCode::INTERNAL_SERVER_ERROR,
        "Expected 201 or 500, got {}",
        response.status()
    );
}

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

    // Test assertion with invalid hex characters
    let invalid_assertion = json!({
        "subject": "Tesla",
        "predicate": "has_revenue",
        "object": {"type": "Number", "value": 100.0},
        "confidence": 0.9,
        "signatures": [{
            "agent_id": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",  // 'z' is not a valid hex character (64 z's)
            "signature": "b".repeat(128),
            "timestamp": 1234567890
        }],
        "source_hash": "c".repeat(64)
    });

    let request = Request::builder()
        .uri("/v1/assert")
        .method("POST")
        .header("content-type", "application/json")
        .body(Body::from(serde_json::to_vec(&invalid_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");
    let error = json["error"].as_str().expect("error");
    // The error could be "Invalid hex encoding" or similar - just check it's a bad request error
    assert!(!error.is_empty(), "Error message should not be empty");
}

// ============================================================================
// Query Parameter Validation Tests
// ============================================================================

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

    let request = Request::builder()
        .uri("/v1/query?subject=nonexistent")
        .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["assertions"], json!([]));
    assert_eq!(json["total_count"], 0);
    assert_eq!(json["has_more"], false);
}

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

    // Test: min_conflict_score > 1.0 (invalid)
    let request = Request::builder()
        .uri("/v1/query?subject=Test&predicate=test&min_conflict_score=1.5")
        .method("GET")
        .body(Body::empty())
        .expect("Request");

    let response = app.clone().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("min_conflict_score must be between 0.0 and 1.0"));

    // Test: min_conflict_score < 0.0 (invalid)
    let request = Request::builder()
        .uri("/v1/query?subject=Test&predicate=test&min_conflict_score=-0.1")
        .method("GET")
        .body(Body::empty())
        .expect("Request");

    let response = app.clone().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("min_conflict_score must be between 0.0 and 1.0"));

    // Test: max_conflict_score > 1.0 (invalid)
    let request = Request::builder()
        .uri("/v1/query?subject=Test&predicate=test&max_conflict_score=2.0")
        .method("GET")
        .body(Body::empty())
        .expect("Request");

    let response = app.clone().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("max_conflict_score must be between 0.0 and 1.0"));

    // Test: valid conflict scores (should succeed even if no results)
    let request = Request::builder()
        .uri("/v1/query?subject=Test&predicate=test&min_conflict_score=0.3&max_conflict_score=0.7")
        .method("GET")
        .body(Body::empty())
        .expect("Request");

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