//! Async Audit Logger Module
//!
//! Writes audit logs asynchronously using a channel

use crate::core::db::PostgresDb;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::task::JoinHandle;

/// Audit log entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
    pub key_id: String,
    pub action: String,
    pub actor: Option<String>,
    pub ip_address: Option<String>,
    pub user_agent: Option<String>,
    pub request_path: Option<String>,
    pub response_code: Option<i32>,
    pub anomaly_type: Option<String>,
    pub details: Option<serde_json::Value>,
}

/// Async audit logger configuration
#[derive(Debug, Clone)]
pub struct AuditLoggerConfig {
    pub channel_buffer_size: usize,
    pub batch_size: usize,
    pub flush_interval_ms: u64,
}

impl Default for AuditLoggerConfig {
    fn default() -> Self {
        Self {
            channel_buffer_size: std::env::var("AUDIT_LOGGER_BUFFER_SIZE")
                .unwrap_or_else(|_| "1000".to_string())
                .parse()
                .unwrap_or(1000),
            batch_size: std::env::var("AUDIT_LOGGER_BATCH_SIZE")
                .unwrap_or_else(|_| "100".to_string())
                .parse()
                .unwrap_or(100),
            flush_interval_ms: std::env::var("AUDIT_LOGGER_FLUSH_INTERVAL_MS")
                .unwrap_or_else(|_| "1000".to_string())
                .parse()
                .unwrap_or(1000),
        }
    }
}

/// Async audit logger
pub struct AsyncAuditLogger {
    sender: Sender<AuditEntry>,
    handle: JoinHandle<()>,
}

impl AsyncAuditLogger {
    /// Create a new async audit logger
    pub fn new(db: PostgresDb, config: AuditLoggerConfig) -> Self {
        let (sender, receiver) = mpsc::channel(config.channel_buffer_size);

        let handle = tokio::spawn(Self::logger_task(db, receiver, config));

        Self { sender, handle }
    }

    /// Create with default config
    pub fn with_default_config(db: PostgresDb) -> Self {
        Self::new(db, AuditLoggerConfig::default())
    }

    /// Log an audit entry
    pub async fn log(&self, entry: AuditEntry) -> Result<()> {
        self.sender
            .send(entry)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to send audit entry: {}", e))
    }

    /// Shutdown the logger
    pub async fn shutdown(self) -> Result<()> {
        drop(self.sender);
        self.handle.await?;
        Ok(())
    }

    /// Logger background task
    async fn logger_task(
        db: PostgresDb,
        mut receiver: Receiver<AuditEntry>,
        config: AuditLoggerConfig,
    ) {
        let mut batch = Vec::with_capacity(config.batch_size);
        let mut interval =
            tokio::time::interval(std::time::Duration::from_millis(config.flush_interval_ms));

        loop {
            tokio::select! {
                Some(entry) = receiver.recv() => {
                    batch.push(entry);

                    if batch.len() >= config.batch_size {
                        if let Err(e) = Self::flush_batch(&db, &batch).await {
                            tracing::error!("Failed to flush audit batch: {}", e);
                        }
                        batch.clear();
                    }
                }
                _ = interval.tick() => {
                    if !batch.is_empty() {
                        if let Err(e) = Self::flush_batch(&db, &batch).await {
                            tracing::error!("Failed to flush audit batch: {}", e);
                        }
                        batch.clear();
                    }
                }
                else => {
                    // Channel closed
                    if !batch.is_empty() {
                        if let Err(e) = Self::flush_batch(&db, &batch).await {
                            tracing::error!("Failed to flush final audit batch: {}", e);
                        }
                    }
                    break;
                }
            }
        }

        tracing::info!("Audit logger task stopped");
    }

    /// Flush a batch of entries to the database
    async fn flush_batch(db: &PostgresDb, entries: &[AuditEntry]) -> Result<()> {
        if entries.is_empty() {
            return Ok(());
        }

        tracing::debug!("Flushing {} audit entries", entries.len());

        for entry in entries {
            if let Err(e) = db
                .log_api_key_audit(
                    &entry.key_id,
                    &entry.action,
                    entry.actor.as_deref(),
                    entry.ip_address.as_deref(),
                    entry.user_agent.as_deref(),
                    entry.request_path.as_deref(),
                    entry.response_code,
                    entry.anomaly_type.as_deref(),
                    entry.details.as_ref(),
                )
                .await
            {
                tracing::error!("Failed to write audit entry: {}", e);
            }
        }

        Ok(())
    }
}

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

    #[test]
    fn test_audit_logger_config_default() {
        let config = AuditLoggerConfig::default();
        assert!(config.channel_buffer_size > 0);
        assert!(config.batch_size > 0);
        assert!(config.flush_interval_ms > 0);
    }

    #[test]
    fn test_audit_entry_creation() {
        let entry = AuditEntry {
            key_id: "test_key".to_string(),
            action: "validate".to_string(),
            actor: Some("user1".to_string()),
            ip_address: Some("192.168.1.1".to_string()),
            user_agent: Some("Mozilla/5.0".to_string()),
            request_path: Some("/api/test".to_string()),
            response_code: Some(200),
            anomaly_type: None,
            details: None,
        };

        assert_eq!(entry.key_id, "test_key");
        assert_eq!(entry.action, "validate");
    }
}
