//! HLC-based Recency Lens: Hybrid Logical Clock timestamp wins.
//!
//! This lens provides distributed-consistent recency ordering using HLC timestamps,
//! which handle clock skew between nodes better than Unix timestamps alone.
//!
//! # Why HLC over Unix timestamp?
//!
//! - **Clock skew tolerance**: Two nodes with drifted clocks will still produce
//!   consistent ordering because HLC combines physical time with logical counters.
//! - **Total ordering**: HLC + node_id provides deterministic ordering even for
//!   concurrent events on different nodes.
//! - **Causal consistency**: HLC preserves happens-before relationships across
//!   distributed nodes.
//!
//! # Resolution Strategy
//!
//! 1. Compare by `hlc_timestamp` (includes NTP64 time + logical counter)
//! 2. If HLC times are equal (concurrent events), compare by `node_id`
//! 3. Final tiebreaker: `source_hash` for determinism

use crate::traits::{compute_conflict_score, Lens, Resolution};
use stemedb_core::types::Assertion;
use tracing::instrument;

/// HLC-based Recency Lens: Returns the assertion with the highest HLC timestamp.
///
/// # Resolution Strategy
///
/// 1. Find assertion with maximum `hlc_timestamp`
/// 2. If HLC tie: HLC's `node_id` provides tiebreaker
/// 3. Final tiebreaker: `source_hash` for determinism across identical HLCs
///
/// # Confidence Calculation
///
/// - Single candidate: 1.0 (trivial resolution)
/// - Multiple candidates: Based on HLC timestamp gap (in milliseconds) to next candidate
///   - > 1 day gap: 0.95
///   - > 1 hour gap: 0.8
///   - > 1 minute gap: 0.6
///   - Otherwise: 0.5
#[derive(Debug, Clone, Copy, Default)]
pub struct HlcRecencyLens;

impl Lens for HlcRecencyLens {
    #[instrument(skip(self, candidates), fields(candidates_count = candidates.len(), lens = "HlcRecency"))]
    fn resolve(&self, candidates: &[Assertion]) -> Resolution {
        if candidates.is_empty() {
            return Resolution::empty();
        }

        if candidates.len() == 1 {
            return Resolution::with_winner(candidates[0].clone(), 1, 1.0, 0.0);
        }

        // Find the assertion with the highest HLC timestamp
        // HLC's Ord implementation compares time_ntp64 first, then node_id
        let winner = candidates
            .iter()
            .max_by(|a, b| {
                // Primary: highest HLC timestamp (includes NTP64 time + node_id tiebreaker)
                // Final tiebreaker: source_hash for determinism
                a.hlc_timestamp
                    .cmp(&b.hlc_timestamp)
                    .then_with(|| a.source_hash.cmp(&b.source_hash))
            })
            .cloned();

        match winner {
            Some(w) => {
                // Calculate confidence based on how much newer the winner is
                let max_hlc = &w.hlc_timestamp;
                let max_ms = max_hlc.millis();

                // Find the second-highest HLC timestamp
                let second_max_ms = candidates
                    .iter()
                    .filter(|a| a.hlc_timestamp < *max_hlc)
                    .map(|a| a.hlc_timestamp.millis())
                    .max()
                    .unwrap_or(0);

                // Confidence is higher when the gap is larger
                let gap_ms = max_ms.saturating_sub(second_max_ms);
                let confidence = if gap_ms > 86_400_000 {
                    // More than a day: high confidence
                    0.95
                } else if gap_ms > 3_600_000 {
                    // More than an hour: good confidence
                    0.8
                } else if gap_ms > 60_000 {
                    // More than a minute: moderate confidence
                    0.6
                } else {
                    // Very close: low confidence
                    0.5
                };

                let conflict = compute_conflict_score(candidates);
                Resolution::with_winner(w, candidates.len(), confidence, conflict)
            }
            None => Resolution::empty(),
        }
    }

    fn name(&self) -> &'static str {
        "HlcRecency"
    }
}

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

    fn create_assertion_with_hlc(subject: &str, time_ntp64: u64, node_id: [u8; 16]) -> Assertion {
        AssertionBuilder::new()
            .subject(subject)
            .hlc_timestamp(HlcTimestamp::new(time_ntp64, node_id))
            .build()
    }

    #[test]
    fn test_empty_candidates() {
        let lens = HlcRecencyLens;
        let resolution = lens.resolve(&[]);

        assert!(resolution.winner.is_none());
        assert_eq!(resolution.candidates_count, 0);
    }

    #[test]
    fn test_single_candidate() {
        let lens = HlcRecencyLens;
        let assertion = create_assertion_with_hlc("Tesla", 1000, [1u8; 16]);
        let resolution = lens.resolve(std::slice::from_ref(&assertion));

        assert!(resolution.winner.is_some());
        assert_eq!(resolution.winner.as_ref().map(|a| &a.subject), Some(&"Tesla".to_string()));
        assert_eq!(resolution.candidates_count, 1);
        assert!((resolution.resolution_confidence - 1.0).abs() < f32::EPSILON);
    }

    #[test]
    fn test_hlc_ordering_beats_unix_timestamp() {
        // Test that HLC ordering is used, not Unix timestamp
        let lens = HlcRecencyLens;

        // Create two assertions with same Unix timestamp but different HLC
        let mut older = AssertionBuilder::new()
            .subject("Older")
            .timestamp(1000) // Same Unix timestamp
            .hlc_timestamp(HlcTimestamp::new(1000, [1u8; 16]))
            .build();
        older.source_hash = [1u8; 32];

        let mut newer = AssertionBuilder::new()
            .subject("Newer")
            .timestamp(1000) // Same Unix timestamp
            .hlc_timestamp(HlcTimestamp::new(2000, [1u8; 16])) // Higher HLC
            .build();
        newer.source_hash = [2u8; 32];

        let resolution = lens.resolve(&[older, newer.clone()]);

        assert!(resolution.winner.is_some());
        assert_eq!(resolution.winner.as_ref().map(|a| &a.subject), Some(&"Newer".to_string()));
    }

    #[test]
    fn test_deterministic_tiebreaker_same_hlc_time() {
        // When HLC time is equal, node_id should break the tie
        let lens = HlcRecencyLens;

        let mut a1 = create_assertion_with_hlc("A", 1000, [1u8; 16]);
        a1.source_hash = [1u8; 32];

        let mut a2 = create_assertion_with_hlc("B", 1000, [2u8; 16]); // Higher node_id
        a2.source_hash = [2u8; 32];

        // Same HLC time, should use node_id as tiebreaker
        let resolution1 = lens.resolve(&[a1.clone(), a2.clone()]);
        let resolution2 = lens.resolve(&[a2.clone(), a1.clone()]);

        // Should be deterministic regardless of input order
        // Higher node_id wins
        assert_eq!(
            resolution1.winner.as_ref().map(|a| &a.subject),
            resolution2.winner.as_ref().map(|a| &a.subject)
        );
        // Node B has higher node_id [2u8; 16] > [1u8; 16]
        assert_eq!(resolution1.winner.as_ref().map(|a| &a.subject), Some(&"B".to_string()));
    }

    #[test]
    fn test_clock_skew_scenario() {
        // Scenario: Node A's wall clock is ahead, but Node B's assertion is causally later
        // In HLC, the causally later assertion should have a higher HLC timestamp
        let lens = HlcRecencyLens;

        // Node A: wall clock ahead (higher NTP64 base), but logically older event
        let node_a_ahead = create_assertion_with_hlc("NodeA_Ahead", 5000, [1u8; 16]);

        // Node B: wall clock behind, but received Node A's timestamp and incremented
        // In real HLC, this would be: max(local_time, received_time) + 1
        let node_b_later = create_assertion_with_hlc("NodeB_CausallyLater", 5001, [2u8; 16]);

        let resolution = lens.resolve(&[node_a_ahead, node_b_later.clone()]);

        // Node B's assertion should win because it's causally later (higher HLC)
        assert_eq!(
            resolution.winner.as_ref().map(|a| &a.subject),
            Some(&"NodeB_CausallyLater".to_string())
        );
    }

    #[test]
    fn test_source_hash_final_tiebreaker() {
        // When HLC timestamps are completely identical, source_hash is final tiebreaker
        let lens = HlcRecencyLens;

        let mut a1 = AssertionBuilder::new()
            .subject("A")
            .hlc_timestamp(HlcTimestamp::new(1000, [1u8; 16]))
            .build();
        a1.source_hash = [1u8; 32];

        let mut a2 = AssertionBuilder::new()
            .subject("B")
            .hlc_timestamp(HlcTimestamp::new(1000, [1u8; 16])) // Identical HLC!
            .build();
        a2.source_hash = [2u8; 32]; // Higher source_hash

        let resolution = lens.resolve(&[a1.clone(), a2.clone()]);

        // Higher source_hash should win
        assert_eq!(resolution.winner.as_ref().map(|a| &a.subject), Some(&"B".to_string()));
    }

    #[test]
    fn test_confidence_calculation() {
        let lens = HlcRecencyLens;

        // Create assertions with large time gap (> 1 day in milliseconds)
        // NTP64 seconds are in upper 32 bits, so 1 second = 1 << 32
        // For a 2-day gap: 2 * 86400 seconds = 172800 seconds
        const NTP_UNIX_OFFSET: u64 = 2_208_988_800;
        let base_seconds = NTP_UNIX_OFFSET + 1000;
        let ntp64_base = base_seconds << 32;
        let ntp64_later = (base_seconds + 172800) << 32; // 2 days later

        let old = create_assertion_with_hlc("Old", ntp64_base, [1u8; 16]);
        let new = create_assertion_with_hlc("New", ntp64_later, [1u8; 16]);

        let resolution = lens.resolve(&[old, new]);

        assert!(resolution.winner.is_some());
        // With > 1 day gap, confidence should be 0.95
        assert!(
            resolution.resolution_confidence > 0.9,
            "Expected high confidence for large gap, got {}",
            resolution.resolution_confidence
        );
    }

    #[test]
    fn test_multiple_candidates_selects_newest() {
        let lens = HlcRecencyLens;

        let old = create_assertion_with_hlc("Old", 1000, [1u8; 16]);
        let newer = create_assertion_with_hlc("Newer", 2000, [1u8; 16]);
        let newest = create_assertion_with_hlc("Newest", 3000, [1u8; 16]);

        let resolution = lens.resolve(&[old, newer, newest.clone()]);

        assert!(resolution.winner.is_some());
        assert_eq!(resolution.winner.as_ref().map(|a| &a.subject), Some(&"Newest".to_string()));
        assert_eq!(resolution.candidates_count, 3);
    }

    #[test]
    fn test_lens_name() {
        let lens = HlcRecencyLens;
        assert_eq!(lens.name(), "HlcRecency");
    }
}
