//! Semantic decay for assertion confidence.
//!
//! This module implements time-based confidence decay for assertions.
//! Older assertions have their effective confidence reduced based on age,
//! allowing recent evidence to outweigh stale claims.
//!
//! # The Problem
//!
//! Medical knowledge decays at different rates. A Reddit post from 2022
//! shouldn't compete equally with a 2024 RCT. Without decay, old assertions
//! with high confidence can dominate over recent, more relevant evidence.
//!
//! # Formula
//!
//! ```text
//! effective_confidence = original_confidence * 2^(-(age / halflife))
//! ```
//!
//! Where:
//! - `age` = now - assertion.timestamp (in seconds)
//! - `halflife` = decay half-life (in seconds)
//!
//! # Example
//!
//! With a 1-year half-life (31,536,000 seconds):
//! - 0 years old: 100% of original confidence
//! - 1 year old: 50% of original confidence
//! - 2 years old: 25% of original confidence
//! - 3 years old: 12.5% of original confidence

use stemedb_core::types::Assertion;

/// Seconds per day (86,400).
const SECONDS_PER_DAY: u64 = 86_400;

/// Apply decay to a set of assertions based on age.
///
/// Returns cloned assertions with their confidence scores reduced based on
/// how old they are relative to the given timestamp.
///
/// # Arguments
///
/// * `assertions` - The assertions to decay
/// * `halflife` - Decay half-life in seconds. After this duration, confidence is halved.
/// * `now` - Reference timestamp (usually current time or `as_of` for time-travel)
///
/// # Returns
///
/// A new vector of assertions with decayed confidence scores.
/// The original assertions are not modified.
///
/// # Formula
///
/// ```text
/// age = now - assertion.timestamp
/// decay_factor = 2^(-(age / halflife))
/// effective_confidence = confidence * decay_factor
/// ```
///
/// # Edge Cases
///
/// - If `halflife` is 0, returns assertions unchanged (no decay)
/// - If assertion timestamp > now, no decay is applied (future assertions)
/// - Confidence is clamped to [0.0, 1.0]
pub fn apply_decay(assertions: &[Assertion], halflife: u64, now: u64) -> Vec<Assertion> {
    if halflife == 0 {
        return assertions.to_vec();
    }

    assertions
        .iter()
        .map(|assertion| {
            let decayed_confidence = compute_decayed_confidence(
                assertion.confidence,
                assertion.timestamp,
                halflife,
                now,
            );

            let mut decayed = assertion.clone();
            decayed.confidence = decayed_confidence;
            decayed
        })
        .collect()
}

/// Apply source-class-aware decay to assertions.
///
/// Each assertion's decay half-life is determined by its `source_class` tier:
/// - Tier 0 (Regulatory): No decay
/// - Tier 1 (Clinical): 2-year half-life
/// - Tier 2 (Observational): 1-year half-life
/// - Tier 3 (Expert): 6-month half-life
/// - Tier 4 (Community): 3-month half-life
/// - Tier 5 (Anecdotal): 1-month half-life
///
/// # Arguments
///
/// * `assertions` - The assertions to decay
/// * `fallback_halflife` - Half-life in seconds to use when source_class has no default
/// * `now` - Reference timestamp
///
/// # Returns
///
/// A new vector of assertions with tier-appropriate decay applied.
pub fn apply_source_class_decay(
    assertions: &[Assertion],
    fallback_halflife: u64,
    now: u64,
) -> Vec<Assertion> {
    assertions
        .iter()
        .map(|assertion| {
            // Get tier-specific half-life from SourceClass, convert days to seconds
            // If default_decay_days() returns None (e.g., Regulatory), no decay is applied.
            let halflife_opt = assertion
                .source_class
                .default_decay_days()
                .map(|days| u64::from(days) * SECONDS_PER_DAY);

            // If source class has no decay (None), return unchanged
            // Otherwise use tier-specific halflife (or fallback if zero)
            let halflife = match halflife_opt {
                None => {
                    // Source class explicitly has no decay (e.g., Regulatory)
                    return assertion.clone();
                }
                Some(0) => fallback_halflife, // Shouldn't happen, but fallback to avoid div-by-zero
                Some(h) => h,
            };

            let decayed_confidence = compute_decayed_confidence(
                assertion.confidence,
                assertion.timestamp,
                halflife,
                now,
            );

            let mut decayed = assertion.clone();
            decayed.confidence = decayed_confidence;
            decayed
        })
        .collect()
}

/// Compute the decayed confidence for a single assertion.
///
/// # Formula
///
/// ```text
/// age = now - timestamp
/// decay_factor = 2^(-(age / halflife))
/// decayed_confidence = confidence * decay_factor
/// ```
///
/// # Arguments
///
/// * `confidence` - Original confidence score (0.0 to 1.0)
/// * `timestamp` - When the assertion was created (Unix seconds)
/// * `halflife` - Decay half-life in seconds
/// * `now` - Reference timestamp (Unix seconds)
///
/// # Returns
///
/// The decayed confidence, clamped to [0.0, 1.0].
fn compute_decayed_confidence(confidence: f32, timestamp: u64, halflife: u64, now: u64) -> f32 {
    // No decay for future assertions
    if timestamp >= now {
        return confidence;
    }

    let age = now - timestamp;
    let age_f = age as f32;
    let halflife_f = halflife as f32;

    // decay_factor = 2^(-(age / halflife))
    // This equals e^(-(age / halflife) * ln(2))
    let decay_factor = 2_f32.powf(-age_f / halflife_f);

    // Clamp to valid confidence range
    (confidence * decay_factor).clamp(0.0, 1.0)
}

#[cfg(test)]
mod tests {
    use super::*;
    use stemedb_core::testing::AssertionBuilder;
    use stemedb_core::types::SourceClass;

    /// One year in seconds (365 days).
    const ONE_YEAR_SECONDS: u64 = 365 * 24 * 60 * 60;
    /// One hour in seconds.
    const ONE_HOUR_SECONDS: u64 = 60 * 60;

    // ========================================================================
    // Core Decay Tests
    // ========================================================================

    #[test]
    fn test_decay_reduces_old_assertion_confidence() {
        // Assertion is 1 year old with 1-year half-life
        // Expected: ~50% of original confidence
        let now = 1_000_000_000_u64; // Some reference time
        let one_year_ago = now - ONE_YEAR_SECONDS;

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.9)
            .timestamp(one_year_ago)
            .build();

        let decayed = apply_decay(&[assertion], ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        let decayed_conf = decayed[0].confidence;

        // Should be approximately 0.45 (0.9 * 0.5)
        // Allow 1% tolerance for floating point
        assert!((decayed_conf - 0.45).abs() < 0.01, "Expected ~0.45, got {}", decayed_conf);
    }

    #[test]
    fn test_decay_preserves_fresh_assertions() {
        // Assertion is 1 hour old with 1-year half-life
        // Expected: ~100% of original confidence (minimal decay)
        let now = 1_000_000_000_u64;
        let one_hour_ago = now - ONE_HOUR_SECONDS;

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.9)
            .timestamp(one_hour_ago)
            .build();

        let decayed = apply_decay(&[assertion], ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        let decayed_conf = decayed[0].confidence;

        // Should be very close to original (99.99%+)
        assert!((decayed_conf - 0.9).abs() < 0.001, "Expected ~0.9, got {}", decayed_conf);
    }

    #[test]
    fn test_decay_interacts_with_lens() {
        // Two assertions: older has higher base confidence but should lose after decay
        let now = 1_000_000_000_u64;
        let two_years_ago = now - (2 * ONE_YEAR_SECONDS);
        let one_week_ago = now - (7 * 24 * 60 * 60);

        // Old assertion: high confidence (0.9), but 2 years old
        let old_assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.9)
            .timestamp(two_years_ago)
            .build();

        // New assertion: lower confidence (0.6), but only 1 week old
        let new_assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.6)
            .timestamp(one_week_ago)
            .build();

        let decayed = apply_decay(&[old_assertion, new_assertion], ONE_YEAR_SECONDS, now);

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

        // Old assertion: 0.9 * 0.25 (2 half-lives) = ~0.225
        let old_decayed = decayed[0].confidence;
        assert!(
            (old_decayed - 0.225).abs() < 0.02,
            "Old assertion expected ~0.225, got {}",
            old_decayed
        );

        // New assertion: 0.6 * ~1.0 = ~0.6 (negligible decay)
        // 1 week = 604800 seconds, 1 year = 31536000 seconds
        // decay factor = 2^(-(604800/31536000)) = 2^(-0.0192) ≈ 0.9868
        // 0.6 * 0.9868 ≈ 0.592
        let new_decayed = decayed[1].confidence;
        assert!(
            (new_decayed - 0.6).abs() < 0.02, // Allow 2% tolerance
            "New assertion expected ~0.6, got {}",
            new_decayed
        );

        // The newer assertion should now have higher effective confidence
        assert!(
            new_decayed > old_decayed,
            "Newer assertion ({}) should beat older ({}) after decay",
            new_decayed,
            old_decayed
        );
    }

    // --- Source-Class-Aware Decay Tests ---

    #[test]
    fn test_source_aware_decay_tier0_no_decay() {
        // Regulatory (Tier 0) sources should never decay
        let now = 1_000_000_000_u64;
        let five_years_ago = now - (5 * ONE_YEAR_SECONDS);

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.95)
            .timestamp(five_years_ago)
            .source_class(SourceClass::Regulatory)
            .build();

        let decayed = apply_source_class_decay(&[assertion], ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        // Regulatory sources have no decay (default_decay_days returns None)
        // So fallback is used, but since Regulatory returns None, we should handle this
        // Actually, looking at the code, None means no decay
        assert_eq!(decayed[0].confidence, 0.95, "Regulatory sources should not decay");
    }

    #[test]
    fn test_source_aware_decay_tier5_rapid_decay() {
        // Anecdotal (Tier 5) sources decay rapidly (30-day half-life)
        let now = 1_000_000_000_u64;
        let sixty_days_ago = now - (60 * SECONDS_PER_DAY);

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.8)
            .timestamp(sixty_days_ago)
            .source_class(SourceClass::Anecdotal)
            .build();

        let decayed = apply_source_class_decay(&[assertion], ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        // 60 days = 2 half-lives for Anecdotal (30-day half-life)
        // Expected: 0.8 * 0.25 = 0.2
        let decayed_conf = decayed[0].confidence;
        assert!(
            (decayed_conf - 0.2).abs() < 0.02,
            "Anecdotal (60 days, 30-day halflife) expected ~0.2, got {}",
            decayed_conf
        );
    }

    #[test]
    fn test_source_aware_decay_mixed_tiers() {
        // Compare Clinical (2yr halflife) vs Anecdotal (30-day halflife)
        let now = 1_000_000_000_u64;
        let one_year_ago = now - ONE_YEAR_SECONDS;

        // Clinical: 1 year = 0.5 half-lives → ~70% decay factor
        let clinical = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.8)
            .timestamp(one_year_ago)
            .source_class(SourceClass::Clinical)
            .build();

        // Anecdotal: 1 year = 12+ half-lives → ~0% decay factor
        let anecdotal = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.8)
            .timestamp(one_year_ago)
            .source_class(SourceClass::Anecdotal)
            .build();

        let decayed = apply_source_class_decay(&[clinical, anecdotal], ONE_YEAR_SECONDS, now);

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

        // Clinical (2yr halflife): 1yr = 0.5 halflife → 2^(-0.5) ≈ 0.707
        // 0.8 * 0.707 ≈ 0.566
        let clinical_decayed = decayed[0].confidence;
        assert!(
            (clinical_decayed - 0.566).abs() < 0.02,
            "Clinical expected ~0.566, got {}",
            clinical_decayed
        );

        // Anecdotal (30-day halflife): 365 days = ~12.2 half-lives → 2^(-12.2) ≈ 0.0002
        // Should be near zero
        let anecdotal_decayed = decayed[1].confidence;
        assert!(
            anecdotal_decayed < 0.01,
            "Anecdotal expected near zero, got {}",
            anecdotal_decayed
        );

        // Clinical should be much higher than Anecdotal after tier-aware decay
        assert!(
            clinical_decayed > anecdotal_decayed * 10.0,
            "Clinical ({}) should be much higher than Anecdotal ({})",
            clinical_decayed,
            anecdotal_decayed
        );
    }

    // ========================================================================
    // Edge Case Tests
    // ========================================================================

    #[test]
    fn test_decay_zero_halflife_no_change() {
        let now = 1_000_000_000_u64;
        let one_year_ago = now - ONE_YEAR_SECONDS;

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.9)
            .timestamp(one_year_ago)
            .build();

        // Zero half-life means no decay
        let decayed = apply_decay(&[assertion], 0, now);

        assert_eq!(decayed.len(), 1);
        assert_eq!(decayed[0].confidence, 0.9, "Zero halflife should skip decay");
    }

    #[test]
    fn test_decay_future_assertion_no_change() {
        let now = 1_000_000_000_u64;
        let future = now + ONE_YEAR_SECONDS;

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.9)
            .timestamp(future)
            .build();

        let decayed = apply_decay(&[assertion], ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        assert_eq!(decayed[0].confidence, 0.9, "Future assertions should not decay");
    }

    #[test]
    fn test_decay_empty_assertions() {
        let decayed = apply_decay(&[], ONE_YEAR_SECONDS, 1_000_000_000);
        assert!(decayed.is_empty());
    }

    #[test]
    fn test_decay_confidence_clamps_to_valid_range() {
        // Very old assertion should decay to near-zero but never negative
        // Use a large enough `now` to avoid overflow with 100-year old assertion
        let now = 5_000_000_000_u64; // ~2128 in Unix time
        let ancient = now - (100 * ONE_YEAR_SECONDS); // 100 years ago

        let assertion = AssertionBuilder::new()
            .subject("test")
            .predicate("value")
            .confidence(0.9)
            .timestamp(ancient)
            .build();

        let decayed = apply_decay(&[assertion], ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        assert!(decayed[0].confidence >= 0.0, "Confidence should not be negative");
        assert!(decayed[0].confidence <= 1.0, "Confidence should not exceed 1.0");
    }

    #[test]
    fn test_decay_preserves_other_fields() {
        let now = 1_000_000_000_u64;
        let one_year_ago = now - ONE_YEAR_SECONDS;

        let assertion = AssertionBuilder::new()
            .subject("Tesla")
            .predicate("revenue")
            .object_number(96.7)
            .confidence(0.9)
            .timestamp(one_year_ago)
            .build();

        let decayed = apply_decay(std::slice::from_ref(&assertion), ONE_YEAR_SECONDS, now);

        assert_eq!(decayed.len(), 1);
        assert_eq!(decayed[0].subject, assertion.subject);
        assert_eq!(decayed[0].predicate, assertion.predicate);
        assert_eq!(decayed[0].object, assertion.object);
        assert_eq!(decayed[0].timestamp, assertion.timestamp);
        // Only confidence should change
        assert_ne!(decayed[0].confidence, assertion.confidence);
    }
}
