//! TTL and Data Lifecycle Management
//!
//! Provides helpers for managing document lifecycle:
//! - **Soft-delete pattern**: Avoids husking on high-churn data (beacons, positions)
//! - **EVICT helpers**: Local storage cleanup for edge devices
//! - **Tombstone TTL config**: Configure tombstone reaping interval
//! - **Offline retention**: Connectivity-aware eviction policies
//!
//! ## Deletion Model
//!
//! Two deletion mechanisms:
//!
//! 1. **DELETE/EVICT**: Creates tombstones that sync mesh-wide and auto-reap after `TOMBSTONE_TTL_HOURS`
//! 2. **EVICT (local)**: Removes documents from local storage only (no tombstone, may re-sync)
//!
//! ## Architectural Challenges
//!
//! ### Delete-Then-Disconnect Problem
//!
//! When a node deletes a document then goes offline, other nodes that were offline
//! during the delete may resurrect the document when they come back online with stale data.
//!
//! **Solution**: Tombstones persist for `TOMBSTONE_TTL_HOURS`, ensuring deletions propagate
//! even to nodes offline during the delete operation.
//!
//! ### Concurrent Delete-Update (Husking)
//!
//! When one node updates a field while another deletes the document, CRDT merge can
//! create "husked documents" where updated fields exist but all others are null.
//!
//! **Solution**: Use soft-delete pattern for high-churn data (update `_deleted` flag instead
//! of deleting).
//!
//! ## Usage
//!
//! ```ignore
//! use peat_mesh::storage::ttl::{TtlConfig, EvictionStrategy};
//! use std::time::Duration;
//!
//! // Configure for tactical operations
//! let config = TtlConfig::tactical();
//!
//! // Or custom configuration
//! let config = TtlConfig::new()
//!     .with_beacon_ttl(Duration::from_secs(300))  // 5 min soft-delete
//!     .with_eviction(EvictionStrategy::OldestFirst);
//! ```

use std::time::Duration;

/// TTL configuration for data lifecycle management
///
/// Provides collection-specific TTLs and eviction strategies that coordinate
/// with tombstone TTL-based reaping.
#[derive(Debug, Clone)]
pub struct TtlConfig {
    /// Tombstone TTL (hours)
    ///
    /// Controls how long tombstones persist before being reaped.
    pub tombstone_ttl_hours: u32,

    /// Enable automatic tombstone reaping
    pub tombstone_reaping_enabled: bool,

    /// Days between tombstone reaping scans (default: 1)
    pub days_between_reaping: u32,

    /// Beacon soft-delete TTL (avoids husking)
    ///
    /// Beacons are high-churn data. Use soft-delete pattern to mark as deleted
    /// rather than creating tombstones.
    pub beacon_ttl: Duration,

    /// Node position soft-delete TTL (avoids husking)
    pub position_ttl: Duration,

    /// Capability hard-delete TTL
    ///
    /// Capabilities are low-churn, coordinated updates. Safe to use hard delete (EVICT).
    pub capability_ttl: Duration,

    /// EVICT strategy for edge devices with storage constraints
    pub evict_strategy: EvictionStrategy,

    /// Offline retention policy (optional)
    pub offline_policy: Option<OfflineRetentionPolicy>,
}

/// Eviction strategy when storage limits are reached or node is offline
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(clippy::upper_case_acronyms)]
pub enum EvictionStrategy {
    /// Evict oldest documents first (by last_updated_at)
    OldestFirst,

    /// Evict based on storage pressure threshold
    StoragePressure {
        /// Percentage threshold (0-100) to trigger eviction
        threshold_pct: u8,
    },

    /// Keep only last N documents per collection
    KeepLastN(usize),

    /// No automatic eviction
    None,
}

/// Offline retention policy for connectivity-aware lifecycle
///
/// When a node is offline, it cannot contribute to the mesh anyway.
/// More aggressive eviction saves storage and battery while preserving
/// the ability to re-sync latest state from peers when reconnected.
#[derive(Debug, Clone)]
pub struct OfflineRetentionPolicy {
    /// TTL when node is online (normal retention)
    pub online_ttl: Duration,

    /// TTL when node is offline (aggressive eviction)
    ///
    /// Typically 10x shorter than online_ttl to free resources
    pub offline_ttl: Duration,

    /// Always keep last N items per collection, even when offline
    pub keep_last_n: usize,
}

impl TtlConfig {
    /// Create a new TTL configuration with conservative defaults
    pub fn new() -> Self {
        Self {
            tombstone_ttl_hours: 168, // 7 days (Edge SDK default)
            tombstone_reaping_enabled: true,
            days_between_reaping: 1,
            beacon_ttl: Duration::from_secs(600),      // 10 min
            position_ttl: Duration::from_secs(600),    // 10 min
            capability_ttl: Duration::from_secs(7200), // 2 hours
            evict_strategy: EvictionStrategy::None,
            offline_policy: None,
        }
    }

    /// Tactical operations preset (high-frequency updates, short TTLs)
    ///
    /// Optimized for:
    /// - Rapid beacon discovery (5 min TTL)
    /// - Frequent position updates (10 min TTL)
    /// - Short-lived capabilities (2 hour TTL)
    /// - Offline nodes keep only last 10 items
    pub fn tactical() -> Self {
        Self {
            tombstone_ttl_hours: 168, // 7 days
            tombstone_reaping_enabled: true,
            days_between_reaping: 1,
            beacon_ttl: Duration::from_secs(300), // 5 min soft-delete
            position_ttl: Duration::from_secs(600), // 10 min soft-delete
            capability_ttl: Duration::from_secs(7200), // 2 hours
            evict_strategy: EvictionStrategy::OldestFirst,
            offline_policy: Some(OfflineRetentionPolicy {
                online_ttl: Duration::from_secs(600), // 10 min
                offline_ttl: Duration::from_secs(60), // 1 min when offline
                keep_last_n: 10,
            }),
        }
    }

    /// Long-duration operations preset (ISR, surveillance)
    ///
    /// Optimized for:
    /// - Longer beacon retention (10 min)
    /// - Extended position history (1 hour)
    /// - Long-lived capabilities (48 hours)
    pub fn long_duration() -> Self {
        Self {
            tombstone_ttl_hours: 168, // 7 days
            tombstone_reaping_enabled: true,
            days_between_reaping: 1,
            beacon_ttl: Duration::from_secs(600),    // 10 min
            position_ttl: Duration::from_secs(3600), // 1 hour
            capability_ttl: Duration::from_secs(172800), // 48 hours
            evict_strategy: EvictionStrategy::StoragePressure { threshold_pct: 80 },
            offline_policy: None, // No special offline handling
        }
    }

    /// Offline/storage-constrained node preset
    ///
    /// Optimized for:
    /// - Minimal storage footprint
    /// - Aggressive local eviction (EVICT, not DELETE)
    /// - Short tombstone TTL (3 days instead of 7)
    pub fn offline_node() -> Self {
        Self {
            tombstone_ttl_hours: 72, // 3 days (shorter for storage-constrained edge)
            tombstone_reaping_enabled: true,
            days_between_reaping: 1,
            beacon_ttl: Duration::from_secs(30),   // 30 sec EVICT
            position_ttl: Duration::from_secs(60), // 1 min EVICT
            capability_ttl: Duration::from_secs(300), // 5 min EVICT
            evict_strategy: EvictionStrategy::KeepLastN(10),
            offline_policy: Some(OfflineRetentionPolicy {
                online_ttl: Duration::from_secs(300), // 5 min
                offline_ttl: Duration::from_secs(30), // 30 sec when offline
                keep_last_n: 5,                       // Minimal retention
            }),
        }
    }

    /// Set beacon soft-delete TTL
    pub fn with_beacon_ttl(mut self, ttl: Duration) -> Self {
        self.beacon_ttl = ttl;
        self
    }

    /// Set position soft-delete TTL
    pub fn with_position_ttl(mut self, ttl: Duration) -> Self {
        self.position_ttl = ttl;
        self
    }

    /// Set capability hard-delete TTL
    pub fn with_capability_ttl(mut self, ttl: Duration) -> Self {
        self.capability_ttl = ttl;
        self
    }

    /// Set eviction strategy
    pub fn with_eviction(mut self, strategy: EvictionStrategy) -> Self {
        self.evict_strategy = strategy;
        self
    }

    /// Set offline retention policy
    pub fn with_offline_policy(mut self, policy: OfflineRetentionPolicy) -> Self {
        self.offline_policy = Some(policy);
        self
    }

    /// Set tombstone TTL (hours)
    pub fn with_tombstone_ttl(mut self, hours: u32) -> Self {
        self.tombstone_ttl_hours = hours;
        self
    }

    /// Get TTL for a specific collection
    ///
    /// Returns the appropriate TTL based on collection name and current configuration.
    pub fn get_collection_ttl(&self, collection: &str) -> Option<Duration> {
        match collection {
            "beacons" => Some(self.beacon_ttl),
            // Accept both the canonical hyphenated peat-schema form and the
            // legacy underscored form, mirroring `QoSClass::for_collection`
            // and the other QoS dispatch tables. Without the hyphenated arm a
            // consumer that adopted the canonical name for QoS would get `None`
            // here and silently grow that collection unbounded.
            "node-positions" | "node_positions" => Some(self.position_ttl),
            "capabilities" => Some(self.capability_ttl),
            "hierarchical_commands" => None, // Manual expiration via TimeoutManager
            "cells" => Some(Duration::from_secs(3600)), // 1 hour
            _ => None,
        }
    }
}

impl Default for TtlConfig {
    fn default() -> Self {
        Self::new()
    }
}

impl OfflineRetentionPolicy {
    /// Create a minimal retention policy for storage-constrained devices
    pub fn minimal() -> Self {
        Self {
            online_ttl: Duration::from_secs(300), // 5 min
            offline_ttl: Duration::from_secs(30), // 30 sec
            keep_last_n: 5,
        }
    }

    /// Create a conservative retention policy
    pub fn conservative() -> Self {
        Self {
            online_ttl: Duration::from_secs(3600), // 1 hour
            offline_ttl: Duration::from_secs(300), // 5 min
            keep_last_n: 20,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tactical_preset() {
        let config = TtlConfig::tactical();

        assert_eq!(config.tombstone_ttl_hours, 168); // 7 days
        assert_eq!(config.beacon_ttl, Duration::from_secs(300)); // 5 min
        assert_eq!(config.position_ttl, Duration::from_secs(600)); // 10 min
        assert!(config.offline_policy.is_some());

        let offline = config.offline_policy.unwrap();
        assert_eq!(offline.keep_last_n, 10);
    }

    #[test]
    fn test_long_duration_preset() {
        let config = TtlConfig::long_duration();

        assert_eq!(config.beacon_ttl, Duration::from_secs(600)); // 10 min
        assert_eq!(config.position_ttl, Duration::from_secs(3600)); // 1 hour
        assert_eq!(config.capability_ttl, Duration::from_secs(172800)); // 48 hours
        assert!(config.offline_policy.is_none());
    }

    #[test]
    fn test_offline_node_preset() {
        let config = TtlConfig::offline_node();

        assert_eq!(config.tombstone_ttl_hours, 72); // 3 days (shorter)
        assert_eq!(config.beacon_ttl, Duration::from_secs(30)); // 30 sec
        assert_eq!(config.position_ttl, Duration::from_secs(60)); // 1 min

        match config.evict_strategy {
            EvictionStrategy::KeepLastN(n) => assert_eq!(n, 10),
            _ => panic!("Expected KeepLastN strategy"),
        }
    }

    #[test]
    fn test_collection_ttl_lookup() {
        let config = TtlConfig::tactical();

        assert_eq!(
            config.get_collection_ttl("beacons"),
            Some(Duration::from_secs(300))
        );
        // Canonical hyphenated form and legacy underscored form must resolve
        // to the same TTL (peat-mesh#209 QA: don't reintroduce the dispatch
        // asymmetry the QoS tables removed).
        assert_eq!(
            config.get_collection_ttl("node-positions"),
            Some(Duration::from_secs(600))
        );
        assert_eq!(
            config.get_collection_ttl("node_positions"),
            Some(Duration::from_secs(600))
        );
        assert_eq!(config.get_collection_ttl("hierarchical_commands"), None);
    }

    #[test]
    fn test_builder_pattern() {
        let config = TtlConfig::new()
            .with_beacon_ttl(Duration::from_secs(120))
            .with_eviction(EvictionStrategy::OldestFirst)
            .with_tombstone_ttl(96); // 4 days

        assert_eq!(config.beacon_ttl, Duration::from_secs(120));
        assert_eq!(config.tombstone_ttl_hours, 96);
        assert!(matches!(
            config.evict_strategy,
            EvictionStrategy::OldestFirst
        ));
    }

    #[test]
    fn test_offline_retention_presets() {
        let minimal = OfflineRetentionPolicy::minimal();
        assert_eq!(minimal.offline_ttl, Duration::from_secs(30));
        assert_eq!(minimal.keep_last_n, 5);

        let conservative = OfflineRetentionPolicy::conservative();
        assert_eq!(conservative.online_ttl, Duration::from_secs(3600));
        assert_eq!(conservative.keep_last_n, 20);
    }

    #[test]
    fn test_eviction_strategy_variants() {
        let oldest = EvictionStrategy::OldestFirst;
        let pressure = EvictionStrategy::StoragePressure { threshold_pct: 80 };
        let keep_n = EvictionStrategy::KeepLastN(100);
        let none = EvictionStrategy::None;

        assert!(matches!(oldest, EvictionStrategy::OldestFirst));
        assert!(matches!(
            pressure,
            EvictionStrategy::StoragePressure { threshold_pct: 80 }
        ));
        assert!(matches!(keep_n, EvictionStrategy::KeepLastN(100)));
        assert!(matches!(none, EvictionStrategy::None));
    }

    #[test]
    fn test_tombstone_ttl_edge_constraint() {
        // Edge SDK should never exceed server TTL
        let edge_config = TtlConfig::tactical();
        assert!(edge_config.tombstone_ttl_hours <= 720); // 30 days server default

        // Offline node uses shorter TTL for storage constraints
        let offline_config = TtlConfig::offline_node();
        assert!(offline_config.tombstone_ttl_hours < edge_config.tombstone_ttl_hours);
    }
}
