/// Lease state used as serving fence.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Lease {
    timeout_ms: i64,
    expire_at_ms: i64,
    granted: bool,
}

impl Lease {
    /// Creates a revoked lease.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Grants lease ownership with timeout in milliseconds.
    pub fn grant(&mut self, now_ms: i64, timeout_ms: i64) {
        self.timeout_ms = timeout_ms.max(1);
        self.expire_at_ms = now_ms + self.timeout_ms;
        self.granted = true;
    }

    /// Extends granted lease from current time.
    pub fn keep_alive(&mut self, now_ms: i64) {
        if self.granted {
            self.expire_at_ms = now_ms + self.timeout_ms;
        }
    }

    /// Revokes lease ownership.
    pub fn revoke(&mut self) {
        self.granted = false;
        self.expire_at_ms = 0;
    }

    /// Returns whether lease ownership is currently granted.
    #[must_use]
    pub const fn granted(&self) -> bool {
        self.granted
    }

    /// Returns lease expiration time in milliseconds.
    #[must_use]
    pub const fn expire_at_ms(&self) -> i64 {
        self.expire_at_ms
    }

    /// Returns whether lease is expired at `now_ms`.
    #[must_use]
    pub fn is_expired(&self, now_ms: i64) -> bool {
        !self.granted || now_ms >= self.expire_at_ms
    }

    /// Returns whether lease is valid at `now_ms`.
    #[must_use]
    pub fn is_valid(&self, now_ms: i64) -> bool {
        self.granted && now_ms < self.expire_at_ms
    }
}

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

    #[test]
    fn lease_expires_without_keepalive() {
        let mut lease = Lease::new();
        lease.grant(1_000, 5_000);
        assert!(lease.is_valid(5_999));
        assert!(lease.is_expired(6_000));
    }

    #[test]
    fn keepalive_extends_expiration() {
        let mut lease = Lease::new();
        lease.grant(1_000, 1_000);
        lease.keep_alive(1_500);
        assert!(lease.is_valid(2_499));
        assert!(lease.is_expired(2_500));
    }
}
