//! Browser Log Analytics
//!
//! Collects and analyzes browser-side logs including:
//! - JavaScript errors and exceptions
//! - Console logs and warnings
//! - Performance metrics
//! - User interaction events
//! - WebAssembly errors

use super::{AnalyticsEvent, EventLevel, LogSource, generate_event_id};
use anyhow::Result;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::sync::mpsc;

pub mod console_collector;
pub mod error_detector;
pub mod interaction_tracker;

pub use console_collector::ConsoleCollector;
pub use error_detector::ErrorDetector;
pub use interaction_tracker::InteractionTracker;

/// Browser log entry structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserLogEntry {
    pub timestamp: DateTime<Utc>,
    pub level: BrowserLogLevel,
    pub message: String,
    pub source: String,
    pub line: Option<u32>,
    pub column: Option<u32>,
    pub stack_trace: Option<String>,
    pub user_agent: Option<String>,
    pub url: String,
}

/// Browser log levels
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BrowserLogLevel {
    Debug,
    Info,
    Warn,
    Error,
    Assert,
    Trace,
}

impl From<BrowserLogLevel> for EventLevel {
    fn from(level: BrowserLogLevel) -> Self {
        match level {
            BrowserLogLevel::Debug => EventLevel::Debug,
            BrowserLogLevel::Info => EventLevel::Info,
            BrowserLogLevel::Warn => EventLevel::Warn,
            BrowserLogLevel::Error => EventLevel::Error,
            BrowserLogLevel::Assert => EventLevel::Error,
            BrowserLogLevel::Trace => EventLevel::Trace,
        }
    }
}

/// JavaScript error information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JavaScriptError {
    pub message: String,
    pub filename: String,
    pub line_number: u32,
    pub column_number: u32,
    pub stack: Option<String>,
    pub error_type: String,
    pub timestamp: DateTime<Utc>,
}

/// Performance metrics from browser
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserPerformanceMetrics {
    /// Page load time in milliseconds
    pub page_load_time_ms: Option<u64>,
    /// DOM content loaded time
    pub dom_content_loaded_ms: Option<u64>,
    /// First contentful paint
    pub first_contentful_paint_ms: Option<u64>,
    /// Largest contentful paint
    pub largest_contentful_paint_ms: Option<u64>,
    /// Cumulative layout shift
    pub cumulative_layout_shift: Option<f64>,
    /// First input delay
    pub first_input_delay_ms: Option<u64>,
    /// Memory usage
    pub memory_usage_mb: Option<f64>,
}

/// User interaction event
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InteractionEvent {
    pub event_type: String,
    pub element_id: Option<String>,
    pub element_class: Option<String>,
    pub element_tag: String,
    pub page_url: String,
    pub timestamp: DateTime<Utc>,
    pub coordinates: Option<(i32, i32)>,
}

/// Browser analytics metrics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrowserMetrics {
    /// Total JavaScript errors
    pub js_errors: u64,
    /// Console warnings
    pub console_warnings: u64,
    /// User interactions
    pub user_interactions: u64,
    /// Page views
    pub page_views: u64,
    /// Average session duration
    pub avg_session_duration_ms: u64,
    /// Performance metrics
    pub performance: BrowserPerformanceMetrics,
}

impl Default for BrowserMetrics {
    fn default() -> Self {
        Self {
            js_errors: 0,
            console_warnings: 0,
            user_interactions: 0,
            page_views: 0,
            avg_session_duration_ms: 0,
            performance: BrowserPerformanceMetrics {
                page_load_time_ms: None,
                dom_content_loaded_ms: None,
                first_contentful_paint_ms: None,
                largest_contentful_paint_ms: None,
                cumulative_layout_shift: None,
                first_input_delay_ms: None,
                memory_usage_mb: None,
            },
        }
    }
}

/// Browser log collector
pub struct BrowserCollector {
    sender: mpsc::UnboundedSender<AnalyticsEvent>,
    log_sources: Vec<PathBuf>,
    console_collector: ConsoleCollector,
    error_detector: ErrorDetector,
    interaction_tracker: InteractionTracker,
    metrics: BrowserMetrics,
}

impl BrowserCollector {
    /// Create new browser collector
    pub fn new(sender: mpsc::UnboundedSender<AnalyticsEvent>) -> Self {
        let log_sources = vec![
            PathBuf::from("logs/browser/console.log"),
            PathBuf::from("logs/browser/errors.log"),
            PathBuf::from("logs/browser/performance.log"),
        ];

        let console_collector = ConsoleCollector::new();
        let error_detector = ErrorDetector::new();
        let interaction_tracker = InteractionTracker::new();

        Self {
            sender,
            log_sources,
            console_collector,
            error_detector,
            interaction_tracker,
            metrics: BrowserMetrics::default(),
        }
    }

    /// Create with custom log sources
    pub fn with_log_sources(
        sender: mpsc::UnboundedSender<AnalyticsEvent>,
        log_sources: Vec<PathBuf>
    ) -> Self {
        let console_collector = ConsoleCollector::new();
        let error_detector = ErrorDetector::new();
        let interaction_tracker = InteractionTracker::new();

        Self {
            sender,
            log_sources,
            console_collector,
            error_detector,
            interaction_tracker,
            metrics: BrowserMetrics::default(),
        }
    }

    /// Start browser log collection
    pub async fn start_collection(&mut self) -> Result<()> {
        tracing::info!("Starting browser log collection...");

        // Start console log collection
        let sender_clone = self.sender.clone();
        self.console_collector.start_monitoring(sender_clone).await?;

        // Start error detection
        let sender_clone = self.sender.clone();
        self.error_detector.start_monitoring(sender_clone).await?;

        // Start interaction tracking
        let sender_clone = self.sender.clone();
        self.interaction_tracker.start_monitoring(sender_clone).await?;

        // Start log file monitoring
        self.start_log_file_monitoring().await?;

        tracing::info!("Browser log collection started");
        Ok(())
    }

    /// Start monitoring browser log files
    async fn start_log_file_monitoring(&self) -> Result<()> {
        for log_source in &self.log_sources {
            if !log_source.exists() {
                tracing::warn!("Browser log file does not exist: {:?}", log_source);
                continue;
            }

            let log_path = log_source.clone();
            let sender = self.sender.clone();

            tokio::spawn(async move {
                Self::monitor_log_file(log_path, sender).await;
            });
        }

        Ok(())
    }

    /// Monitor a single browser log file
    async fn monitor_log_file(log_path: PathBuf, sender: mpsc::UnboundedSender<AnalyticsEvent>) {
        let mut last_size = 0u64;

        loop {
            match tokio::fs::metadata(&log_path).await {
                Ok(metadata) => {
                    let current_size = metadata.len();
                    if current_size > last_size {
                        // File has grown, read new content
                        if let Ok(content) = tokio::fs::read_to_string(&log_path).await {
                            let new_content = if last_size > 0 {
                                content.chars().skip(last_size as usize).collect()
                            } else {
                                content
                            };

                            // Process new log entries
                            for line in new_content.lines() {
                                if let Ok(event) = Self::parse_browser_log_line(line) {
                                    if let Err(e) = sender.send(event) {
                                        tracing::error!("Failed to send browser log event: {}", e);
                                        return;
                                    }
                                }
                            }
                        }
                        last_size = current_size;
                    }
                }
                Err(_) => {
                    // Log file might not exist yet
                    last_size = 0;
                }
            }

            // Check every 2 seconds for browser logs (more frequent than server logs)
            tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
        }
    }

    /// Parse browser log line and convert to analytics event
    fn parse_browser_log_line(line: &str) -> Result<AnalyticsEvent> {
        // Try to parse as JSON first (structured browser logging)
        if let Ok(entry) = serde_json::from_str::<BrowserLogEntry>(line) {
            return Self::convert_browser_log_entry(entry);
        }

        // Try to parse as JavaScript error
        if let Ok(error) = serde_json::from_str::<JavaScriptError>(line) {
            return Self::convert_js_error(error);
        }

        // Try to parse as performance metrics
        if let Ok(perf) = serde_json::from_str::<BrowserPerformanceMetrics>(line) {
            return Self::convert_performance_metrics(perf);
        }

        // Fall back to plain text parsing
        Self::parse_plain_browser_log(line)
    }

    /// Convert structured browser log entry to analytics event
    fn convert_browser_log_entry(entry: BrowserLogEntry) -> Result<AnalyticsEvent> {
        let mut metadata = HashMap::new();
        metadata.insert("source".to_string(), serde_json::Value::String(entry.source.clone()));
        metadata.insert("url".to_string(), serde_json::Value::String(entry.url.clone()));

        if let Some(line) = entry.line {
            metadata.insert("line".to_string(), serde_json::Value::Number(line.into()));
        }

        if let Some(column) = entry.column {
            metadata.insert("column".to_string(), serde_json::Value::Number(column.into()));
        }

        if let Some(stack_trace) = &entry.stack_trace {
            metadata.insert("stack_trace".to_string(), serde_json::Value::String(stack_trace.clone()));
        }

        if let Some(user_agent) = &entry.user_agent {
            metadata.insert("user_agent".to_string(), serde_json::Value::String(user_agent.clone()));
        }

        let event_type = match entry.level {
            BrowserLogLevel::Error | BrowserLogLevel::Assert => "browser_error",
            BrowserLogLevel::Warn => "browser_warning",
            _ => "browser_log",
        }.to_string();

        Ok(AnalyticsEvent {
            id: generate_event_id(),
            timestamp: entry.timestamp,
            source: LogSource::Browser,
            event_type,
            session_id: None,
            path: Some(entry.url),
            level: entry.level.into(),
            message: entry.message,
            metadata,
            duration_ms: None,
            errors: Vec::new(),
        })
    }

    /// Convert JavaScript error to analytics event
    fn convert_js_error(error: JavaScriptError) -> Result<AnalyticsEvent> {
        let mut metadata = HashMap::new();
        metadata.insert("filename".to_string(), serde_json::Value::String(error.filename.clone()));
        metadata.insert("line_number".to_string(), serde_json::Value::Number(error.line_number.into()));
        metadata.insert("column_number".to_string(), serde_json::Value::Number(error.column_number.into()));
        metadata.insert("error_type".to_string(), serde_json::Value::String(error.error_type.clone()));

        if let Some(stack) = &error.stack {
            metadata.insert("stack".to_string(), serde_json::Value::String(stack.clone()));
        }

        let message = format!("{} at {}:{}:{}",
            error.message, error.filename, error.line_number, error.column_number);

        Ok(AnalyticsEvent {
            id: generate_event_id(),
            timestamp: error.timestamp,
            source: LogSource::Browser,
            event_type: "javascript_error".to_string(),
            session_id: None,
            path: Some(error.filename),
            level: EventLevel::Error,
            message,
            metadata,
            duration_ms: None,
            errors: vec![error.message],
        })
    }

    /// Convert performance metrics to analytics event
    fn convert_performance_metrics(perf: BrowserPerformanceMetrics) -> Result<AnalyticsEvent> {
        let mut metadata = HashMap::new();

        if let Some(load_time) = perf.page_load_time_ms {
            metadata.insert("page_load_time_ms".to_string(), serde_json::Value::Number(load_time.into()));
        }

        if let Some(dom_loaded) = perf.dom_content_loaded_ms {
            metadata.insert("dom_content_loaded_ms".to_string(), serde_json::Value::Number(dom_loaded.into()));
        }

        if let Some(fcp) = perf.first_contentful_paint_ms {
            metadata.insert("first_contentful_paint_ms".to_string(), serde_json::Value::Number(fcp.into()));
        }

        if let Some(lcp) = perf.largest_contentful_paint_ms {
            metadata.insert("largest_contentful_paint_ms".to_string(), serde_json::Value::Number(lcp.into()));
        }

        if let Some(cls) = perf.cumulative_layout_shift {
            metadata.insert("cumulative_layout_shift".to_string(),
                serde_json::Value::Number(serde_json::Number::from_f64(cls).unwrap()));
        }

        if let Some(fid) = perf.first_input_delay_ms {
            metadata.insert("first_input_delay_ms".to_string(), serde_json::Value::Number(fid.into()));
        }

        if let Some(memory) = perf.memory_usage_mb {
            metadata.insert("memory_usage_mb".to_string(),
                serde_json::Value::Number(serde_json::Number::from_f64(memory).unwrap()));
        }

        let message = Self::format_performance_message(&perf);

        Ok(AnalyticsEvent {
            id: generate_event_id(),
            timestamp: Utc::now(),
            source: LogSource::Browser,
            event_type: "browser_performance".to_string(),
            session_id: None,
            path: None,
            level: EventLevel::Info,
            message,
            metadata,
            duration_ms: perf.page_load_time_ms,
            errors: Vec::new(),
        })
    }

    /// Parse plain text browser log
    fn parse_plain_browser_log(line: &str) -> Result<AnalyticsEvent> {
        let level = if line.contains("ERROR") || line.contains("error") {
            EventLevel::Error
        } else if line.contains("WARN") || line.contains("warn") {
            EventLevel::Warn
        } else if line.contains("INFO") || line.contains("info") {
            EventLevel::Info
        } else {
            EventLevel::Debug
        };

        let event_type = if line.contains("error") {
            "browser_error"
        } else if line.contains("performance") {
            "browser_performance"
        } else {
            "browser_log"
        }.to_string();

        Ok(AnalyticsEvent {
            id: generate_event_id(),
            timestamp: Utc::now(),
            source: LogSource::Browser,
            event_type,
            session_id: None,
            path: None,
            level,
            message: line.to_string(),
            metadata: HashMap::new(),
            duration_ms: None,
            errors: Vec::new(),
        })
    }

    /// Format performance metrics message
    fn format_performance_message(perf: &BrowserPerformanceMetrics) -> String {
        let mut parts = Vec::new();

        if let Some(load_time) = perf.page_load_time_ms {
            parts.push(format!("{}ms load", load_time));
        }

        if let Some(fcp) = perf.first_contentful_paint_ms {
            parts.push(format!("{}ms FCP", fcp));
        }

        if let Some(lcp) = perf.largest_contentful_paint_ms {
            parts.push(format!("{}ms LCP", lcp));
        }

        if let Some(cls) = perf.cumulative_layout_shift {
            parts.push(format!("{:.3} CLS", cls));
        }

        if parts.is_empty() {
            "Browser performance metrics".to_string()
        } else {
            format!("Performance: {}", parts.join(", "))
        }
    }

    /// Get current browser metrics
    pub fn get_metrics(&self) -> &BrowserMetrics {
        &self.metrics
    }

    /// Update browser metrics
    pub async fn update_metrics(&mut self) -> Result<()> {
        // Collect current browser metrics from various sources
        self.metrics = Self::collect_browser_metrics().await?;

        // Send metrics update event
        let event = self.create_metrics_event();
        self.sender.send(event)?;

        Ok(())
    }

    /// Collect browser metrics
    async fn collect_browser_metrics() -> Result<BrowserMetrics> {
        // In a real implementation, this would aggregate data from:
        // - Console logs for error counts
        // - Performance API for timing metrics
        // - User interaction events
        // - Session tracking data

        let mut metrics = BrowserMetrics::default();

        // Simulate metrics collection
        metrics.js_errors = rand::random::<u64>() % 10;
        metrics.console_warnings = rand::random::<u64>() % 25;
        metrics.user_interactions = 100 + rand::random::<u64>() % 500;
        metrics.page_views = 50 + rand::random::<u64>() % 200;
        metrics.avg_session_duration_ms = 30000 + rand::random::<u64>() % 120000;

        // Simulate performance metrics
        metrics.performance.page_load_time_ms = Some(800 + rand::random::<u64>() % 2000);
        metrics.performance.dom_content_loaded_ms = Some(400 + rand::random::<u64>() % 1000);
        metrics.performance.first_contentful_paint_ms = Some(600 + rand::random::<u64>() % 1500);
        metrics.performance.largest_contentful_paint_ms = Some(1200 + rand::random::<u64>() % 2000);
        metrics.performance.cumulative_layout_shift = Some(0.05 + rand::random::<f64>() * 0.1);
        metrics.performance.first_input_delay_ms = Some(50 + rand::random::<u64>() % 150);
        metrics.performance.memory_usage_mb = Some(20.0 + rand::random::<f64>() * 30.0);

        Ok(metrics)
    }

    /// Create browser metrics analytics event
    fn create_metrics_event(&self) -> AnalyticsEvent {
        let mut metadata = HashMap::new();
        metadata.insert("js_errors".to_string(),
            serde_json::Value::Number(self.metrics.js_errors.into()));
        metadata.insert("console_warnings".to_string(),
            serde_json::Value::Number(self.metrics.console_warnings.into()));
        metadata.insert("user_interactions".to_string(),
            serde_json::Value::Number(self.metrics.user_interactions.into()));
        metadata.insert("page_views".to_string(),
            serde_json::Value::Number(self.metrics.page_views.into()));
        metadata.insert("avg_session_duration_ms".to_string(),
            serde_json::Value::Number(self.metrics.avg_session_duration_ms.into()));

        // Add performance metrics
        if let Some(load_time) = self.metrics.performance.page_load_time_ms {
            metadata.insert("page_load_time_ms".to_string(),
                serde_json::Value::Number(load_time.into()));
        }

        if let Some(fcp) = self.metrics.performance.first_contentful_paint_ms {
            metadata.insert("first_contentful_paint_ms".to_string(),
                serde_json::Value::Number(fcp.into()));
        }

        let level = if self.metrics.js_errors > 5 {
            EventLevel::Warn
        } else {
            EventLevel::Info
        };

        let message = format!(
            "Browser metrics: {} errors, {} interactions, {} page views",
            self.metrics.js_errors,
            self.metrics.user_interactions,
            self.metrics.page_views
        );

        AnalyticsEvent {
            id: generate_event_id(),
            timestamp: Utc::now(),
            source: LogSource::Browser,
            event_type: "browser_metrics".to_string(),
            session_id: None,
            path: None,
            level,
            message,
            metadata,
            duration_ms: self.metrics.performance.page_load_time_ms,
            errors: Vec::new(),
        }
    }
}
