// Copyright (c) 2024 Jason Witty <jasonpwitty+socktop@proton.me>.
// All rights reserved.
//
// Umami analytics integration for tracking terminal events using pop-telemetry

use pop_telemetry::{record_cli_command, Telemetry};
use serde_json::json;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;

/// Umami analytics tracker
pub struct Analytics {
    telemetry: Arc<Mutex<Option<Telemetry>>>,
    enabled: bool,
}

impl Analytics {
    /// Create a new Analytics instance with custom Umami endpoint
    ///
    /// # Arguments
    /// * `website_id` - The Umami website ID
    /// * `endpoint` - The Umami instance endpoint (e.g., "http://unami.wittyoneoff.com/api/send")
    /// * `config_path` - Path to the telemetry config file (for opt-out checks)
    pub fn new(website_id: String, endpoint: String, config_path: PathBuf) -> Self {
        let telemetry = Telemetry::init_with_website_id(endpoint, website_id, &config_path);

        Self {
            telemetry: Arc::new(Mutex::new(Some(telemetry))),
            enabled: true,
        }
    }

    /// Create a disabled Analytics instance (no-op)
    pub fn disabled() -> Self {
        Self {
            telemetry: Arc::new(Mutex::new(None)),
            enabled: false,
        }
    }

    /// Track a terminal command event
    ///
    /// # Arguments
    /// * `command` - The command that was typed (will be sanitized)
    /// * `_user_agent` - Optional user agent string (not used with pop-telemetry)
    pub async fn track_command(&self, command: &str, _user_agent: Option<String>) {
        if !self.enabled {
            return;
        }

        let telemetry = self.telemetry.lock().await;

        if let Some(t) = telemetry.as_ref() {
            // Sanitize the command for analytics
            let sanitized_command = sanitize_command(command);

            // Track as an event using pop-telemetry
            let data = json!({
                "command": sanitized_command,
                "type": "terminal_command"
            });

            match record_cli_command(t.clone(), "command_typed", data).await {
                Ok(_) => log::debug!("Tracked command event: {}", sanitized_command),
                Err(e) => log::warn!("Failed to track command event: {:?}", e),
            }
        }
    }

    /// Track a page view
    ///
    /// # Arguments
    /// * `path` - The page path
    /// * `_user_agent` - Optional user agent string (not used with pop-telemetry)
    pub async fn track_pageview(&self, path: &str, _user_agent: Option<String>) {
        if !self.enabled {
            return;
        }

        let telemetry = self.telemetry.lock().await;

        if let Some(t) = telemetry.as_ref() {
            let data = json!({
                "path": path,
                "type": "pageview"
            });

            match record_cli_command(t.clone(), "pageview", data).await {
                Ok(_) => log::debug!("Tracked pageview: {}", path),
                Err(e) => log::warn!("Failed to track pageview: {:?}", e),
            }
        }
    }

    /// Track a terminal session start
    pub async fn track_session_start(&self, _user_agent: Option<String>) {
        if !self.enabled {
            return;
        }

        let telemetry = self.telemetry.lock().await;

        if let Some(t) = telemetry.as_ref() {
            let data = json!({
                "event": "session_start",
                "type": "terminal_session"
            });

            match record_cli_command(t.clone(), "session_start", data).await {
                Ok(_) => log::debug!("Tracked session start"),
                Err(e) => log::warn!("Failed to track session start: {:?}", e),
            }
        }
    }

    /// Track a terminal session end
    pub async fn track_session_end(&self, _user_agent: Option<String>) {
        if !self.enabled {
            return;
        }

        let telemetry = self.telemetry.lock().await;

        if let Some(t) = telemetry.as_ref() {
            let data = json!({
                "event": "session_end",
                "type": "terminal_session"
            });

            match record_cli_command(t.clone(), "session_end", data).await {
                Ok(_) => log::debug!("Tracked session end"),
                Err(e) => log::warn!("Failed to track session end: {:?}", e),
            }
        }
    }
}

impl Clone for Analytics {
    fn clone(&self) -> Self {
        Self {
            telemetry: Arc::clone(&self.telemetry),
            enabled: self.enabled,
        }
    }
}

/// Sanitize a command for analytics tracking
///
/// This removes potentially sensitive information like:
/// - Passwords in commands (e.g., mysql -p password)
/// - URLs with credentials
/// - SSH keys
/// - File paths (replaced with generic placeholders)
///
/// Returns a sanitized version of the command safe for analytics
fn sanitize_command(command: &str) -> String {
    let trimmed = command.trim();

    // If empty, return as-is
    if trimmed.is_empty() {
        return "empty".to_string();
    }

    // Split into words
    let words: Vec<&str> = trimmed.split_whitespace().collect();

    if words.is_empty() {
        return "empty".to_string();
    }

    // Get the base command (first word)
    let base_cmd = words[0];

    // For sensitive commands, only track the command name
    let sensitive_commands = [
        "ssh", "scp", "sftp", "rsync", "mysql", "psql", "mongo", "curl", "wget", "git", "docker",
        "kubectl", "aws", "gcloud", "sudo", "su", "passwd", "chpasswd", "openssl", "gpg",
    ];

    if sensitive_commands.iter().any(|&cmd| base_cmd.contains(cmd)) {
        return format!("{}_REDACTED", base_cmd);
    }

    // For common safe commands, keep the command and count of args
    let safe_commands = [
        "ls", "cd", "pwd", "cat", "less", "more", "head", "tail", "echo", "grep", "find", "which",
        "whoami", "date", "cal", "clear", "exit", "history", "man", "help", "top", "htop", "ps",
        "kill", "df", "du", "free", "uptime", "uname", "socktop",
    ];

    if safe_commands.contains(&base_cmd) {
        if words.len() > 1 {
            return format!("{}_with_{}_args", base_cmd, words.len() - 1);
        } else {
            return base_cmd.to_string();
        }
    }

    // For other commands, just return the base command
    base_cmd.to_string()
}

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

    #[test]
    fn test_sanitize_empty() {
        assert_eq!(sanitize_command(""), "empty");
        assert_eq!(sanitize_command("   "), "empty");
    }

    #[test]
    fn test_sanitize_safe_commands() {
        assert_eq!(sanitize_command("ls"), "ls");
        assert_eq!(sanitize_command("ls -la"), "ls_with_1_args");
        assert_eq!(sanitize_command("cd /tmp"), "cd_with_1_args");
        assert_eq!(sanitize_command("pwd"), "pwd");
        assert_eq!(sanitize_command("socktop"), "socktop");
        assert_eq!(sanitize_command("socktop -P local"), "socktop_with_2_args");
    }

    #[test]
    fn test_sanitize_sensitive_commands() {
        assert_eq!(sanitize_command("ssh user@host"), "ssh_REDACTED");
        assert_eq!(
            sanitize_command("mysql -u root -p password"),
            "mysql_REDACTED"
        );
        assert_eq!(
            sanitize_command("curl https://api.com/secret"),
            "curl_REDACTED"
        );
        assert_eq!(sanitize_command("sudo rm -rf /"), "sudo_REDACTED");
    }

    #[test]
    fn test_sanitize_unknown_commands() {
        assert_eq!(sanitize_command("customcmd arg1 arg2"), "customcmd");
        assert_eq!(sanitize_command("./script.sh"), "./script.sh");
    }

    #[test]
    fn test_analytics_disabled() {
        let analytics = Analytics::disabled();
        assert!(!analytics.enabled);
    }

    #[tokio::test]
    async fn test_track_command_disabled() {
        let analytics = Analytics::disabled();
        // Should not panic or error when disabled
        analytics.track_command("ls -la", None).await;
    }
}
