//! Bounded audit log for session policy decisions.

/// Maximum audit log entries before truncation.
pub const MAX_AUDIT_ENTRIES: usize = 10_000;
/// Maximum annotation entries.
pub const MAX_ANNOTATIONS: usize = 100;
/// Maximum number of closed sessions retained in memory.
pub const MAX_CLOSED_SESSIONS: usize = 10_000;
/// Number of oldest closed sessions to evict when the cap is exceeded.
pub const EVICT_BATCH_SIZE: usize = 1_000;

// ── AuditEntry ────────────────────────────────────────────────────────────────

/// A single policy check result recorded in the session audit log.
#[derive(Debug, Clone)]
pub struct AuditEntry {
    /// Nanosecond timestamp of the check.
    pub timestamp_ns: u64,
    /// Signal type name that was checked.
    pub signal_type: String,
    /// Whether the signal was accepted.
    pub accepted: bool,
    /// Reason for rejection (present when `accepted == false`).
    pub reason: Option<String>,
}

// ── AuditLog ──────────────────────────────────────────────────────────────────

/// Bounded audit log with overflow eviction.
///
/// Holds up to `MAX_AUDIT_ENTRIES` entries. When the cap is reached, the oldest
/// entry is evicted and `truncated` is set to `true`.
pub struct AuditLog {
    entries: Vec<AuditEntry>,
    /// Set to `true` once an entry has been evicted due to overflow.
    pub truncated: bool,
}

impl AuditLog {
    /// Create an empty audit log.
    #[must_use]
    pub const fn new() -> Self {
        Self {
            entries: Vec::new(),
            truncated: false,
        }
    }

    /// Append an entry, evicting the oldest if the cap is reached.
    pub fn push(&mut self, entry: AuditEntry) {
        if self.entries.len() >= MAX_AUDIT_ENTRIES {
            self.entries.remove(0);
            self.truncated = true;
        }
        self.entries.push(entry);
    }

    /// Borrow the entries slice.
    #[must_use]
    pub fn entries(&self) -> &[AuditEntry] {
        &self.entries
    }

    /// Number of entries currently held.
    #[must_use]
    pub const fn len(&self) -> usize {
        self.entries.len()
    }

    /// `true` if no entries are held.
    #[must_use]
    pub const fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
}

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

// ── Unit tests ────────────────────────────────────────────────────────────────

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

    #[test]
    fn audit_log_truncates_on_overflow() {
        let mut log = AuditLog::new();
        assert!(!log.truncated);

        // Fill to capacity.
        for i in 0..MAX_AUDIT_ENTRIES {
            log.push(AuditEntry {
                timestamp_ns: i as u64,
                signal_type: "view".to_string(),
                accepted: true,
                reason: None,
            });
        }
        assert!(!log.truncated);
        assert_eq!(log.len(), MAX_AUDIT_ENTRIES);

        // One more should evict oldest and set truncated.
        log.push(AuditEntry {
            timestamp_ns: MAX_AUDIT_ENTRIES as u64,
            signal_type: "view".to_string(),
            accepted: true,
            reason: None,
        });
        assert!(log.truncated);
        assert_eq!(log.len(), MAX_AUDIT_ENTRIES);
        // Oldest (ts=0) should have been evicted; newest entry is at the end.
        assert_eq!(log.entries()[0].timestamp_ns, 1);
        assert_eq!(
            log.entries()[MAX_AUDIT_ENTRIES - 1].timestamp_ns,
            MAX_AUDIT_ENTRIES as u64
        );
    }
}
