//! E2E decay integration tests.
//!
//! Tests that verify time-based confidence decay in queries.
//!
//! # Test Coverage
//!
//! | Test | Validates |
//! |------|-----------|
//! | `test_e2e_decay_reduces_old_confidence` | Decay reduces effective confidence of old assertions |

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

use ed25519_dalek::{Signer, SigningKey};
use rand::rngs::OsRng;
use std::sync::Arc;
use stemedb_core::testing::AssertionBuilder;
use stemedb_core::types::{Assertion, LifecycleStage, ObjectValue, SignatureEntry};
use stemedb_ingest::worker::{serialize_assertion, IngestWorker};
use stemedb_query::{Query, QueryEngine};
use stemedb_storage::{key_codec, HybridStore, KVStore};
use stemedb_wal::Journal;
use tempfile::tempdir;
use tokio::sync::Mutex;

// ============================================================================
// TEST HELPERS
// ============================================================================

/// Create a signed assertion with Ed25519 signature.
///
/// The signature signs the message `"{subject}:{predicate}"` which matches
/// IngestWorker's verification logic.
fn create_signed_assertion(
    subject: &str,
    predicate: &str,
    value: f64,
    timestamp: u64,
) -> Assertion {
    let mut csprng = OsRng;
    let signing_key = SigningKey::generate(&mut csprng);
    let verifying_key = signing_key.verifying_key();

    let message = format!("{}:{}", subject, predicate);
    let signature = signing_key.sign(message.as_bytes());

    AssertionBuilder::new()
        .subject(subject)
        .predicate(predicate)
        .object_number(value)
        .confidence(0.95)
        .lifecycle(LifecycleStage::Proposed)
        .timestamp(timestamp)
        .signatures(vec![SignatureEntry {
            version: 1,
            agent_id: verifying_key.to_bytes(),
            signature: signature.to_bytes(),
            timestamp,
        }])
        .build()
}

// ============================================================================
// DECAY INTEGRATION TESTS
// ============================================================================

/// Test: Decay reduces effective confidence of old assertions.
///
/// Proves that when `decay_halflife` is set, older assertions have their
/// confidence reduced, allowing newer lower-confidence assertions to win.
#[tokio::test]
async fn test_e2e_decay_reduces_old_confidence() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    // Constants for decay calculation
    let now: u64 = 1_000_000_000;
    let one_year_ago = now - (365 * 24 * 60 * 60);
    let one_week_ago = now - (7 * 24 * 60 * 60);
    let one_year_seconds: u64 = 365 * 24 * 60 * 60;

    // Old assertion with HIGH original confidence (0.95)
    // But 1 year old with 1-year halflife = ~0.475 effective
    let old_assertion = {
        let mut a = create_signed_assertion("Semaglutide", "muscle_effect", -5.0, one_year_ago);
        a.confidence = 0.95;
        a
    };

    // New assertion with LOWER original confidence (0.6)
    // Only 1 week old = ~0.59 effective (minimal decay)
    let new_assertion = {
        let mut a = create_signed_assertion("Semaglutide", "muscle_effect", -2.0, one_week_ago);
        a.confidence = 0.6;
        a
    };

    // Write both to WAL and ingest
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&old_assertion).expect("ser")).expect("append");
    journal.append(serialize_assertion(&new_assertion).expect("ser")).expect("append");

    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("worker");
    worker.step().await.expect("step 1");
    worker.step().await.expect("step 2");

    // Verify both assertions are stored (check via subject-scoped assertion keys)
    let old_hash =
        *blake3::hash(&stemedb_core::serde::serialize(&old_assertion).expect("ser")).as_bytes();
    let new_hash =
        *blake3::hash(&stemedb_core::serde::serialize(&new_assertion).expect("ser")).as_bytes();
    let old_key = key_codec::assertion_key("Semaglutide", &hex::encode(old_hash));
    let new_key = key_codec::assertion_key("Semaglutide", &hex::encode(new_hash));
    assert!(
        store.get(&old_key).await.expect("get old").is_some(),
        "old assertion should be stored"
    );
    assert!(
        store.get(&new_key).await.expect("get new").is_some(),
        "new assertion should be stored"
    );

    // Query WITHOUT decay: old assertion wins (0.95 > 0.6)
    let engine = QueryEngine::new(store.clone());
    let query_no_decay = Query::builder().subject("Semaglutide").predicate("muscle_effect").build();

    let result_no_decay = engine.execute(&query_no_decay).await.expect("query no decay");
    assert_eq!(result_no_decay.assertions.len(), 2);
    // Find the highest confidence one without decay
    let highest_no_decay = result_no_decay
        .assertions
        .iter()
        .max_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap_or(std::cmp::Ordering::Equal))
        .expect("at least one assertion");
    assert_eq!(
        highest_no_decay.object,
        ObjectValue::Number(-5.0),
        "Without decay, old high-confidence assertion has highest confidence"
    );

    // Query WITH decay: new assertion should have higher effective confidence
    // Old: 0.95 * 2^(-1) = 0.475
    // New: 0.6 * 2^(-(7/365)) ≈ 0.59
    let query_with_decay = Query::builder()
        .subject("Semaglutide")
        .predicate("muscle_effect")
        .decay_halflife(one_year_seconds)
        .as_of(now) // Use as_of to control "now" for deterministic test
        .build();

    let result_with_decay = engine.execute(&query_with_decay).await.expect("query with decay");
    assert_eq!(result_with_decay.assertions.len(), 2);

    // Find the highest confidence one WITH decay applied
    let highest_with_decay = result_with_decay
        .assertions
        .iter()
        .max_by(|a, b| a.confidence.partial_cmp(&b.confidence).unwrap_or(std::cmp::Ordering::Equal))
        .expect("at least one assertion");

    assert_eq!(
        highest_with_decay.object,
        ObjectValue::Number(-2.0),
        "With decay, newer assertion should have higher effective confidence"
    );

    // Verify the actual decayed confidence values
    let old_decayed = result_with_decay
        .assertions
        .iter()
        .find(|a| a.object == ObjectValue::Number(-5.0))
        .expect("find old assertion");
    let new_decayed = result_with_decay
        .assertions
        .iter()
        .find(|a| a.object == ObjectValue::Number(-2.0))
        .expect("find new assertion");

    // Old: 0.95 * 2^(-1) ≈ 0.475
    assert!(
        (old_decayed.confidence - 0.475).abs() < 0.02,
        "Old assertion should decay to ~0.475, got {}",
        old_decayed.confidence
    );

    // New: 0.6 * 2^(-(7/365)) ≈ 0.592
    assert!(
        (new_decayed.confidence - 0.592).abs() < 0.02,
        "New assertion should decay minimally to ~0.592, got {}",
        new_decayed.confidence
    );
}
