//! Common utilities for Veza Rust services
//! 
//! This module provides utility functions and helpers shared across
//! all Veza Rust services.

use std::collections::HashMap;
use uuid::Uuid;
use chrono::{DateTime, Utc};
use sha2::{Sha256, Digest};
use hmac::{Hmac, Mac};
use base64::{Engine as _, engine::general_purpose};

use crate::{VezaError, VezaResult};

/// Generate a new UUID v4
pub fn generate_uuid() -> Uuid {
    Uuid::new_v4()
}

/// Generate a random string of specified length
pub fn generate_random_string(length: usize) -> String {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::{Hash, Hasher};
    use std::time::{SystemTime, UNIX_EPOCH};
    
    let mut hasher = DefaultHasher::new();
    SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos().hash(&mut hasher);
    let hash = hasher.finish();
    
    format!("{:x}", hash)[..length.min(16)].to_string()
}

/// Generate a secure random token
pub fn generate_token(length: usize) -> VezaResult<String> {
    use rand::Rng;
    
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let mut rng = rand::thread_rng();
    
    let token: String = (0..length)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    
    Ok(token)
}

/// Hash a string using SHA-256
pub fn hash_sha256(input: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(input.as_bytes());
    format!("{:x}", hasher.finalize())
}

/// Generate HMAC signature
pub fn generate_hmac_signature(data: &str, secret: &str) -> VezaResult<String> {
    let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
        .map_err(|_| VezaError::Internal("Invalid HMAC secret".to_string()))?;
    
    mac.update(data.as_bytes());
    let result = mac.finalize();
    let signature = general_purpose::STANDARD.encode(result.into_bytes());
    
    Ok(signature)
}

/// Verify HMAC signature
pub fn verify_hmac_signature(data: &str, signature: &str, secret: &str) -> VezaResult<bool> {
    let expected = generate_hmac_signature(data, secret)?;
    Ok(signature == expected)
}

/// Encode data to base64
pub fn encode_base64(data: &[u8]) -> String {
    general_purpose::STANDARD.encode(data)
}

/// Decode base64 data
pub fn decode_base64(data: &str) -> VezaResult<Vec<u8>> {
    general_purpose::STANDARD.decode(data)
        .map_err(|e| VezaError::Serialization(format!("Base64 decode error: {}", e)))
}

/// Format duration in human-readable format
pub fn format_duration(seconds: u32) -> String {
    let hours = seconds / 3600;
    let minutes = (seconds % 3600) / 60;
    let secs = seconds % 60;
    
    if hours > 0 {
        format!("{}:{:02}:{:02}", hours, minutes, secs)
    } else {
        format!("{}:{:02}", minutes, secs)
    }
}

/// Format file size in human-readable format
pub fn format_file_size(bytes: u64) -> String {
    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
    const THRESHOLD: u64 = 1024;
    
    if bytes == 0 {
        return "0 B".to_string();
    }
    
    let mut size = bytes as f64;
    let mut unit_index = 0;
    
    while size >= THRESHOLD as f64 && unit_index < UNITS.len() - 1 {
        size /= THRESHOLD as f64;
        unit_index += 1;
    }
    
    if unit_index == 0 {
        format!("{} {}", bytes, UNITS[unit_index])
    } else {
        format!("{:.1} {}", size, UNITS[unit_index])
    }
}

/// Format number with thousands separator
pub fn format_number(n: u64) -> String {
    let s = n.to_string();
    let mut result = String::new();
    let mut count = 0;
    
    for c in s.chars().rev() {
        if count > 0 && count % 3 == 0 {
            result.push(',');
        }
        result.push(c);
        count += 1;
    }
    
    result.chars().rev().collect()
}

/// Validate email format
pub fn validate_email(email: &str) -> bool {
    use regex::Regex;
    
    let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
        .expect("Invalid email regex");
    
    email_regex.is_match(email)
}

/// Validate username format
pub fn validate_username(username: &str) -> VezaResult<bool> {
    if username.len() < 3 || username.len() > 30 {
        return Ok(false);
    }
    
    use regex::Regex;
    let username_regex = Regex::new(r"^[a-zA-Z0-9_-]+$")
        .expect("Invalid username regex");
    
    Ok(username_regex.is_match(username))
}

/// Validate password strength
pub fn validate_password_strength(password: &str) -> VezaResult<Vec<String>> {
    let mut errors = Vec::new();
    
    if password.len() < 8 {
        errors.push("Password must be at least 8 characters long".to_string());
    }
    
    if password.len() > 128 {
        errors.push("Password must be less than 128 characters".to_string());
    }
    
    if !password.chars().any(|c| c.is_uppercase()) {
        errors.push("Password must contain at least one uppercase letter".to_string());
    }
    
    if !password.chars().any(|c| c.is_lowercase()) {
        errors.push("Password must contain at least one lowercase letter".to_string());
    }
    
    if !password.chars().any(|c| c.is_numeric()) {
        errors.push("Password must contain at least one digit".to_string());
    }
    
    if !password.chars().any(|c| c.is_ascii_punctuation()) {
        errors.push("Password must contain at least one special character".to_string());
    }
    
    Ok(errors)
}

/// Sanitize string input
pub fn sanitize_string(input: &str) -> String {
    input
        .chars()
        .filter(|c| !c.is_control() || *c == '\n' || *c == '\r' || *c == '\t')
        .collect::<String>()
        .trim()
        .to_string()
}

/// Truncate string to specified length
pub fn truncate_string(s: &str, max_length: usize) -> String {
    if s.len() <= max_length {
        s.to_string()
    } else {
        format!("{}...", &s[..max_length.saturating_sub(3)])
    }
}

/// Check if string is empty or whitespace only
pub fn is_empty_or_whitespace(s: &str) -> bool {
    s.trim().is_empty()
}

/// Convert string to slug
pub fn to_slug(input: &str) -> String {
    input
        .to_lowercase()
        .chars()
        .map(|c| match c {
            ' ' => '-',
            c if c.is_alphanumeric() => c,
            _ => '-',
        })
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<&str>>()
        .join("-")
}

/// Parse duration string (e.g., "1h30m", "45s")
pub fn parse_duration(duration_str: &str) -> VezaResult<u64> {
    let mut total_seconds = 0u64;
    let mut current_number = String::new();
    
    for c in duration_str.chars() {
        if c.is_numeric() {
            current_number.push(c);
        } else {
            if !current_number.is_empty() {
                let number: u64 = current_number.parse()
                    .map_err(|_| VezaError::Validation(format!("Invalid duration: {}", duration_str)))?;
                
                match c {
                    's' => total_seconds += number,
                    'm' => total_seconds += number * 60,
                    'h' => total_seconds += number * 3600,
                    'd' => total_seconds += number * 86400,
                    _ => return Err(VezaError::Validation(format!("Invalid duration unit: {}", c))),
                }
                
                current_number.clear();
            }
        }
    }
    
    // Handle case where duration ends with a number (assume seconds)
    if !current_number.is_empty() {
        let number: u64 = current_number.parse()
            .map_err(|_| VezaError::Validation(format!("Invalid duration: {}", duration_str)))?;
        total_seconds += number;
    }
    
    Ok(total_seconds)
}

/// Get current timestamp
pub fn now() -> DateTime<Utc> {
    Utc::now()
}

/// Get timestamp from Unix timestamp
pub fn from_timestamp(timestamp: i64) -> VezaResult<DateTime<Utc>> {
    DateTime::from_timestamp(timestamp, 0)
        .ok_or_else(|| VezaError::Validation(format!("Invalid timestamp: {}", timestamp)))
}

/// Calculate time difference in seconds
pub fn time_diff_seconds(start: DateTime<Utc>, end: DateTime<Utc>) -> i64 {
    (end - start).num_seconds()
}

/// Check if timestamp is expired
pub fn is_expired(timestamp: DateTime<Utc>, ttl_seconds: u64) -> bool {
    let now = Utc::now();
    (now - timestamp).num_seconds() > ttl_seconds as i64
}

/// Generate pagination cursor
pub fn generate_cursor(timestamp: DateTime<Utc>, id: Uuid) -> String {
    let data = format!("{}:{}", timestamp.timestamp(), id);
    encode_base64(data.as_bytes())
}

/// Parse pagination cursor
pub fn parse_cursor(cursor: &str) -> VezaResult<(DateTime<Utc>, Uuid)> {
    let data = decode_base64(cursor)?;
    let data_str = String::from_utf8(data)
        .map_err(|e| VezaError::Validation(format!("Invalid cursor format: {}", e)))?;
    
    let parts: Vec<&str> = data_str.split(':').collect();
    if parts.len() != 2 {
        return Err(VezaError::Validation("Invalid cursor format".to_string()));
    }
    
    let timestamp = parts[0].parse::<i64>()
        .map_err(|_| VezaError::Validation("Invalid timestamp in cursor".to_string()))?;
    
    let id = parts[1].parse::<Uuid>()
        .map_err(|_| VezaError::Validation("Invalid UUID in cursor".to_string()))?;
    
    let datetime = from_timestamp(timestamp)?;
    Ok((datetime, id))
}

/// Merge two HashMaps
pub fn merge_hashmaps<K, V>(mut map1: HashMap<K, V>, map2: HashMap<K, V>) -> HashMap<K, V>
where
    K: std::hash::Hash + Eq + Clone,
    V: Clone,
{
    for (k, v) in map2 {
        map1.insert(k, v);
    }
    map1
}

/// Remove duplicates from a vector
pub fn remove_duplicates<T: PartialEq + Clone>(vec: Vec<T>) -> Vec<T> {
    let mut result = Vec::new();
    for item in vec {
        if !result.contains(&item) {
            result.push(item);
        }
    }
    result
}

/// Chunk a vector into smaller vectors
pub fn chunk_vector<T>(vec: Vec<T>, chunk_size: usize) -> Vec<Vec<T>> {
    let mut result = Vec::new();
    let mut current_chunk = Vec::new();
    
    for item in vec {
        current_chunk.push(item);
        if current_chunk.len() == chunk_size {
            result.push(current_chunk);
            current_chunk = Vec::new();
        }
    }
    
    if !current_chunk.is_empty() {
        result.push(current_chunk);
    }
    
    result
}

/// Retry function with exponential backoff
pub async fn retry_with_backoff<F, T, E>(
    mut f: F,
    max_retries: u32,
    base_delay: u64,
) -> VezaResult<T>
where
    F: FnMut() -> Result<T, E>,
    E: std::fmt::Display,
{
    let mut last_error = None;
    
    for attempt in 0..=max_retries {
        match f() {
            Ok(result) => return Ok(result),
            Err(e) => {
                last_error = Some(e);
                if attempt < max_retries {
                    let delay = base_delay * 2_u64.pow(attempt);
                    tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await;
                }
            }
        }
    }
    
    Err(VezaError::Internal(format!(
        "Operation failed after {} retries: {}",
        max_retries,
        last_error.unwrap()
    )))
}

/// Timeout wrapper for async operations
pub async fn with_timeout<F, T>(
    future: F,
    timeout_duration: std::time::Duration,
) -> VezaResult<T>
where
    F: std::future::Future<Output = VezaResult<T>>,
{
    tokio::time::timeout(timeout_duration, future)
        .await
        .map_err(|_| VezaError::Timeout("Operation timed out".to_string()))?
}

/// Generate request ID
pub fn generate_request_id() -> String {
    format!("req_{}", generate_uuid())
}

/// Generate correlation ID
pub fn generate_correlation_id() -> String {
    format!("corr_{}", generate_uuid())
}

/// Extract IP address from headers
pub fn extract_ip_address(headers: &HashMap<String, String>) -> Option<String> {
    headers
        .get("x-forwarded-for")
        .or_else(|| headers.get("x-real-ip"))
        .or_else(|| headers.get("x-client-ip"))
        .map(|s| s.split(',').next().unwrap_or(s).trim().to_string())
}

/// Validate UUID format
pub fn is_valid_uuid(uuid_str: &str) -> bool {
    uuid_str.parse::<Uuid>().is_ok()
}

/// Convert bytes to hex string
pub fn bytes_to_hex(bytes: &[u8]) -> String {
    bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

/// Convert hex string to bytes
pub fn hex_to_bytes(hex: &str) -> VezaResult<Vec<u8>> {
    if hex.len() % 2 != 0 {
        return Err(VezaError::Validation("Hex string must have even length".to_string()));
    }
    
    let mut bytes = Vec::new();
    for chunk in hex.as_bytes().chunks(2) {
        let byte_str = std::str::from_utf8(chunk)
            .map_err(|_| VezaError::Validation("Invalid hex string".to_string()))?;
        let byte = u8::from_str_radix(byte_str, 16)
            .map_err(|_| VezaError::Validation("Invalid hex character".to_string()))?;
        bytes.push(byte);
    }
    
    Ok(bytes)
}
