//! Battery 10: Advanced Signature Tests.
//!
//! Tests for v2 signatures, multi-sig, and unknown signature versions.
//!
//! # Test Coverage
//!
//! | Test | Scenario | Validates |
//! |------|----------|-----------|
//! | `test_v2_valid_signature_accepted` | Valid v2 sig | Accepted and stored |
//! | `test_unknown_signature_version_rejected` | Unknown version | Rejected |
//! | `test_multi_sig_all_valid_accepted` | Multi-sig valid | Accepted |
//! | `test_multi_sig_one_invalid_rejected` | Multi-sig partial | Rejected |

#![allow(clippy::expect_used)] // Test code uses expect() for clear failure messages

use super::helpers::*;

/// Test 10.1: Valid v2 signature is accepted.
///
/// Agent A signs an assertion using v2 (content hash) format.
/// Assert: assertion is stored, index entries exist.
#[tokio::test]
async fn test_v2_valid_signature_accepted() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let base_ts: u64 = 1_000_000;

    // Create a properly signed v2 assertion using the helper
    let assertion = create_signed_assertion_v2(
        "Subject_V2_Valid",
        "predicate_test",
        ObjectValue::Text("v2_value".to_string()),
        SourceClass::Clinical,
        0.8,
        base_ts,
    );

    // Verify the assertion has v2 signature
    assert_eq!(assertion.signatures[0].version, 2, "should be v2 signature");

    // Write to WAL
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    // Ingest via IngestWorker
    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));

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

    let bytes = worker.step().await.expect("v2 ingest step should succeed");
    assert!(bytes > 0, "v2: should process data from WAL");

    // Verify assertion is stored (H: key exists)
    let h_prefix = key_codec::assertion_key("Subject_V2_Valid", "");
    let h_entries = store.scan_prefix(&h_prefix).await.expect("scan H:");
    assert_eq!(h_entries.len(), 1, "v2: should have 1 assertion stored");

    // Verify SP: index exists
    let sp_prefix = key_codec::subject_predicate_scan_prefix("Subject_V2_Valid");
    let sp_entries = store.scan_prefix(&sp_prefix).await.expect("scan SP:");
    assert_eq!(sp_entries.len(), 1, "v2: should have 1 SP: index entry");
}

/// Test 10.2: Unknown signature version is rejected.
///
/// Agent A signs an assertion but uses an unknown version (e.g., v99).
/// Assert: ingestion fails with unknown version error.
#[tokio::test]
async fn test_unknown_signature_version_rejected() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let base_ts: u64 = 1_000_000;

    // Create a signed assertion
    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();

    // Sign for "Subject_Unknown:predicate_test"
    let message = format!("{}:{}", "Subject_Unknown", "predicate_test");
    let signature = signing_key.sign(message.as_bytes());

    // Create assertion with unknown version
    let assertion = AssertionBuilder::new()
        .subject("Subject_Unknown")
        .predicate("predicate_test")
        .object_text("value")
        .source_class(SourceClass::Clinical)
        .confidence(0.8)
        .lifecycle(LifecycleStage::Proposed)
        .timestamp(base_ts)
        .signatures(vec![SignatureEntry {
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp: base_ts,
            version: 99,
        }])
        .build();

    // Write to WAL
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    // Ingest via IngestWorker
    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));

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

    // Attempt to ingest - should fail due to unknown version
    let result = worker.step().await;
    assert!(result.is_err(), "unknown signature version should be rejected");

    // Verify the error mentions the unknown version
    let err = result.unwrap_err();
    let err_str = err.to_string();
    assert!(
        err_str.contains("Unknown signature version") || err_str.contains("99"),
        "error should mention unknown version, got: {}",
        err_str
    );

    // Verify no assertion was stored
    let h_prefix = key_codec::assertion_key("Subject_Unknown", "");
    let h_entries = store.scan_prefix(&h_prefix).await.expect("scan H:");
    assert_eq!(h_entries.len(), 0, "assertion with unknown signature version should NOT be stored");
}

/// Test 10.3: Multi-sig with all valid signatures is accepted.
///
/// Agent A and Agent B both sign the same assertion (two valid SignatureEntries).
/// Assert: ingestion succeeds.
#[tokio::test]
async fn test_multi_sig_all_valid_accepted() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let base_ts: u64 = 1_000_000;

    // Create Agent A's key pair
    let mut csprng = OsRng;
    let signing_key_a = SigningKey::generate(&mut csprng);
    let verifying_key_a = signing_key_a.verifying_key();

    // Create Agent B's key pair
    let signing_key_b = SigningKey::generate(&mut csprng);
    let verifying_key_b = signing_key_b.verifying_key();

    // Both agents sign the same message "Subject_F:predicate_test"
    let message = format!("{}:{}", "Subject_F", "predicate_test");
    let signature_a = signing_key_a.sign(message.as_bytes());
    let signature_b = signing_key_b.sign(message.as_bytes());

    // Create assertion with two valid signatures
    let assertion = AssertionBuilder::new()
        .subject("Subject_F")
        .predicate("predicate_test")
        .object_text("value")
        .source_class(SourceClass::Clinical)
        .confidence(0.8)
        .lifecycle(LifecycleStage::Proposed)
        .timestamp(base_ts)
        .signatures(vec![
            SignatureEntry {
                agent_id: verifying_key_a.to_bytes(),
                signature: signature_a.to_bytes(),
                timestamp: base_ts,
                version: 1,
            },
            SignatureEntry {
                agent_id: verifying_key_b.to_bytes(),
                signature: signature_b.to_bytes(),
                timestamp: base_ts,
                version: 1,
            },
        ])
        .build();

    // Write to WAL
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    // Ingest via IngestWorker
    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));

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

    let bytes = worker.step().await.expect("multi-sig should be accepted");
    assert!(bytes > 0, "should process data from WAL");

    // Verify assertion is stored
    let h_prefix = key_codec::assertion_key("Subject_F", "");
    let h_entries = store.scan_prefix(&h_prefix).await.expect("scan H:");
    assert_eq!(h_entries.len(), 1, "multi-sig assertion should be stored");

    // Verify the stored assertion has both signatures
    let (_key, value) = &h_entries[0];
    let stored: Assertion = stemedb_core::serde::deserialize(value).expect("deserialize");
    assert_eq!(stored.signatures.len(), 2, "stored assertion should have 2 signatures");
}

/// Test 10.4: Multi-sig with one invalid signature is rejected.
///
/// Agent A signs validly, Agent B's signature is invalid (tampered).
/// Assert: ingestion fails. ALL signatures must be valid.
#[tokio::test]
async fn test_multi_sig_one_invalid_rejected() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let base_ts: u64 = 1_000_000;

    // Create Agent A's key pair
    let mut csprng = OsRng;
    let signing_key_a = SigningKey::generate(&mut csprng);
    let verifying_key_a = signing_key_a.verifying_key();

    // Create Agent B's key pair
    let signing_key_b = SigningKey::generate(&mut csprng);
    let verifying_key_b = signing_key_b.verifying_key();

    // Agent A signs correctly for "Subject_G:predicate_test"
    let message = format!("{}:{}", "Subject_G", "predicate_test");
    let signature_a = signing_key_a.sign(message.as_bytes());

    // Agent B signs a DIFFERENT message (tampered)
    let wrong_message = format!("{}:{}", "Wrong_Subject", "predicate_test");
    let signature_b_wrong = signing_key_b.sign(wrong_message.as_bytes());

    // Create assertion with one valid and one invalid signature
    let assertion = AssertionBuilder::new()
        .subject("Subject_G")
        .predicate("predicate_test")
        .object_text("value")
        .source_class(SourceClass::Clinical)
        .confidence(0.8)
        .lifecycle(LifecycleStage::Proposed)
        .timestamp(base_ts)
        .signatures(vec![
            SignatureEntry {
                agent_id: verifying_key_a.to_bytes(),
                signature: signature_a.to_bytes(),
                timestamp: base_ts,
                version: 1,
            },
            SignatureEntry {
                agent_id: verifying_key_b.to_bytes(),
                signature: signature_b_wrong.to_bytes(),
                timestamp: base_ts,
                version: 1,
            },
        ])
        .build();

    // Write to WAL
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&assertion).expect("ser")).expect("append");

    // Ingest via IngestWorker
    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));

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

    // Attempt to ingest - should fail because one signature is invalid
    let result = worker.step().await;
    assert!(
        result.is_err(),
        "multi-sig with one invalid signature should fail (ALL signatures must be valid)"
    );

    // Verify the error is an invalid signature error
    let err = result.unwrap_err();
    let err_str = err.to_string();
    assert!(
        err_str.contains("Signature") || err_str.contains("verification"),
        "error should be related to signature verification, got: {}",
        err_str
    );

    // Verify no assertion was stored
    let h_prefix = key_codec::assertion_key("Subject_G", "");
    let h_entries = store.scan_prefix(&h_prefix).await.expect("scan H:");
    assert_eq!(h_entries.len(), 0, "multi-sig with invalid signature should NOT be stored");
}
