//! Lease types for TTL-based key expiration
//!
//! Leases provide time-to-live (TTL) functionality for keys. When a lease expires
//! or is revoked, all keys attached to it are automatically deleted.

use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};

/// Unique identifier for a lease
pub type LeaseId = i64;

/// A lease with TTL-based expiration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Lease {
    /// Unique ID of the lease
    pub id: LeaseId,
    /// Time-to-live in seconds (as originally granted)
    pub ttl: i64,
    /// Remaining TTL in seconds (decremented over time)
    #[serde(skip)]
    pub remaining_ttl: i64,
    /// Keys attached to this lease
    pub keys: Vec<Vec<u8>>,
    /// When the lease was created (for TTL calculation)
    #[serde(skip)]
    pub granted_at: Option<Instant>,
}

impl Lease {
    /// Create a new lease with the given ID and TTL
    pub fn new(id: LeaseId, ttl: i64) -> Self {
        Self {
            id,
            ttl,
            remaining_ttl: ttl,
            keys: Vec::new(),
            granted_at: Some(Instant::now()),
        }
    }

    /// Check if the lease has expired
    pub fn is_expired(&self) -> bool {
        if let Some(granted_at) = self.granted_at {
            let elapsed = granted_at.elapsed();
            elapsed >= Duration::from_secs(self.ttl as u64)
        } else {
            // If no granted_at, use remaining_ttl
            self.remaining_ttl <= 0
        }
    }

    /// Get the remaining TTL in seconds
    pub fn remaining(&self) -> i64 {
        if let Some(granted_at) = self.granted_at {
            let elapsed = granted_at.elapsed().as_secs() as i64;
            (self.ttl - elapsed).max(0)
        } else {
            self.remaining_ttl.max(0)
        }
    }

    /// Refresh the lease TTL (for keep-alive)
    pub fn refresh(&mut self) {
        self.granted_at = Some(Instant::now());
        self.remaining_ttl = self.ttl;
    }

    /// Attach a key to this lease
    pub fn attach_key(&mut self, key: Vec<u8>) {
        if !self.keys.contains(&key) {
            self.keys.push(key);
        }
    }

    /// Detach a key from this lease
    pub fn detach_key(&mut self, key: &[u8]) {
        self.keys.retain(|k| k != key);
    }
}

/// Persistent lease data (for serialization without Instant)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LeaseData {
    /// Unique ID of the lease
    pub id: LeaseId,
    /// Time-to-live in seconds
    pub ttl: i64,
    /// Keys attached to this lease
    pub keys: Vec<Vec<u8>>,
    /// Unix timestamp when granted (for persistence)
    pub granted_at_unix: u64,
}

impl LeaseData {
    /// Create lease data from a lease
    pub fn from_lease(lease: &Lease) -> Self {
        use std::time::SystemTime;
        let now = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        Self {
            id: lease.id,
            ttl: lease.ttl,
            keys: lease.keys.clone(),
            granted_at_unix: now,
        }
    }

    /// Convert to a lease (sets granted_at to now)
    pub fn to_lease(&self) -> Lease {
        use std::time::SystemTime;
        let now = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap()
            .as_secs();
        let elapsed = (now - self.granted_at_unix) as i64;
        let remaining = (self.ttl - elapsed).max(0);

        Lease {
            id: self.id,
            ttl: self.ttl,
            remaining_ttl: remaining,
            keys: self.keys.clone(),
            granted_at: Some(Instant::now() - Duration::from_secs(elapsed as u64)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread;
    use std::time::Duration;

    #[test]
    fn test_lease_creation() {
        let lease = Lease::new(1, 10);
        assert_eq!(lease.id, 1);
        assert_eq!(lease.ttl, 10);
        assert!(!lease.is_expired());
    }

    #[test]
    fn test_lease_remaining() {
        let lease = Lease::new(1, 10);
        let remaining = lease.remaining();
        assert!(remaining >= 9 && remaining <= 10);
    }

    #[test]
    fn test_lease_attach_key() {
        let mut lease = Lease::new(1, 10);
        lease.attach_key(b"key1".to_vec());
        lease.attach_key(b"key2".to_vec());
        assert_eq!(lease.keys.len(), 2);

        // Duplicate should not add
        lease.attach_key(b"key1".to_vec());
        assert_eq!(lease.keys.len(), 2);
    }

    #[test]
    fn test_lease_detach_key() {
        let mut lease = Lease::new(1, 10);
        lease.attach_key(b"key1".to_vec());
        lease.attach_key(b"key2".to_vec());

        lease.detach_key(b"key1");
        assert_eq!(lease.keys.len(), 1);
        assert_eq!(lease.keys[0], b"key2".to_vec());
    }

    #[test]
    fn test_lease_refresh() {
        let mut lease = Lease::new(1, 1);
        // Sleep briefly to ensure some time passes
        thread::sleep(Duration::from_millis(100));

        let remaining_before = lease.remaining();
        lease.refresh();
        let remaining_after = lease.remaining();

        // After refresh, remaining should be back to full TTL
        assert!(remaining_after >= remaining_before);
    }
}
