//! Battery 3: Decay Math Precision.
//!
//! Tests confidence decay calculations across different source class tiers.
//!
//! # Test Coverage
//!
//! | Test | Feature | Validates |
//! |------|---------|-----------|
//! | `test_decay_tier0_never_decays` | Tier 0 | Regulatory never decays |
//! | `test_decay_tier1_exact_halflife` | Tier 1 | Clinical at 730d = 0.5x |
//! | `test_decay_tier1_two_halflives` | Tier 1 | Clinical at 1460d = 0.25x |
//! | `test_decay_tier5_exact_halflife` | Tier 5 | Anecdotal at 30d = 0.5x |
//! | `test_decay_tier5_three_halflives` | Tier 5 | Anecdotal at 90d = 0.125x |
//! | `test_decay_zero_confidence_stays_zero` | Edge case | 0 * decay = 0 |
//! | `test_decay_never_goes_negative` | Edge case | No negative confidence |
//! | `test_decay_uses_as_of_for_age_calculation` | Time travel | Uses `now` param |

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

use super::helpers::*;

/// Test 3.1: Regulatory assertions never decay.
///
/// Tier 0 (Regulatory) has no decay halflife.
/// A Regulatory assertion with confidence 0.95, timestamped 10 years ago,
/// should maintain its original confidence after decay application.
#[test]
fn test_decay_tier0_never_decays() {
    let now: u64 = 1_000_000_000;
    let ten_years_ago = now - (10 * 365 * 86_400);

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Regulatory)
        .confidence(0.95)
        .timestamp(ten_years_ago)
        .build();

    let fallback_halflife: u64 = 365 * 86_400; // 1 year
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    assert_eq!(
        decayed[0].confidence, 0.95,
        "Regulatory assertions should never decay, confidence should remain exactly 0.95"
    );
}

/// Test 3.2: Clinical assertion decays to 0.5 at exactly one half-life.
///
/// Tier 1 (Clinical) has 730-day half-life.
/// An assertion with confidence 1.0, timestamped exactly 730 days ago,
/// should decay to 0.5 (within tolerance of 0.02).
#[test]
fn test_decay_tier1_exact_halflife() {
    let now: u64 = 1_000_000_000;
    let days_730 = 730 * 86_400;
    let past = now - days_730;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Clinical)
        .confidence(1.0)
        .timestamp(past)
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    let effective_conf = decayed[0].confidence;
    assert!(
        (effective_conf - 0.5).abs() < 0.02,
        "Clinical assertion at 730 days (1 half-life) should decay to ~0.5, got {}",
        effective_conf
    );
}

/// Test 3.3: Clinical assertion decays to 0.25 at exactly two half-lives.
///
/// Tier 1 (Clinical) has 730-day half-life.
/// An assertion with confidence 1.0, timestamped exactly 1460 days ago,
/// should decay to 0.25 (within tolerance of 0.02).
#[test]
fn test_decay_tier1_two_halflives() {
    let now: u64 = 1_000_000_000;
    let days_1460 = 1460 * 86_400;
    let past = now - days_1460;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Clinical)
        .confidence(1.0)
        .timestamp(past)
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    let effective_conf = decayed[0].confidence;
    assert!(
        (effective_conf - 0.25).abs() < 0.02,
        "Clinical assertion at 1460 days (2 half-lives) should decay to ~0.25, got {}",
        effective_conf
    );
}

/// Test 3.4: Anecdotal assertion decays to 0.5 at exactly one half-life.
///
/// Tier 5 (Anecdotal) has 30-day half-life.
/// An assertion with confidence 1.0, timestamped exactly 30 days ago,
/// should decay to 0.5 (within tolerance of 0.02).
#[test]
fn test_decay_tier5_exact_halflife() {
    let now: u64 = 1_000_000_000;
    let days_30 = 30 * 86_400;
    let past = now - days_30;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Anecdotal)
        .confidence(1.0)
        .timestamp(past)
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    let effective_conf = decayed[0].confidence;
    assert!(
        (effective_conf - 0.5).abs() < 0.02,
        "Anecdotal assertion at 30 days (1 half-life) should decay to ~0.5, got {}",
        effective_conf
    );
}

/// Test 3.5: Anecdotal assertion decays to 0.125 at exactly three half-lives.
///
/// Tier 5 (Anecdotal) has 30-day half-life.
/// An assertion with confidence 1.0, timestamped exactly 90 days ago,
/// should decay to 0.125 (within tolerance of 0.02).
#[test]
fn test_decay_tier5_three_halflives() {
    let now: u64 = 1_000_000_000;
    let days_90 = 90 * 86_400;
    let past = now - days_90;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Anecdotal)
        .confidence(1.0)
        .timestamp(past)
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    let effective_conf = decayed[0].confidence;
    assert!(
        (effective_conf - 0.125).abs() < 0.02,
        "Anecdotal assertion at 90 days (3 half-lives) should decay to ~0.125, got {}",
        effective_conf
    );
}

/// Test 3.6: Assertion with zero confidence stays zero after decay.
///
/// Decay formula: confidence * 2^(-age/halflife)
/// If confidence = 0.0, then 0 * anything = 0.
#[test]
fn test_decay_zero_confidence_stays_zero() {
    let now: u64 = 1_000_000_000;
    let days_365 = 365 * 86_400;
    let past = now - days_365;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Clinical)
        .confidence(0.0)
        .timestamp(past)
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    assert_eq!(
        decayed[0].confidence, 0.0,
        "Zero confidence should remain zero after decay (0 * anything = 0)"
    );
}

/// Test 3.7: Decay never produces negative confidence.
///
/// Even with very low initial confidence and extreme age (12+ half-lives),
/// the effective confidence should never go below 0.0.
#[test]
fn test_decay_never_goes_negative() {
    let now: u64 = 1_000_000_000;
    let days_365 = 365 * 86_400;
    let past = now - days_365;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Anecdotal) // 30-day half-life
        .confidence(0.01)
        .timestamp(past) // 365 days ago = 12+ half-lives
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(&[assertion], fallback_halflife, now);

    assert_eq!(decayed.len(), 1);
    assert!(
        decayed[0].confidence >= 0.0,
        "Confidence should never go negative, got {}",
        decayed[0].confidence
    );
}

/// Test 3.8: Decay uses `now` parameter for age calculation.
///
/// Two assertions at T=1000, both with confidence 0.9:
/// - Assertion A: Clinical (730-day half-life)
/// - Assertion B: Anecdotal (30-day half-life)
///
/// Apply decay with `now = T + 730*86400` (exactly 730 days later).
/// Assert:
/// - A's effective confidence ~ 0.45 (0.9 * 0.5, one half-life)
/// - B's effective confidence ~ near zero (0.9 * 2^(-24), 24 half-lives)
#[test]
fn test_decay_uses_as_of_for_age_calculation() {
    let base_ts: u64 = 1000;
    let days_730 = 730 * 86_400;
    let now = base_ts + days_730;

    let clinical_assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(1.0)
        .source_class(SourceClass::Clinical)
        .confidence(0.9)
        .timestamp(base_ts)
        .build();

    let anecdotal_assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("decay_test")
        .object_number(2.0)
        .source_class(SourceClass::Anecdotal)
        .confidence(0.9)
        .timestamp(base_ts)
        .build();

    let fallback_halflife: u64 = 365 * 86_400;
    let decayed = apply_source_class_decay(
        &[clinical_assertion, anecdotal_assertion],
        fallback_halflife,
        now,
    );

    assert_eq!(decayed.len(), 2);

    // Clinical: 0.9 * 2^(-1) = 0.45
    let clinical_conf = decayed[0].confidence;
    assert!(
        (clinical_conf - 0.45).abs() < 0.02,
        "Clinical assertion at 730 days (1 half-life) should decay to ~0.45, got {}",
        clinical_conf
    );

    // Anecdotal: 0.9 * 2^(-24) ≈ 0.9 * 5.96e-8 ≈ near zero
    let anecdotal_conf = decayed[1].confidence;
    assert!(
        anecdotal_conf < 0.001,
        "Anecdotal assertion at 730 days (24 half-lives @ 30-day rate) should decay to near zero, got {}",
        anecdotal_conf
    );
}
