opensearch-api 0.1.0

High-performance REST API gateway for OpenSearch with security, observability and multi-tenant support
use anyhow::{Result, anyhow};
use chrono::{DateTime, Duration, Utc};
use mac_address::get_mac_address;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::Path;
use std::process::Command;

const LICENSE_FILE: &str = ".opensearch-api-license";
const TRIAL_DAYS: i64 = 7;

#[derive(Debug, Serialize, Deserialize)]
pub struct License {
    pub key: String,
    pub fingerprint: String,
    pub ip: String,
    pub product: String,
    pub issued_at: DateTime<Utc>,
    pub expires_at: Option<DateTime<Utc>>,
    pub is_trial: bool,
    pub validated_at: DateTime<Utc>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SystemInfo {
    pub fingerprint: String,
    pub ip: String,
    pub product: String,
    pub hostname: String,
    pub mac_address: String,
    pub cpu_info: String,
}

impl SystemInfo {
    pub fn collect() -> Result<Self> {
        let hostname = hostname::get()?.to_string_lossy().to_string();

        // Get MAC address
        let mac_address = get_mac_address()?
            .map(|mac| mac.to_string())
            .unwrap_or_else(|| "unknown".to_string());

        // Get CPU info (Linux specific)
        let cpu_info = if cfg!(target_os = "linux") {
            fs::read_to_string("/proc/cpuinfo")
                .ok()
                .and_then(|content| {
                    content
                        .lines()
                        .find(|line| line.starts_with("model name"))
                        .map(|line| line.split(':').nth(1).unwrap_or("").trim().to_string())
                })
                .unwrap_or_else(|| "unknown".to_string())
        } else {
            "unknown".to_string()
        };

        // Get IP address
        let ip = get_local_ip().unwrap_or_else(|| "127.0.0.1".to_string());

        // Generate fingerprint
        let fingerprint = Self::generate_fingerprint(&hostname, &mac_address, &cpu_info);

        Ok(Self {
            fingerprint,
            ip,
            product: "opensearch-api".to_string(),
            hostname,
            mac_address,
            cpu_info,
        })
    }

    fn generate_fingerprint(hostname: &str, mac: &str, cpu: &str) -> String {
        let mut hasher = Sha256::new();
        hasher.update(hostname.as_bytes());
        hasher.update(mac.as_bytes());
        hasher.update(cpu.as_bytes());

        let result = hasher.finalize();
        format!("{:x}", result)
    }
}

fn get_local_ip() -> Option<String> {
    // Try to get local IP
    let output = Command::new("hostname").arg("-I").output().ok()?;

    if output.status.success() {
        let ips = String::from_utf8_lossy(&output.stdout);
        ips.split_whitespace().next().map(|ip| ip.to_string())
    } else {
        None
    }
}

pub struct LicenseManager {
    license_path: String,
}

impl LicenseManager {
    pub fn new() -> Self {
        let license_path = LICENSE_FILE.to_string();

        Self { license_path }
    }

    pub fn check_license(&self) -> Result<License> {
        // Check if license file exists
        if !Path::new(&self.license_path).exists() {
            // Check for trial period
            return self.check_trial();
        }

        // Load and validate license
        let license_data = fs::read_to_string(&self.license_path)?;
        let license: License = serde_json::from_str(&license_data)?;

        // Validate fingerprint
        let system_info = SystemInfo::collect()?;
        if license.fingerprint != system_info.fingerprint {
            return Err(anyhow!("License is not valid for this system"));
        }

        // Check expiration
        if let Some(expires_at) = license.expires_at {
            if Utc::now() > expires_at {
                return Err(anyhow!("License has expired"));
            }
        }

        Ok(license)
    }

    fn check_trial(&self) -> Result<License> {
        let trial_file = format!("{}.trial", self.license_path);

        if !Path::new(&trial_file).exists() {
            // Create trial license
            let system_info = SystemInfo::collect()?;
            let trial = License {
                key: "TRIAL".to_string(),
                fingerprint: system_info.fingerprint.clone(),
                ip: system_info.ip.clone(),
                product: system_info.product.clone(),
                issued_at: Utc::now(),
                expires_at: Some(Utc::now() + Duration::days(TRIAL_DAYS)),
                is_trial: true,
                validated_at: Utc::now(),
            };

            // Save trial info
            let trial_data = serde_json::to_string_pretty(&trial)?;
            fs::write(&trial_file, trial_data)?;

            println!("🔔 Trial license created (valid for {} days)", TRIAL_DAYS);
            println!("   To get a full license, run: opensearch-api --license");

            return Ok(trial);
        }

        // Load existing trial
        let trial_data = fs::read_to_string(&trial_file)?;
        let trial: License = serde_json::from_str(&trial_data)?;

        // Check if trial is still valid
        if let Some(expires_at) = trial.expires_at {
            if Utc::now() > expires_at {
                return Err(anyhow!(
                    "Trial period has expired. Please obtain a license by running: opensearch-api --license"
                ));
            }

            let days_left = (expires_at - Utc::now()).num_days();
            if days_left <= 2 {
                println!(
                    "⚠️  Trial expires in {} days. Run 'opensearch-api --license' to get a full license",
                    days_left
                );
            }
        }

        Ok(trial)
    }

    pub fn save_license(&self, license_key: &str) -> Result<()> {
        let system_info = SystemInfo::collect()?;

        // Validate license with server (in production, this would call a license server)
        // For now, we'll create a local license
        let license = License {
            key: license_key.to_string(),
            fingerprint: system_info.fingerprint,
            ip: system_info.ip,
            product: system_info.product,
            issued_at: Utc::now(),
            expires_at: None, // Production license doesn't expire
            is_trial: false,
            validated_at: Utc::now(),
        };

        let license_data = serde_json::to_string_pretty(&license)?;
        fs::write(&self.license_path, license_data)?;

        // Remove trial file if it exists
        let trial_file = format!("{}.trial", self.license_path);
        if Path::new(&trial_file).exists() {
            fs::remove_file(&trial_file).ok();
        }

        println!("✅ License saved successfully");
        Ok(())
    }

    pub fn show_license_request() -> Result<()> {
        let system_info = SystemInfo::collect()?;

        println!("\n{}", "=".repeat(70));
        println!("LICENSE REQUEST INFORMATION");
        println!("{}", "=".repeat(70));
        println!("Please provide this information to obtain a license:\n");
        println!("Product:     {}", system_info.product);
        println!("Hostname:    {}", system_info.hostname);
        println!("IP Address:  {}", system_info.ip);
        println!("Fingerprint: {}", system_info.fingerprint);
        println!("\nSystem Details:");
        println!("  MAC Address: {}", system_info.mac_address);
        println!("  CPU Info:    {}", system_info.cpu_info);
        println!("{}", "=".repeat(70));
        println!("\nTo activate your license:");
        println!("1. Send the above information to your license provider");
        println!("2. Once you receive your license key, run:");
        println!("   opensearch-api --activate-license YOUR_LICENSE_KEY");
        println!("{}", "=".repeat(70));

        Ok(())
    }
}