//! Boundary and edge-case validation tests.
//!
//! Tests for infinite confidence/weight, future timestamps,
//! and boundary values (0.0, 1.0) for confidence.

use super::*;
use crate::error::IngestError;

/// Test: Assertions with infinite confidence are rejected.
#[tokio::test]
async fn test_rejects_infinite_confidence() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();
    let message = "Test:inf_confidence";
    let signature = signing_key.sign(message.as_bytes());

    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "inf_confidence".to_string(),
        object: ObjectValue::Text("test".to_string()),
        parent_hash: None,
        source_hash: [0u8; 32],
        source_class: SourceClass::Expert,
        visual_hash: None,
        epoch: None,
        source_metadata: None,
        lifecycle: LifecycleStage::Proposed,
        signatures: vec![SignatureEntry {
            version: 1,
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp: 1000,
        }],
        confidence: f32::INFINITY,
        timestamp: 1000,
        hlc_timestamp: HlcTimestamp::default(),
        vector: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_err(), "Should reject infinite confidence");

    let err = result.unwrap_err();
    assert!(
        matches!(err, IngestError::InputValidation(_)),
        "Error should be InputValidation, got: {:?}",
        err
    );
    assert!(err.to_string().contains("infinite"));
}

/// Test: Votes with NaN weight are rejected.
#[tokio::test]
async fn test_rejects_nan_vote_weight() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let vote = Vote {
        assertion_hash: [1u8; 32],
        agent_id: [2u8; 32],
        weight: f32::NAN,
        signature: [3u8; 64],
        timestamp: 1000,
        source_url: None,
        observed_context: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_vote(&vote).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_err(), "Should reject NaN vote weight");

    let err = result.unwrap_err();
    assert!(
        matches!(err, IngestError::InputValidation(_)),
        "Error should be InputValidation, got: {:?}",
        err
    );
    assert!(err.to_string().contains("NaN"));
}

/// Test: Votes with infinite weight are rejected.
#[tokio::test]
async fn test_rejects_infinite_vote_weight() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let vote = Vote {
        assertion_hash: [1u8; 32],
        agent_id: [2u8; 32],
        weight: f32::INFINITY,
        signature: [3u8; 64],
        timestamp: 1000,
        source_url: None,
        observed_context: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_vote(&vote).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_err(), "Should reject infinite vote weight");

    let err = result.unwrap_err();
    assert!(
        matches!(err, IngestError::InputValidation(_)),
        "Error should be InputValidation, got: {:?}",
        err
    );
    assert!(err.to_string().contains("infinite"));
}

/// Test: Assertions with timestamp far in the future are rejected.
#[tokio::test]
async fn test_rejects_future_timestamp() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();
    let message = "Test:future";
    let signature = signing_key.sign(message.as_bytes());

    // Create timestamp 2 hours in the future (should be rejected)
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);
    let future_timestamp = now + 7200; // 2 hours ahead

    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "future".to_string(),
        object: ObjectValue::Text("test".to_string()),
        parent_hash: None,
        source_hash: [0u8; 32],
        source_class: SourceClass::Expert,
        visual_hash: None,
        epoch: None,
        source_metadata: None,
        lifecycle: LifecycleStage::Proposed,
        signatures: vec![SignatureEntry {
            version: 1,
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp: 1000,
        }],
        confidence: 0.9,
        timestamp: future_timestamp,
        hlc_timestamp: HlcTimestamp::default(),
        vector: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_err(), "Should reject timestamp more than 1 hour in future");

    let err = result.unwrap_err();
    assert!(
        matches!(err, IngestError::InputValidation(_)),
        "Error should be InputValidation, got: {:?}",
        err
    );
    assert!(err.to_string().contains("timestamp"));
}

/// Test: Assertions with timestamp slightly in the future (< 1 hour) are accepted.
#[tokio::test]
async fn test_accepts_near_future_timestamp() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();
    let message = "Test:near_future";
    let signature = signing_key.sign(message.as_bytes());

    // Create timestamp 30 minutes in the future (should be accepted)
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);
    let near_future_timestamp = now + 1800; // 30 minutes ahead

    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "near_future".to_string(),
        object: ObjectValue::Text("test".to_string()),
        parent_hash: None,
        source_hash: [0u8; 32],
        source_class: SourceClass::Expert,
        visual_hash: None,
        epoch: None,
        source_metadata: None,
        lifecycle: LifecycleStage::Proposed,
        signatures: vec![SignatureEntry {
            version: 1,
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp: 1000,
        }],
        confidence: 0.9,
        timestamp: near_future_timestamp,
        hlc_timestamp: HlcTimestamp::default(),
        vector: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_ok(), "Should accept timestamp within 1 hour clock skew");
}

/// Test: Edge case - confidence exactly 0.0 is accepted.
#[tokio::test]
async fn test_accepts_zero_confidence() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();
    let message = "Test:zero_conf";
    let signature = signing_key.sign(message.as_bytes());

    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "zero_conf".to_string(),
        object: ObjectValue::Text("test".to_string()),
        parent_hash: None,
        source_hash: [0u8; 32],
        source_class: SourceClass::Expert,
        visual_hash: None,
        epoch: None,
        source_metadata: None,
        lifecycle: LifecycleStage::Proposed,
        signatures: vec![SignatureEntry {
            version: 1,
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp: 1000,
        }],
        confidence: 0.0, // Valid: boundary case
        timestamp: 1000,
        hlc_timestamp: HlcTimestamp::default(),
        vector: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_ok(), "Should accept confidence = 0.0");
}

/// Test: Edge case - confidence exactly 1.0 is accepted.
#[tokio::test]
async fn test_accepts_one_confidence() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();
    let message = "Test:one_conf";
    let signature = signing_key.sign(message.as_bytes());

    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "one_conf".to_string(),
        object: ObjectValue::Text("test".to_string()),
        parent_hash: None,
        source_hash: [0u8; 32],
        source_class: SourceClass::Expert,
        visual_hash: None,
        epoch: None,
        source_metadata: None,
        lifecycle: LifecycleStage::Proposed,
        signatures: vec![SignatureEntry {
            version: 1,
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp: 1000,
        }],
        confidence: 1.0, // Valid: boundary case
        timestamp: 1000,
        hlc_timestamp: HlcTimestamp::default(),
        vector: None,
    };

    let mut journal = Journal::open(&wal_dir).expect("Failed to open journal");
    let store = HybridStore::open(&db_dir).expect("Failed to open store");

    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(store);
    let mut worker =
        IngestWorker::new(journal, store.clone()).await.expect("Failed to create worker");

    let result = worker.step().await;
    assert!(result.is_ok(), "Should accept confidence = 1.0");
}
