//! Audit storage backends
//!
//! Provides multiple storage implementations for audit logs.

use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde_json;
use tokio::fs::{self, OpenOptions};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tracing::{debug, error, info, warn};

use super::types::{AuditEvent, AuditFilter, AuditQuery, RetentionPolicy, SiemFormat};

/// Audit storage trait
#[async_trait]
pub trait AuditStorage: Send + Sync {
    /// Write a single audit event
    async fn write(&self, event: &AuditEvent) -> Result<()>;

    /// Write multiple audit events (batch)
    async fn write_batch(&self, events: &[AuditEvent]) -> Result<()>;

    /// Query audit events
    async fn query(&self, query: AuditQuery) -> Result<Vec<AuditEvent>>;

    /// Export audit events
    async fn export(&self, format: SiemFormat, filter: Option<AuditFilter>) -> Result<Vec<u8>>;

    /// Apply retention policy
    async fn apply_retention(&self, policy: RetentionPolicy) -> Result<usize>;

    /// Get storage statistics
    async fn stats(&self) -> Result<StorageStats>;
}

/// Storage statistics
#[derive(Debug, Clone, serde::Serialize)]
pub struct StorageStats {
    pub total_events: usize,
    pub total_size_bytes: u64,
    pub oldest_event: Option<DateTime<Utc>>,
    pub newest_event: Option<DateTime<Utc>>,
}

/// File-based storage with daily rotation
pub struct FileStorage {
    base_path: PathBuf,
    max_file_size: u64,
}

impl FileStorage {
    /// Create a new file storage backend
    pub async fn new(base_path: PathBuf, max_file_size: u64) -> Result<Self> {
        // Ensure base directory exists
        fs::create_dir_all(&base_path)
            .await
            .context("Failed to create audit log directory")?;

        info!("File storage initialized at: {}", base_path.display());

        Ok(Self {
            base_path,
            max_file_size,
        })
    }

    /// Get the log file path for today
    fn get_current_log_path(&self) -> PathBuf {
        let now = Utc::now();
        let filename = format!("audit-{}.jsonl", now.format("%Y-%m-%d"));
        self.base_path.join(filename)
    }

    /// Get log file path for a specific date
    fn get_log_path_for_date(&self, date: DateTime<Utc>) -> PathBuf {
        let filename = format!("audit-{}.jsonl", date.format("%Y-%m-%d"));
        self.base_path.join(filename)
    }

    /// List all log files
    async fn list_log_files(&self) -> Result<Vec<PathBuf>> {
        let mut files = Vec::new();
        let mut entries = fs::read_dir(&self.base_path)
            .await
            .context("Failed to read audit log directory")?;

        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("jsonl") {
                files.push(path);
            }
        }

        files.sort();
        Ok(files)
    }

    /// Check if rotation is needed
    async fn needs_rotation(&self, path: &Path) -> Result<bool> {
        if !path.exists() {
            return Ok(false);
        }

        let metadata = fs::metadata(path).await?;
        Ok(metadata.len() >= self.max_file_size)
    }

    /// Rotate log file
    async fn rotate_file(&self, path: &Path) -> Result<()> {
        let now = Utc::now();
        let timestamp = now.format("%Y%m%d-%H%M%S");
        let rotated_path = path.with_extension(format!("jsonl.{}", timestamp));

        fs::rename(path, &rotated_path)
            .await
            .context("Failed to rotate log file")?;

        info!(
            "Rotated audit log: {} -> {}",
            path.display(),
            rotated_path.display()
        );
        Ok(())
    }

    /// Read events from a file
    async fn read_file(&self, path: &Path) -> Result<Vec<AuditEvent>> {
        let file = fs::File::open(path)
            .await
            .context("Failed to open audit log file")?;

        let reader = BufReader::new(file);
        let mut lines = reader.lines();
        let mut events = Vec::new();

        while let Some(line) = lines.next_line().await? {
            if line.trim().is_empty() {
                continue;
            }

            match serde_json::from_str::<AuditEvent>(&line) {
                Ok(event) => events.push(event),
                Err(e) => {
                    warn!("Failed to parse audit event: {} (line: {})", e, line);
                }
            }
        }

        Ok(events)
    }
}

#[async_trait]
impl AuditStorage for FileStorage {
    async fn write(&self, event: &AuditEvent) -> Result<()> {
        let log_path = self.get_current_log_path();

        // Check if rotation is needed
        if self.needs_rotation(&log_path).await? {
            self.rotate_file(&log_path).await?;
        }

        // Serialize event as JSON line
        let json_line = serde_json::to_string(event).context("Failed to serialize audit event")?;

        // Append to file
        let mut file = OpenOptions::new()
            .create(true)
            .append(true)
            .open(&log_path)
            .await
            .context("Failed to open audit log file")?;

        file.write_all(json_line.as_bytes()).await?;
        file.write_all(b"\n").await?;
        file.flush().await?;

        debug!("Wrote audit event to: {}", log_path.display());
        Ok(())
    }

    async fn write_batch(&self, events: &[AuditEvent]) -> Result<()> {
        if events.is_empty() {
            return Ok(());
        }

        let log_path = self.get_current_log_path();

        // Check if rotation is needed
        if self.needs_rotation(&log_path).await? {
            self.rotate_file(&log_path).await?;
        }

        // Serialize all events
        let mut buffer = String::new();
        for event in events {
            let json_line =
                serde_json::to_string(event).context("Failed to serialize audit event")?;
            buffer.push_str(&json_line);
            buffer.push('\n');
        }

        // Append batch to file
        let mut file = OpenOptions::new()
            .create(true)
            .append(true)
            .open(&log_path)
            .await
            .context("Failed to open audit log file")?;

        file.write_all(buffer.as_bytes()).await?;
        file.flush().await?;

        debug!(
            "Wrote {} audit events to: {}",
            events.len(),
            log_path.display()
        );
        Ok(())
    }

    async fn query(&self, query: AuditQuery) -> Result<Vec<AuditEvent>> {
        let files = self.list_log_files().await?;
        let mut all_events = Vec::new();

        // Read all log files
        for file_path in files {
            match self.read_file(&file_path).await {
                Ok(events) => all_events.extend(events),
                Err(e) => {
                    error!("Failed to read log file {}: {}", file_path.display(), e);
                }
            }
        }

        // Apply filter
        let filtered: Vec<AuditEvent> = all_events
            .into_iter()
            .filter(|event| query.filter.matches(event))
            .collect();

        // Apply sorting
        let mut sorted = filtered;
        if let Some(ref sort_by) = query.sort_by {
            match sort_by.as_str() {
                "timestamp" => {
                    sorted.sort_by(|a, b| {
                        if query.sort_desc {
                            b.timestamp.cmp(&a.timestamp)
                        } else {
                            a.timestamp.cmp(&b.timestamp)
                        }
                    });
                }
                "user" => {
                    sorted.sort_by(|a, b| {
                        if query.sort_desc {
                            b.user.id.cmp(&a.user.id)
                        } else {
                            a.user.id.cmp(&b.user.id)
                        }
                    });
                }
                _ => {}
            }
        }

        // Apply pagination
        let offset = query.offset.unwrap_or(0);
        let limit = query.limit.unwrap_or(sorted.len());

        let result = sorted.into_iter().skip(offset).take(limit).collect();

        Ok(result)
    }

    async fn export(&self, format: SiemFormat, filter: Option<AuditFilter>) -> Result<Vec<u8>> {
        let query = AuditQuery {
            filter: filter.unwrap_or_default(),
            limit: None,
            offset: None,
            sort_by: Some("timestamp".to_string()),
            sort_desc: false,
        };

        let events = self.query(query).await?;

        match format {
            SiemFormat::Json => {
                let json =
                    serde_json::to_vec_pretty(&events).context("Failed to serialize to JSON")?;
                Ok(json)
            }
            SiemFormat::JsonLines => {
                let mut output = Vec::new();
                for event in events {
                    let line = serde_json::to_string(&event)?;
                    output.extend_from_slice(line.as_bytes());
                    output.push(b'\n');
                }
                Ok(output)
            }
            SiemFormat::Csv => {
                let mut csv = String::from(
                    "event_id,timestamp,user_id,action_type,resource,workspace,status,duration_ms\\
                     \
                     n",
                );
                for event in events {
                    csv.push_str(&format!(
                        "{},{},{},{:?},{},{},{:?},{}\n",
                        event.event_id,
                        event.timestamp.to_rfc3339(),
                        event.user.id,
                        event.action.action_type,
                        event.action.resource,
                        event.action.workspace,
                        event.result.status,
                        event.result.duration_ms,
                    ));
                }
                Ok(csv.into_bytes())
            }
            SiemFormat::SplunkCim => {
                // Splunk Common Information Model format
                let splunk_events: Vec<_> = events
                    .iter()
                    .map(|event| {
                        serde_json::json!({
                            "time": event.timestamp.timestamp(),
                            "host": event.user.ip,
                            "source": "provisioning-audit",
                            "sourcetype": "audit:infrastructure",
                            "event": {
                                "event_id": event.event_id,
                                "user": event.user.id,
                                "action": format!("{:?}", event.action.action_type),
                                "object": event.action.resource,
                                "status": format!("{:?}", event.result.status),
                                "duration": event.result.duration_ms,
                            }
                        })
                    })
                    .collect();

                let json = serde_json::to_vec_pretty(&splunk_events)?;
                Ok(json)
            }
            SiemFormat::ElasticEcs => {
                // Elastic Common Schema format
                let ecs_events: Vec<_> = events
                    .iter()
                    .map(|event| {
                        serde_json::json!({
                            "@timestamp": event.timestamp.to_rfc3339(),
                            "event": {
                                "id": event.event_id,
                                "action": format!("{:?}", event.action.action_type),
                                "outcome": match event.result.status {
                                    super::types::AuditStatus::Success => "success",
                                    super::types::AuditStatus::Failure => "failure",
                                    super::types::AuditStatus::Error => "failure",
                                    super::types::AuditStatus::Pending => "unknown",
                                },
                                "duration": event.result.duration_ms * 1_000_000, // Convert to nanoseconds
                            },
                            "user": {
                                "id": event.user.id,
                                "name": event.user.name,
                            },
                            "source": {
                                "ip": event.user.ip,
                            },
                            "resource": {
                                "id": event.action.resource,
                                "type": event.action.workspace,
                            },
                        })
                    })
                    .collect();

                let json = serde_json::to_vec_pretty(&ecs_events)?;
                Ok(json)
            }
        }
    }

    async fn apply_retention(&self, policy: RetentionPolicy) -> Result<usize> {
        let cutoff_date = policy.cutoff_date();
        let files = self.list_log_files().await?;
        let mut deleted_count = 0;

        for file_path in files {
            // Parse date from filename
            let filename = file_path.file_stem().and_then(|s| s.to_str()).unwrap_or("");

            // Extract date from filename (format: audit-YYYY-MM-DD)
            if let Some(date_str) = filename.strip_prefix("audit-") {
                if let Ok(file_date) = chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
                    let file_datetime = file_date
                        .and_hms_opt(0, 0, 0)
                        .map(|dt| DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc));

                    if let Some(dt) = file_datetime {
                        if dt < cutoff_date {
                            // Archive if configured
                            if policy.archive_before_delete {
                                if let Some(ref archive_path) = policy.archive_path {
                                    let archive_dir = PathBuf::from(archive_path);
                                    fs::create_dir_all(&archive_dir).await?;

                                    let archive_file =
                                        archive_dir.join(file_path.file_name().unwrap());

                                    fs::copy(&file_path, &archive_file)
                                        .await
                                        .context("Failed to archive log file")?;

                                    info!("Archived audit log: {}", archive_file.display());
                                }
                            }

                            // Delete old file
                            fs::remove_file(&file_path)
                                .await
                                .context("Failed to delete old log file")?;

                            deleted_count += 1;
                            info!("Deleted old audit log: {}", file_path.display());
                        }
                    }
                }
            }
        }

        Ok(deleted_count)
    }

    async fn stats(&self) -> Result<StorageStats> {
        let files = self.list_log_files().await?;
        let mut total_events = 0;
        let mut total_size_bytes = 0;
        let mut oldest: Option<DateTime<Utc>> = None;
        let mut newest: Option<DateTime<Utc>> = None;

        for file_path in files {
            // Get file size
            if let Ok(metadata) = fs::metadata(&file_path).await {
                total_size_bytes += metadata.len();
            }

            // Read events to get timestamps
            if let Ok(events) = self.read_file(&file_path).await {
                total_events += events.len();

                for event in events {
                    match (oldest, newest) {
                        (None, None) => {
                            oldest = Some(event.timestamp);
                            newest = Some(event.timestamp);
                        }
                        (Some(old), Some(new)) => {
                            if event.timestamp < old {
                                oldest = Some(event.timestamp);
                            }
                            if event.timestamp > new {
                                newest = Some(event.timestamp);
                            }
                        }
                        _ => {}
                    }
                }
            }
        }

        Ok(StorageStats {
            total_events,
            total_size_bytes,
            oldest_event: oldest,
            newest_event: newest,
        })
    }
}

/// Database storage backend (PostgreSQL/SurrealDB)
pub struct DatabaseStorage {
    // TODO: Implement database storage
    // This would connect to PostgreSQL or SurrealDB
    _placeholder: (),
}

#[async_trait]
impl AuditStorage for DatabaseStorage {
    async fn write(&self, _event: &AuditEvent) -> Result<()> {
        anyhow::bail!("Database storage not yet implemented")
    }

    async fn write_batch(&self, _events: &[AuditEvent]) -> Result<()> {
        anyhow::bail!("Database storage not yet implemented")
    }

    async fn query(&self, _query: AuditQuery) -> Result<Vec<AuditEvent>> {
        anyhow::bail!("Database storage not yet implemented")
    }

    async fn export(&self, _format: SiemFormat, _filter: Option<AuditFilter>) -> Result<Vec<u8>> {
        anyhow::bail!("Database storage not yet implemented")
    }

    async fn apply_retention(&self, _policy: RetentionPolicy) -> Result<usize> {
        anyhow::bail!("Database storage not yet implemented")
    }

    async fn stats(&self) -> Result<StorageStats> {
        anyhow::bail!("Database storage not yet implemented")
    }
}

/// SIEM exporter (webhook-based)
pub struct SiemExporter {
    endpoint: String,
    format: SiemFormat,
}

impl SiemExporter {
    pub fn new(endpoint: String, format: SiemFormat) -> Self {
        Self { endpoint, format }
    }

    /// Export events to SIEM endpoint
    pub async fn export_batch(&self, events: &[AuditEvent]) -> Result<()> {
        let client = reqwest::Client::new();

        // Convert events to target format
        let payload = match self.format {
            SiemFormat::Json => serde_json::to_vec(events)?,
            SiemFormat::JsonLines => {
                let mut output = Vec::new();
                for event in events {
                    let line = serde_json::to_string(event)?;
                    output.extend_from_slice(line.as_bytes());
                    output.push(b'\n');
                }
                output
            }
            _ => serde_json::to_vec(events)?,
        };

        // Send to SIEM endpoint
        let response = client
            .post(&self.endpoint)
            .header("Content-Type", "application/json")
            .body(payload)
            .send()
            .await
            .context("Failed to send events to SIEM endpoint")?;

        if !response.status().is_success() {
            anyhow::bail!("SIEM endpoint returned error: {}", response.status());
        }

        info!(
            "Exported {} events to SIEM: {}",
            events.len(),
            self.endpoint
        );
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use tempfile::TempDir;

    use super::super::types::{ActionInfo, ActionType, AuthorizationInfo, UserInfo};
    use super::*;

    fn create_test_event() -> AuditEvent {
        let user = UserInfo {
            id: "test_user".to_string(),
            name: "Test User".to_string(),
            ip: "127.0.0.1".to_string(),
            user_agent: "test".to_string(),
            email: None,
            session_id: None,
        };

        let action = ActionInfo {
            action_type: ActionType::ServerCreate,
            resource: "test-server".to_string(),
            workspace: "test".to_string(),
            parameters: serde_json::json!({"count": 1}),
            tags: HashMap::new(),
        };

        let authz = AuthorizationInfo {
            token_id: "test_token".to_string(),
            cedar_policy: "allow_all".to_string(),
            mfa_verified: false,
            permissions: vec![],
            evaluation_duration_us: None,
        };

        AuditEvent::new(user, action, authz).complete(100)
    }

    #[tokio::test]
    async fn test_file_storage_write() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path().to_path_buf(), 1024 * 1024)
            .await
            .unwrap();

        let event = create_test_event();
        storage.write(&event).await.unwrap();

        // Verify file was created
        let log_path = storage.get_current_log_path();
        assert!(log_path.exists());
    }

    #[tokio::test]
    async fn test_file_storage_batch_write() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path().to_path_buf(), 1024 * 1024)
            .await
            .unwrap();

        let events = vec![
            create_test_event(),
            create_test_event(),
            create_test_event(),
        ];
        storage.write_batch(&events).await.unwrap();

        // Query back
        let query = AuditQuery::default();
        let retrieved = storage.query(query).await.unwrap();

        assert_eq!(retrieved.len(), 3);
    }

    #[tokio::test]
    async fn test_file_storage_query_with_filter() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path().to_path_buf(), 1024 * 1024)
            .await
            .unwrap();

        // Write events with different users
        for i in 0..5 {
            let mut event = create_test_event();
            event.user.id = format!("user{}", i);
            storage.write(&event).await.unwrap();
        }

        // Query with filter
        let query = AuditQuery {
            filter: AuditFilter {
                user_id: Some("user2".to_string()),
                ..Default::default()
            },
            ..Default::default()
        };

        let results = storage.query(query).await.unwrap();
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].user.id, "user2");
    }

    #[tokio::test]
    async fn test_file_storage_stats() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path().to_path_buf(), 1024 * 1024)
            .await
            .unwrap();

        let events = vec![create_test_event(), create_test_event()];
        storage.write_batch(&events).await.unwrap();

        let stats = storage.stats().await.unwrap();
        assert_eq!(stats.total_events, 2);
        assert!(stats.total_size_bytes > 0);
        assert!(stats.oldest_event.is_some());
        assert!(stats.newest_event.is_some());
    }

    #[tokio::test]
    async fn test_export_csv() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path().to_path_buf(), 1024 * 1024)
            .await
            .unwrap();

        let event = create_test_event();
        storage.write(&event).await.unwrap();

        let csv = storage.export(SiemFormat::Csv, None).await.unwrap();
        let csv_str = String::from_utf8(csv).unwrap();

        assert!(csv_str.contains("event_id,timestamp"));
        assert!(csv_str.contains("test_user"));
    }
}
