//! Utility functions for Attune services
//!
//! This module provides common utility functions used across all services.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::time::Duration;

/// Pagination parameters
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pagination {
    /// Page number (0-indexed)
    #[serde(default)]
    pub page: u32,

    /// Number of items per page
    #[serde(default = "default_page_size")]
    pub page_size: u32,
}

fn default_page_size() -> u32 {
    50
}

impl Default for Pagination {
    fn default() -> Self {
        Self {
            page: 0,
            page_size: default_page_size(),
        }
    }
}

impl Pagination {
    /// Calculate the offset for SQL queries
    pub fn offset(&self) -> u32 {
        self.page * self.page_size
    }

    /// Get the limit for SQL queries
    pub fn limit(&self) -> u32 {
        self.page_size
    }

    /// Validate pagination parameters
    pub fn validate(&self) -> crate::Result<()> {
        if self.page_size == 0 {
            return Err(crate::Error::validation("Page size must be greater than 0"));
        }

        if self.page_size > 1000 {
            return Err(crate::Error::validation("Page size must not exceed 1000"));
        }

        Ok(())
    }
}

/// Paginated response wrapper
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginatedResponse<T> {
    /// The data items
    pub data: Vec<T>,

    /// Pagination metadata
    pub pagination: PaginationMetadata,
}

/// Pagination metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaginationMetadata {
    /// Current page number
    pub page: u32,

    /// Number of items per page
    pub page_size: u32,

    /// Total number of items
    pub total: u64,

    /// Total number of pages
    pub total_pages: u32,

    /// Whether there is a next page
    pub has_next: bool,

    /// Whether there is a previous page
    pub has_prev: bool,
}

impl PaginationMetadata {
    /// Create pagination metadata
    pub fn new(pagination: &Pagination, total: u64) -> Self {
        let total_pages = ((total as f64) / (pagination.page_size as f64)).ceil() as u32;
        let has_next = pagination.page + 1 < total_pages;
        let has_prev = pagination.page > 0;

        Self {
            page: pagination.page,
            page_size: pagination.page_size,
            total,
            total_pages,
            has_next,
            has_prev,
        }
    }
}

/// Convert Duration to human-readable string
pub fn format_duration(duration: Duration) -> String {
    let secs = duration.as_secs();
    if secs < 60 {
        format!("{}s", secs)
    } else if secs < 3600 {
        format!("{}m {}s", secs / 60, secs % 60)
    } else if secs < 86400 {
        format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
    } else {
        format!("{}d {}h", secs / 86400, (secs % 86400) / 3600)
    }
}

/// Format timestamp relative to now (e.g., "2 hours ago")
pub fn format_relative_time(timestamp: DateTime<Utc>) -> String {
    let now = Utc::now();
    let duration = now.signed_duration_since(timestamp);

    if duration.num_seconds() < 0 {
        return "in the future".to_string();
    }

    let secs = duration.num_seconds();
    if secs < 60 {
        format!("{} seconds ago", secs)
    } else if secs < 3600 {
        let mins = secs / 60;
        if mins == 1 {
            "1 minute ago".to_string()
        } else {
            format!("{} minutes ago", mins)
        }
    } else if secs < 86400 {
        let hours = secs / 3600;
        if hours == 1 {
            "1 hour ago".to_string()
        } else {
            format!("{} hours ago", hours)
        }
    } else {
        let days = secs / 86400;
        if days == 1 {
            "1 day ago".to_string()
        } else {
            format!("{} days ago", days)
        }
    }
}

/// Sanitize a reference string (lowercase, replace spaces with hyphens)
pub fn sanitize_ref(input: &str) -> String {
    input
        .to_lowercase()
        .trim()
        .chars()
        .map(|c| if c.is_whitespace() { '-' } else { c })
        .collect()
}

/// Generate a unique identifier
pub fn generate_id() -> String {
    uuid::Uuid::new_v4().to_string()
}

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

/// Redact sensitive information from strings
pub fn redact_sensitive(s: &str) -> String {
    if s.is_empty() {
        return String::new();
    }

    let visible_chars = s.len().min(4);
    let redacted_chars = s.len().saturating_sub(visible_chars);

    if redacted_chars == 0 {
        return "*".repeat(s.len());
    }

    format!(
        "{}{}",
        "*".repeat(redacted_chars),
        &s[s.len() - visible_chars..]
    )
}

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

    #[test]
    fn test_pagination_offset() {
        let page = Pagination {
            page: 0,
            page_size: 10,
        };
        assert_eq!(page.offset(), 0);
        assert_eq!(page.limit(), 10);

        let page = Pagination {
            page: 2,
            page_size: 25,
        };
        assert_eq!(page.offset(), 50);
        assert_eq!(page.limit(), 25);
    }

    #[test]
    fn test_pagination_validation() {
        let page = Pagination {
            page: 0,
            page_size: 0,
        };
        assert!(page.validate().is_err());

        let page = Pagination {
            page: 0,
            page_size: 2000,
        };
        assert!(page.validate().is_err());

        let page = Pagination {
            page: 0,
            page_size: 50,
        };
        assert!(page.validate().is_ok());
    }

    #[test]
    fn test_pagination_metadata() {
        let pagination = Pagination {
            page: 1,
            page_size: 10,
        };
        let metadata = PaginationMetadata::new(&pagination, 45);

        assert_eq!(metadata.page, 1);
        assert_eq!(metadata.page_size, 10);
        assert_eq!(metadata.total, 45);
        assert_eq!(metadata.total_pages, 5);
        assert!(metadata.has_next);
        assert!(metadata.has_prev);
    }

    #[test]
    fn test_format_duration() {
        assert_eq!(format_duration(Duration::from_secs(30)), "30s");
        assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
        assert_eq!(format_duration(Duration::from_secs(3661)), "1h 1m");
        assert_eq!(format_duration(Duration::from_secs(86400)), "1d 0h");
    }

    #[test]
    fn test_sanitize_ref() {
        assert_eq!(sanitize_ref("My Action"), "my-action");
        assert_eq!(sanitize_ref("  Test  "), "test");
        assert_eq!(sanitize_ref("UPPERCASE"), "uppercase");
    }

    #[test]
    fn test_generate_id() {
        let id1 = generate_id();
        let id2 = generate_id();
        assert_ne!(id1, id2);
        assert_eq!(id1.len(), 36); // UUID v4 format
    }

    #[test]
    fn test_truncate() {
        assert_eq!(truncate("short", 10), "short");
        assert_eq!(truncate("this is a long string", 10), "this is...");
        assert_eq!(truncate("abc", 3), "abc");
        assert_eq!(truncate("abcd", 3), "...");
    }

    #[test]
    fn test_redact_sensitive() {
        assert_eq!(redact_sensitive(""), "");
        assert_eq!(redact_sensitive("abc"), "***");
        assert_eq!(redact_sensitive("password123"), "*******d123");
        assert_eq!(redact_sensitive("secret"), "**cret");
    }
}
