//! Log viewer for monitoring application events

use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

/// Log severity level
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum LogLevel {
    /// Trace level - most verbose
    Trace,
    /// Debug level
    Debug,
    /// Info level
    Info,
    /// Warning level
    Warn,
    /// Error level
    Error,
    /// Fatal level - most severe
    Fatal,
}

impl LogLevel {
    /// Get display string
    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::Trace => "TRACE",
            Self::Debug => "DEBUG",
            Self::Info => "INFO",
            Self::Warn => "WARN",
            Self::Error => "ERROR",
            Self::Fatal => "FATAL",
        }
    }

    /// Check if this level is at least as severe as other
    #[must_use]
    pub const fn is_at_least(&self, other: Self) -> bool {
        (*self as u8) >= (other as u8)
    }
}

/// Single log entry
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LogEntry {
    /// Log level
    pub level: LogLevel,
    /// Logger name/module
    pub logger: String,
    /// Log message
    pub message: String,
    /// Timestamp
    pub timestamp: u64,
    /// Additional context
    pub context: Option<String>,
}

impl LogEntry {
    /// Create new log entry
    #[must_use]
    pub fn new(level: LogLevel, logger: String, message: String) -> Self {
        Self {
            level,
            logger,
            message,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
            context: None,
        }
    }

    /// Add context information
    #[must_use]
    pub fn with_context(mut self, context: String) -> Self {
        self.context = Some(context);
        self
    }

    /// Get display string
    #[must_use]
    pub fn display(&self) -> String {
        format!(
            "[{}] {}: {}",
            self.level.as_str(),
            self.logger,
            self.message
        )
    }
}

/// Log level filter
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LogFilter {
    /// Minimum level to show
    pub min_level: LogLevel,
    /// Filter by logger name
    pub logger_filter: Option<String>,
    /// Filter by message content
    pub message_filter: Option<String>,
}

impl LogFilter {
    /// Create new filter (show all)
    #[must_use]
    pub const fn new() -> Self {
        Self {
            min_level: LogLevel::Trace,
            logger_filter: None,
            message_filter: None,
        }
    }

    /// Set minimum level
    #[must_use]
    pub const fn with_level(mut self, level: LogLevel) -> Self {
        self.min_level = level;
        self
    }

    /// Set logger filter
    #[must_use]
    pub fn with_logger(mut self, logger: String) -> Self {
        self.logger_filter = Some(logger);
        self
    }

    /// Set message filter
    #[must_use]
    pub fn with_message(mut self, message: String) -> Self {
        self.message_filter = Some(message);
        self
    }

    /// Check if entry matches filter
    #[must_use]
    pub fn matches(&self, entry: &LogEntry) -> bool {
        // Check level
        if !entry.level.is_at_least(self.min_level) {
            return false;
        }

        // Check logger filter
        if let Some(ref logger_filter) = self.logger_filter {
            if !entry.logger.contains(logger_filter) {
                return false;
            }
        }

        // Check message filter
        if let Some(ref message_filter) = self.message_filter {
            if !entry.message.contains(message_filter) {
                return false;
            }
        }

        true
    }
}

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

/// Log viewer for displaying and filtering logs
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LogViewer {
    /// All log entries
    entries: Vec<LogEntry>,
    /// Current filter
    filter: LogFilter,
    /// Selected entry index
    selected: usize,
    /// Maximum entries to keep
    max_entries: usize,
    /// Whether to auto-scroll to new entries
    auto_scroll: bool,
}

impl LogViewer {
    /// Create new log viewer
    #[must_use]
    pub fn new() -> Self {
        Self::with_capacity(10000)
    }

    /// Create with specified capacity
    #[must_use]
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            entries: Vec::with_capacity(capacity),
            filter: LogFilter::new(),
            selected: 0,
            max_entries: capacity,
            auto_scroll: true,
        }
    }

    /// Add log entry
    pub fn add_entry(&mut self, entry: LogEntry) {
        self.entries.push(entry);

        // Maintain size limit
        while self.entries.len() > self.max_entries {
            self.entries.remove(0);
        }

        // Auto-scroll to new entry
        if self.auto_scroll {
            let filtered = self.filtered_entries();
            if !filtered.is_empty() {
                self.selected = filtered.len() - 1;
            }
        }
    }

    /// Get filtered entries
    #[must_use]
    pub fn filtered_entries(&self) -> Vec<&LogEntry> {
        self.entries
            .iter()
            .filter(|e| self.filter.matches(e))
            .collect()
    }

    /// Get selected entry
    #[must_use]
    pub fn selected_entry(&self) -> Option<&LogEntry> {
        let filtered = self.filtered_entries();
        filtered.get(self.selected).copied()
    }

    /// Set filter
    pub fn set_filter(&mut self, filter: LogFilter) {
        self.filter = filter;
        self.selected = 0;
    }

    /// Clear filter
    pub fn clear_filter(&mut self) {
        self.filter = LogFilter::new();
        self.selected = 0;
    }

    /// Select next entry
    pub fn select_next(&mut self) {
        let filtered = self.filtered_entries();
        if !filtered.is_empty() {
            self.selected = (self.selected + 1) % filtered.len();
        }
    }

    /// Select previous entry
    pub fn select_previous(&mut self) {
        let filtered = self.filtered_entries();
        if !filtered.is_empty() {
            self.selected = if self.selected == 0 {
                filtered.len() - 1
            } else {
                self.selected - 1
            };
        }
    }

    /// Toggle auto-scroll
    pub const fn toggle_auto_scroll(&mut self) {
        self.auto_scroll = !self.auto_scroll;
    }

    /// Clear all logs
    pub fn clear(&mut self) {
        self.entries.clear();
        self.selected = 0;
    }

    /// Get total entry count
    #[must_use]
    pub const fn total_count(&self) -> usize {
        self.entries.len()
    }

    /// Get filtered entry count
    #[must_use]
    pub fn filtered_count(&self) -> usize {
        self.filtered_entries().len()
    }

    /// Count entries by level
    #[must_use]
    pub fn count_by_level(&self, level: LogLevel) -> usize {
        self.entries.iter().filter(|e| e.level == level).count()
    }

    /// Get entries by logger
    #[must_use]
    pub fn entries_by_logger(&self, logger: &str) -> Vec<&LogEntry> {
        self.entries.iter().filter(|e| e.logger == logger).collect()
    }
}

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

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

    #[test]
    fn test_log_level_as_str() {
        assert_eq!(LogLevel::Debug.as_str(), "DEBUG");
        assert_eq!(LogLevel::Error.as_str(), "ERROR");
    }

    #[test]
    fn test_log_level_is_at_least() {
        assert!(LogLevel::Error.is_at_least(LogLevel::Warn));
        assert!(!LogLevel::Warn.is_at_least(LogLevel::Error));
        assert!(LogLevel::Error.is_at_least(LogLevel::Error));
    }

    #[test]
    fn test_log_entry_new() {
        let entry = LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Application started".to_string(),
        );

        assert_eq!(entry.level, LogLevel::Info);
        assert_eq!(entry.logger, "app");
        assert_eq!(entry.message, "Application started");
    }

    #[test]
    fn test_log_entry_with_context() {
        let entry = LogEntry::new(
            LogLevel::Error,
            "db".to_string(),
            "Connection failed".to_string(),
        )
        .with_context("host: localhost:5432".to_string());

        assert_eq!(entry.context, Some("host: localhost:5432".to_string()));
    }

    #[test]
    fn test_log_filter_new() {
        let filter = LogFilter::new();
        assert_eq!(filter.min_level, LogLevel::Trace);
        assert!(filter.logger_filter.is_none());
        assert!(filter.message_filter.is_none());
    }

    #[test]
    fn test_log_filter_matches() {
        let filter = LogFilter::new().with_level(LogLevel::Warn);

        let entry = LogEntry::new(LogLevel::Error, "app".to_string(), "Error".to_string());

        assert!(filter.matches(&entry));

        let entry = LogEntry::new(LogLevel::Debug, "app".to_string(), "Debug".to_string());

        assert!(!filter.matches(&entry));
    }

    #[test]
    fn test_log_viewer_new() {
        let viewer = LogViewer::new();
        assert_eq!(viewer.total_count(), 0);
        assert!(viewer.auto_scroll);
    }

    #[test]
    fn test_add_entry() {
        let mut viewer = LogViewer::new();
        let entry = LogEntry::new(LogLevel::Info, "app".to_string(), "Started".to_string());

        viewer.add_entry(entry);

        assert_eq!(viewer.total_count(), 1);
    }

    #[test]
    fn test_add_multiple_entries() {
        let mut viewer = LogViewer::new();

        for i in 0..10 {
            let entry = LogEntry::new(LogLevel::Info, "app".to_string(), format!("Message {i}"));
            viewer.add_entry(entry);
        }

        assert_eq!(viewer.total_count(), 10);
    }

    #[test]
    fn test_log_viewer_capacity_limit() {
        let mut viewer = LogViewer::with_capacity(5);

        for i in 0..10 {
            let entry = LogEntry::new(LogLevel::Info, "app".to_string(), format!("Message {i}"));
            viewer.add_entry(entry);
        }

        assert_eq!(viewer.total_count(), 5);
    }

    #[test]
    fn test_filtered_entries() {
        let mut viewer = LogViewer::new();
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Info".to_string(),
        ));
        viewer.add_entry(LogEntry::new(
            LogLevel::Error,
            "app".to_string(),
            "Error".to_string(),
        ));

        let filter = LogFilter::new().with_level(LogLevel::Error);
        viewer.set_filter(filter);

        assert_eq!(viewer.filtered_count(), 1);
    }

    #[test]
    fn test_select_next() {
        let mut viewer = LogViewer::new();
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Msg1".to_string(),
        ));
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Msg2".to_string(),
        ));

        assert_eq!(viewer.selected, 1); // Auto-scrolled
        viewer.select_previous();
        assert_eq!(viewer.selected, 0);
    }

    #[test]
    fn test_count_by_level() {
        let mut viewer = LogViewer::new();
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Info1".to_string(),
        ));
        viewer.add_entry(LogEntry::new(
            LogLevel::Error,
            "app".to_string(),
            "Error1".to_string(),
        ));
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Info2".to_string(),
        ));

        assert_eq!(viewer.count_by_level(LogLevel::Info), 2);
        assert_eq!(viewer.count_by_level(LogLevel::Error), 1);
    }

    #[test]
    fn test_entries_by_logger() {
        let mut viewer = LogViewer::new();
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Message".to_string(),
        ));
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "db".to_string(),
            "Message".to_string(),
        ));
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Message".to_string(),
        ));

        let app_entries = viewer.entries_by_logger("app");
        assert_eq!(app_entries.len(), 2);
    }

    #[test]
    fn test_toggle_auto_scroll() {
        let mut viewer = LogViewer::new();
        assert!(viewer.auto_scroll);

        viewer.toggle_auto_scroll();
        assert!(!viewer.auto_scroll);

        viewer.toggle_auto_scroll();
        assert!(viewer.auto_scroll);
    }

    #[test]
    fn test_clear_logs() {
        let mut viewer = LogViewer::new();
        viewer.add_entry(LogEntry::new(
            LogLevel::Info,
            "app".to_string(),
            "Message".to_string(),
        ));

        viewer.clear();

        assert_eq!(viewer.total_count(), 0);
    }
}
