//! Query Analytics for GraphQL Gateway
//!
//! Provides comprehensive analytics tracking including:
//! - Most used queries
//! - Slowest queries
//! - Error patterns
//! - Field usage statistics
//!
//! # Example
//!
//! ```rust,no_run
//! use grpc_graphql_gateway::{Gateway, AnalyticsConfig};
//! use std::time::Duration;
//!
//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let gateway = Gateway::builder()
//!     .enable_analytics(AnalyticsConfig::default())
//!     // ... other configuration
//! #   ;
//! # Ok(())
//! # }
//! ```

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;  // SECURITY: Non-poisoning locks
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use sha2::{Digest, Sha256};  // SECURITY: Use cryptographic hash

/// Configuration for query analytics
#[derive(Debug, Clone)]
pub struct AnalyticsConfig {
    /// Maximum number of unique queries to track
    pub max_queries: usize,
    /// Maximum number of field entries to track
    pub max_fields: usize,
    /// Maximum number of error patterns to track
    pub max_errors: usize,
    /// Whether to track query text (can be large, set to false for privacy)
    pub track_query_text: bool,
    /// Slow query threshold in milliseconds
    pub slow_query_threshold_ms: u64,
    /// Enable dashboard endpoint
    pub enable_dashboard: bool,
    /// Retention period for data (older entries are pruned)
    pub retention_period: Duration,
}

impl Default for AnalyticsConfig {
    fn default() -> Self {
        Self {
            max_queries: 1000,
            max_fields: 500,
            max_errors: 200,
            track_query_text: true,
            slow_query_threshold_ms: 500,
            enable_dashboard: true,
            retention_period: Duration::from_secs(86400), // 24 hours
        }
    }
}

impl AnalyticsConfig {
    /// Create a new analytics config
    pub fn new() -> Self {
        Self::default()
    }

    /// Set max queries to track
    pub fn max_queries(mut self, n: usize) -> Self {
        self.max_queries = n;
        self
    }

    /// Set slow query threshold
    pub fn slow_query_threshold(mut self, threshold: Duration) -> Self {
        self.slow_query_threshold_ms = threshold.as_millis() as u64;
        self
    }

    /// Disable query text tracking for privacy
    pub fn disable_query_text(mut self) -> Self {
        self.track_query_text = false;
        self
    }

    /// Minimal configuration for production with privacy
    pub fn production() -> Self {
        Self {
            max_queries: 500,
            max_fields: 200,
            max_errors: 100,
            track_query_text: false,
            slow_query_threshold_ms: 1000,
            enable_dashboard: true,
            retention_period: Duration::from_secs(3600), // 1 hour
        }
    }

    /// Verbose configuration for development
    pub fn development() -> Self {
        Self {
            max_queries: 2000,
            max_fields: 1000,
            max_errors: 500,
            track_query_text: true,
            slow_query_threshold_ms: 200,
            enable_dashboard: true,
            retention_period: Duration::from_secs(86400 * 7), // 7 days
        }
    }
}

/// Statistics for a single query
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryStats {
    /// Query hash (SHA-256)
    pub query_hash: String,
    /// Operation name (if provided)
    pub operation_name: Option<String>,
    /// Operation type (query/mutation/subscription)
    pub operation_type: String,
    /// Query text (may be truncated or None for privacy)
    pub query_text: Option<String>,
    /// Total number of executions
    pub execution_count: u64,
    /// Total execution time in milliseconds
    pub total_time_ms: u64,
    /// Average execution time in milliseconds
    pub avg_time_ms: f64,
    /// Minimum execution time in milliseconds
    pub min_time_ms: u64,
    /// Maximum execution time in milliseconds
    pub max_time_ms: u64,
    /// Number of errors
    pub error_count: u64,
    /// Last execution timestamp (Unix epoch seconds)
    pub last_execution: u64,
    /// First execution timestamp (Unix epoch seconds)
    pub first_execution: u64,
    /// 95th percentile latency (approximate)
    pub p95_time_ms: f64,
}

impl QueryStats {
    fn new(
        query_hash: String,
        operation_name: Option<String>,
        operation_type: String,
        query_text: Option<String>,
    ) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        Self {
            query_hash,
            operation_name,
            operation_type,
            query_text,
            execution_count: 0,
            total_time_ms: 0,
            avg_time_ms: 0.0,
            min_time_ms: u64::MAX,
            max_time_ms: 0,
            error_count: 0,
            last_execution: now,
            first_execution: now,
            p95_time_ms: 0.0,
        }
    }

    fn record_execution(&mut self, duration_ms: u64, had_error: bool) {
        self.execution_count += 1;
        self.total_time_ms += duration_ms;
        self.avg_time_ms = self.total_time_ms as f64 / self.execution_count as f64;
        self.min_time_ms = self.min_time_ms.min(duration_ms);
        self.max_time_ms = self.max_time_ms.max(duration_ms);
        
        // Approximate P95 using exponential moving average
        if self.execution_count == 1 {
            self.p95_time_ms = duration_ms as f64;
        } else {
            let alpha = 0.05;
            if duration_ms as f64 > self.p95_time_ms {
                self.p95_time_ms = self.p95_time_ms * (1.0 - alpha) + duration_ms as f64 * alpha;
            }
        }
        
        if had_error {
            self.error_count += 1;
        }
        
        self.last_execution = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
    }
}

/// Statistics for field usage
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldStats {
    /// Field name (fully qualified, e.g., "User.email")
    pub field_name: String,
    /// Parent type name
    pub parent_type: String,
    /// Total number of times this field was requested
    pub request_count: u64,
    /// Average resolution time in milliseconds
    pub avg_time_ms: f64,
    /// Last requested timestamp
    pub last_requested: u64,
}

impl FieldStats {
    fn new(field_name: String, parent_type: String) -> Self {
        Self {
            field_name,
            parent_type,
            request_count: 0,
            avg_time_ms: 0.0,
            last_requested: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        }
    }
}

/// Error pattern statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorStats {
    /// Error code or type
    pub error_code: String,
    /// Error message (first occurrence)
    pub error_message: String,
    /// Total occurrences
    pub occurrence_count: u64,
    /// Affected queries (hashes)
    pub affected_queries: Vec<String>,
    /// First occurrence timestamp
    pub first_occurrence: u64,
    /// Last occurrence timestamp
    pub last_occurrence: u64,
}

impl ErrorStats {
    fn new(error_code: String, error_message: String) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        Self {
            error_code,
            error_message,
            occurrence_count: 1,
            affected_queries: Vec::new(),
            first_occurrence: now,
            last_occurrence: now,
        }
    }
}

/// Aggregated analytics data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalyticsSnapshot {
    /// Timestamp of this snapshot
    pub timestamp: u64,
    /// Total requests processed
    pub total_requests: u64,
    /// Total errors
    pub total_errors: u64,
    /// Average latency across all requests (ms)
    pub avg_latency_ms: f64,
    /// Requests per minute (current rate)
    pub requests_per_minute: f64,
    /// Error rate (percentage)
    pub error_rate: f64,
    /// Most used queries (top 20)
    pub top_queries: Vec<QueryStats>,
    /// Slowest queries (top 20)
    pub slowest_queries: Vec<QueryStats>,
    /// Most used fields (top 50)
    pub top_fields: Vec<FieldStats>,
    /// Error patterns
    pub error_patterns: Vec<ErrorStats>,
    /// Requests by operation type
    pub requests_by_type: HashMap<String, u64>,
    /// Cache hit rate (if caching enabled)
    pub cache_hit_rate: Option<f64>,
    /// Uptime in seconds
    pub uptime_seconds: u64,
}

/// Internal state for tracking analytics
struct AnalyticsState {
    config: AnalyticsConfig,
    queries: HashMap<String, QueryStats>,
    fields: HashMap<String, FieldStats>,
    errors: HashMap<String, ErrorStats>,
    total_requests: u64,
    total_errors: u64,
    total_latency_ms: u64,
    start_time: Instant,
    requests_in_window: Vec<u64>, // Timestamps for rate calculation
    cache_hits: u64,
    cache_misses: u64,
}

impl AnalyticsState {
    fn new(config: AnalyticsConfig) -> Self {
        Self {
            config,
            queries: HashMap::new(),
            fields: HashMap::new(),
            errors: HashMap::new(),
            total_requests: 0,
            total_errors: 0,
            total_latency_ms: 0,
            start_time: Instant::now(),
            requests_in_window: Vec::new(),
            cache_hits: 0,
            cache_misses: 0,
        }
    }
}

/// Query Analytics Engine
///
/// Thread-safe analytics collector for GraphQL operations.
pub struct QueryAnalytics {
    state: RwLock<AnalyticsState>,
    config: AnalyticsConfig,
}

impl QueryAnalytics {
    /// Create a new analytics engine
    pub fn new(config: AnalyticsConfig) -> Self {
        Self {
            state: RwLock::new(AnalyticsState::new(config.clone())),
            config,
        }
    }

    /// Record a query execution
    pub fn record_query(
        &self,
        query: &str,
        operation_name: Option<&str>,
        operation_type: &str,
        duration: Duration,
        had_error: bool,
        error_details: Option<(&str, &str)>, // (code, message)
    ) {
        let duration_ms = duration.as_millis() as u64;
        let query_hash = self.hash_query(query);
        
        let mut state = self.state.write();
        
        // Update totals
        state.total_requests += 1;
        state.total_latency_ms += duration_ms;
        if had_error {
            state.total_errors += 1;
        }
        
        // Track request timestamp for rate calculation
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        state.requests_in_window.push(now);
        
        // Prune old timestamps (keep last minute)
        let cutoff = now.saturating_sub(60);
        state.requests_in_window.retain(|&t| t > cutoff);
        
        // Update query stats
        let query_text = if self.config.track_query_text {
            Some(Self::truncate_query(query, 500))
        } else {
            None
        };
        
        let query_stats = state.queries.entry(query_hash.clone()).or_insert_with(|| {
            QueryStats::new(
                query_hash.clone(),
                operation_name.map(String::from),
                operation_type.to_string(),
                query_text,
            )
        });
        query_stats.record_execution(duration_ms, had_error);
        
        // Track error if present
        if let Some((code, message)) = error_details {
            let error_key = code.to_string();
            let error_stats = state.errors.entry(error_key.clone()).or_insert_with(|| {
                ErrorStats::new(code.to_string(), message.to_string())
            });
            error_stats.occurrence_count += 1;
            error_stats.last_occurrence = now;
            if !error_stats.affected_queries.contains(&query_hash) 
                && error_stats.affected_queries.len() < 10 
            {
                error_stats.affected_queries.push(query_hash.clone());
            }
        }
        
        // Prune if over limits
        if state.queries.len() > self.config.max_queries {
            self.prune_queries(&mut state);
        }
        if state.errors.len() > self.config.max_errors {
            self.prune_errors(&mut state);
        }
    }

    /// Record field usage
    pub fn record_field(&self, parent_type: &str, field_name: &str, duration: Option<Duration>) {
        let key = format!("{}.{}", parent_type, field_name);
        let duration_ms = duration.map(|d| d.as_millis() as f64).unwrap_or(0.0);
        
        let mut state = self.state.write();
        let field_stats = state.fields.entry(key.clone()).or_insert_with(|| {
            FieldStats::new(field_name.to_string(), parent_type.to_string())
        });
        
        field_stats.request_count += 1;
        if duration_ms > 0.0 {
            // Running average
            field_stats.avg_time_ms = 
                (field_stats.avg_time_ms * (field_stats.request_count - 1) as f64 + duration_ms)
                / field_stats.request_count as f64;
        }
        field_stats.last_requested = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        
        // Prune if over limit
        if state.fields.len() > self.config.max_fields {
            self.prune_fields(&mut state);
        }
    }

    /// Record cache hit/miss
    pub fn record_cache_access(&self, hit: bool) {
        let mut state = self.state.write();
        if hit {
            state.cache_hits += 1;
        } else {
            state.cache_misses += 1;
        }
    }

    /// Get current analytics snapshot
    pub fn get_snapshot(&self) -> AnalyticsSnapshot {
        let state = self.state.read();
        
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        
        // Calculate requests per minute
        let requests_per_minute = state.requests_in_window.len() as f64;
        
        // Calculate error rate
        let error_rate = if state.total_requests > 0 {
            (state.total_errors as f64 / state.total_requests as f64) * 100.0
        } else {
            0.0
        };
        
        // Calculate average latency
        let avg_latency_ms = if state.total_requests > 0 {
            state.total_latency_ms as f64 / state.total_requests as f64
        } else {
            0.0
        };
        
        // Calculate cache hit rate
        let cache_hit_rate = {
            let total_cache = state.cache_hits + state.cache_misses;
            if total_cache > 0 {
                Some((state.cache_hits as f64 / total_cache as f64) * 100.0)
            } else {
                None
            }
        };
        
        // Get top queries by count
        let mut top_queries: Vec<_> = state.queries.values().cloned().collect();
        top_queries.sort_by(|a, b| b.execution_count.cmp(&a.execution_count));
        top_queries.truncate(20);
        
        // Get slowest queries by average time
        let mut slowest_queries: Vec<_> = state.queries.values().cloned().collect();
        slowest_queries.sort_by(|a, b| {
            b.avg_time_ms.partial_cmp(&a.avg_time_ms).unwrap_or(std::cmp::Ordering::Equal)
        });
        slowest_queries.truncate(20);
        
        // Get top fields
        let mut top_fields: Vec<_> = state.fields.values().cloned().collect();
        top_fields.sort_by(|a, b| b.request_count.cmp(&a.request_count));
        top_fields.truncate(50);
        
        // Get error patterns
        let mut error_patterns: Vec<_> = state.errors.values().cloned().collect();
        error_patterns.sort_by(|a, b| b.occurrence_count.cmp(&a.occurrence_count));
        
        // Requests by type
        let mut requests_by_type: HashMap<String, u64> = HashMap::new();
        for query in state.queries.values() {
            *requests_by_type.entry(query.operation_type.clone()).or_insert(0) += query.execution_count;
        }
        
        AnalyticsSnapshot {
            timestamp: now,
            total_requests: state.total_requests,
            total_errors: state.total_errors,
            avg_latency_ms,
            requests_per_minute,
            error_rate,
            top_queries,
            slowest_queries,
            top_fields,
            error_patterns,
            requests_by_type,
            cache_hit_rate,
            uptime_seconds: state.start_time.elapsed().as_secs(),
        }
    }

    /// Reset all analytics data
    pub fn reset(&self) {
        let mut state = self.state.write();
        *state = AnalyticsState::new(self.config.clone());
    }

    /// Get configuration
    pub fn config(&self) -> &AnalyticsConfig {
        &self.config
    }

    /// Create a hash of the query for tracking
    ///
    /// # Security
    ///
    /// Uses SHA-256 for collision resistance. DefaultHasher is not suitable
    /// because it's easy to craft collisions that could affect analytics accuracy.
    fn hash_query(&self, query: &str) -> String {
        let mut hasher = Sha256::new();
        hasher.update(query.as_bytes());
        hex::encode(hasher.finalize())[..16].to_string()  // First 16 chars for reasonable length
    }

    fn truncate_query(query: &str, max_len: usize) -> String {
        if query.len() <= max_len {
            query.to_string()
        } else {
            format!("{}...", &query[..max_len.saturating_sub(3)])
        }
    }

    fn prune_queries(&self, state: &mut AnalyticsState) {
        // Remove least recently used queries
        let cutoff = state.queries.len() - self.config.max_queries + 100;
        let mut entries: Vec<_> = state.queries.iter()
            .map(|(k, v)| (k.clone(), v.last_execution))
            .collect();
        entries.sort_by_key(|(_, t)| *t);
        
        for (key, _) in entries.into_iter().take(cutoff) {
            state.queries.remove(&key);
        }
    }

    fn prune_fields(&self, state: &mut AnalyticsState) {
        let cutoff = state.fields.len() - self.config.max_fields + 50;
        let mut entries: Vec<_> = state.fields.iter()
            .map(|(k, v)| (k.clone(), v.last_requested))
            .collect();
        entries.sort_by_key(|(_, t)| *t);
        
        for (key, _) in entries.into_iter().take(cutoff) {
            state.fields.remove(&key);
        }
    }

    fn prune_errors(&self, state: &mut AnalyticsState) {
        let cutoff = state.errors.len() - self.config.max_errors + 20;
        let mut entries: Vec<_> = state.errors.iter()
            .map(|(k, v)| (k.clone(), v.last_occurrence))
            .collect();
        entries.sort_by_key(|(_, t)| *t);
        
        for (key, _) in entries.into_iter().take(cutoff) {
            state.errors.remove(&key);
        }
    }
}

/// Shared analytics instance
pub type SharedQueryAnalytics = Arc<QueryAnalytics>;

/// Create a shared analytics instance
pub fn create_analytics(config: AnalyticsConfig) -> SharedQueryAnalytics {
    Arc::new(QueryAnalytics::new(config))
}

/// Analytics guard for timing query execution
pub struct AnalyticsGuard {
    analytics: SharedQueryAnalytics,
    query: String,
    operation_name: Option<String>,
    operation_type: String,
    start: Instant,
    had_error: bool,
    error_details: Option<(String, String)>,
}

impl AnalyticsGuard {
    /// Create a new analytics guard
    pub fn new(
        analytics: SharedQueryAnalytics,
        query: String,
        operation_name: Option<String>,
        operation_type: String,
    ) -> Self {
        Self {
            analytics,
            query,
            operation_name,
            operation_type,
            start: Instant::now(),
            had_error: false,
            error_details: None,
        }
    }

    /// Mark this execution as having an error
    pub fn set_error(&mut self, code: String, message: String) {
        self.had_error = true;
        self.error_details = Some((code, message));
    }
}

impl Drop for AnalyticsGuard {
    fn drop(&mut self) {
        let duration = self.start.elapsed();
        let error_details = self.error_details.as_ref()
            .map(|(c, m)| (c.as_str(), m.as_str()));
        
        self.analytics.record_query(
            &self.query,
            self.operation_name.as_deref(),
            &self.operation_type,
            duration,
            self.had_error,
            error_details,
        );
    }
}

/// HTML for the analytics dashboard
pub fn analytics_dashboard_html() -> &'static str {
    include_str!("analytics_dashboard.html")
}

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

    #[test]
    fn test_analytics_config_default() {
        let config = AnalyticsConfig::default();
        assert_eq!(config.max_queries, 1000);
        assert!(config.track_query_text);
    }

    #[test]
    fn test_record_query() {
        let analytics = QueryAnalytics::new(AnalyticsConfig::default());
        
        analytics.record_query(
            "query { user { id } }",
            Some("GetUser"),
            "query",
            Duration::from_millis(50),
            false,
            None,
        );
        
        let snapshot = analytics.get_snapshot();
        assert_eq!(snapshot.total_requests, 1);
        assert_eq!(snapshot.total_errors, 0);
        assert!(!snapshot.top_queries.is_empty());
    }

    #[test]
    fn test_record_error() {
        let analytics = QueryAnalytics::new(AnalyticsConfig::default());
        
        analytics.record_query(
            "query { invalid }",
            None,
            "query",
            Duration::from_millis(10),
            true,
            Some(("GRAPHQL_VALIDATION_ERROR", "Unknown field 'invalid'")),
        );
        
        let snapshot = analytics.get_snapshot();
        assert_eq!(snapshot.total_errors, 1);
        assert!(!snapshot.error_patterns.is_empty());
    }

    #[test]
    fn test_record_field() {
        let analytics = QueryAnalytics::new(AnalyticsConfig::default());
        
        analytics.record_field("User", "email", Some(Duration::from_millis(5)));
        analytics.record_field("User", "email", Some(Duration::from_millis(10)));
        
        let snapshot = analytics.get_snapshot();
        assert!(!snapshot.top_fields.is_empty());
        assert_eq!(snapshot.top_fields[0].request_count, 2);
    }

    #[test]
    fn test_cache_tracking() {
        let analytics = QueryAnalytics::new(AnalyticsConfig::default());
        
        analytics.record_cache_access(true);
        analytics.record_cache_access(true);
        analytics.record_cache_access(false);
        
        let snapshot = analytics.get_snapshot();
        let hit_rate = snapshot.cache_hit_rate.unwrap();
        assert!((hit_rate - 66.67).abs() < 1.0);
    }

    #[test]
    fn test_concurrent_access() {
        let analytics = Arc::new(QueryAnalytics::new(AnalyticsConfig::default()));
        let mut handles = vec![];
        
        for i in 0..10 {
            let analytics = Arc::clone(&analytics);
            handles.push(thread::spawn(move || {
                for j in 0..100 {
                    analytics.record_query(
                        &format!("query {{ field_{} }}", j),
                        None,
                        "query",
                        Duration::from_millis(i as u64 + j as u64),
                        false,
                        None,
                    );
                }
            }));
        }
        
        for handle in handles {
            handle.join().unwrap();
        }
        
        let snapshot = analytics.get_snapshot();
        assert_eq!(snapshot.total_requests, 1000);
    }
}
