//! Signature verification tests.
//!
//! Tests for Ed25519 signature verification, rejection of invalid
//! signatures, and multi-signature validation.

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

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

    // Create an assertion with an invalid signature (all zeros)
    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "invalid_sig".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: [1u8; 32],  // Invalid: not a valid Ed25519 public key
            signature: [2u8; 64], // Invalid: not a valid signature
            timestamp: 1000,
        }],
        confidence: 0.95,
        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");

    // Step should fail with InvalidSignature error
    let result = worker.step().await;
    assert!(result.is_err(), "Should reject invalid signature");

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

    // Verify no assertion was stored
    let count_key = key_codec::assertion_count_key();
    let count_entry = store.get(&count_key).await.expect("get");
    assert!(count_entry.is_none(), "No assertion should be stored");
}

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

    // Create an assertion with no signatures
    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "unsigned".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![], // No signatures!
        confidence: 0.95,
        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 unsigned assertion");

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

/// Test: Multi-signature assertions require all signatures to be valid.
#[tokio::test]
async fn test_multisig_all_must_be_valid() {
    let dir = tempdir().expect("Failed to create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    // Create an assertion with one valid and one invalid signature
    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();
    let message = "Test:multisig";
    let valid_signature = signing_key.sign(message.as_bytes());

    let assertion = Assertion {
        subject: "Test".to_string(),
        predicate: "multisig".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![
            // Valid signature
            SignatureEntry {
                version: 1,
                agent_id: verifying_key.to_bytes(),
                signature: valid_signature.to_bytes(),
                timestamp: 1000,
            },
            // Invalid signature
            SignatureEntry {
                version: 1,
                agent_id: [1u8; 32],
                signature: [2u8; 64],
                timestamp: 1001,
            },
        ],
        confidence: 0.95,
        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 when any signature is invalid");
}
