//! Utility functions for the core lib template

use crate::*;

/// Configuration builder for application setup
pub struct AppConfigBuilder {
    /// Site configuration
    pub site_config: Option<SiteConfig>,
    /// Framework configuration
    pub framework_config: Option<FrameworkConfig>,
}

impl AppConfigBuilder {
    /// Create new configuration builder
    pub fn new() -> Self {
        Self {
            site_config: None,
            framework_config: None,
        }
    }

    /// Set site configuration
    pub fn site_config(mut self, config: SiteConfig) -> Self {
        self.site_config = Some(config);
        self
    }

    /// Set framework configuration
    pub fn framework_config(mut self, config: FrameworkConfig) -> Self {
        self.framework_config = Some(config);
        self
    }

    /// Build configuration with defaults
    pub fn build(self) -> Result<(SiteConfig, FrameworkConfig), AppError> {
        let site_config = self.site_config
            .ok_or_else(|| AppError::Config("Site configuration is required".to_string()))?;

        let framework_config = self.framework_config
            .unwrap_or_else(FrameworkConfig::default);

        Ok((site_config, framework_config))
    }
}

impl Default for AppConfigBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Path utilities
pub mod path {
    /// Normalize a path by removing double slashes and ensuring it starts with /
    pub fn normalize(path: &str) -> String {
        if path.is_empty() {
            return "/".to_string();
        }

        let mut normalized = path
            .split('/')
            .filter(|s| !s.is_empty())
            .collect::<Vec<_>>()
            .join("/");

        if normalized.is_empty() {
            "/".to_string()
        } else {
            format!("/{}", normalized)
        }
    }

    /// Join path segments
    pub fn join(segments: &[&str]) -> String {
        let joined = segments
            .iter()
            .filter(|s| !s.is_empty())
            .map(|s| s.trim_matches('/'))
            .filter(|s| !s.is_empty())
            .collect::<Vec<_>>()
            .join("/");

        if joined.is_empty() {
            "/".to_string()
        } else {
            format!("/{}", joined)
        }
    }

    /// Extract file extension from path
    pub fn extension(path: &str) -> Option<String> {
        let clean_path = path.split('?').next().unwrap_or(path);
        clean_path.split('.').last()
            .filter(|ext| !ext.is_empty() && ext != &clean_path)
            .map(|ext| ext.to_lowercase())
    }
}

/// String utilities
pub mod string {
    /// Convert string to kebab-case
    pub fn to_kebab_case(input: &str) -> String {
        input
            .chars()
            .map(|c| if c.is_uppercase() {
                format!("-{}", c.to_lowercase())
            } else {
                c.to_string()
            })
            .collect::<String>()
            .trim_start_matches('-')
            .replace(' ', "-")
            .replace('_', "-")
            .to_lowercase()
    }

    /// Convert string to snake_case
    pub fn to_snake_case(input: &str) -> String {
        input
            .chars()
            .map(|c| if c.is_uppercase() {
                format!("_{}", c.to_lowercase())
            } else {
                c.to_string()
            })
            .collect::<String>()
            .trim_start_matches('_')
            .replace(' ', "_")
            .replace('-', "_")
            .to_lowercase()
    }

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

/// Time utilities (requires "time" feature)
#[cfg(feature = "time")]
pub mod time {
    use chrono::{DateTime, Utc};

    /// Get current timestamp as ISO 8601 string
    pub fn now_iso() -> String {
        Utc::now().to_rfc3339()
    }

    /// Parse ISO 8601 timestamp
    pub fn parse_iso(timestamp: &str) -> Result<DateTime<Utc>, chrono::ParseError> {
        DateTime::parse_from_rfc3339(timestamp)
            .map(|dt| dt.with_timezone(&Utc))
    }

    /// Format timestamp for display
    pub fn format_display(timestamp: &str) -> Result<String, chrono::ParseError> {
        let dt = parse_iso(timestamp)?;
        Ok(dt.format("%Y-%m-%d %H:%M UTC").to_string())
    }
}

/// ID utilities (requires "ids" feature)
#[cfg(feature = "ids")]
pub mod ids {
    use uuid::Uuid;

    /// Generate new UUID v4
    pub fn generate() -> String {
        Uuid::new_v4().to_string()
    }

    /// Generate short ID (8 characters)
    pub fn generate_short() -> String {
        Uuid::new_v4().simple().to_string()[..8].to_string()
    }

    /// Validate UUID string
    pub fn is_valid_uuid(id: &str) -> bool {
        Uuid::parse_str(id).is_ok()
    }
}

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

    #[test]
    fn test_path_normalize() {
        assert_eq!(path::normalize(""), "/");
        assert_eq!(path::normalize("/"), "/");
        assert_eq!(path::normalize("//test//path//"), "/test/path");
        assert_eq!(path::normalize("test/path"), "/test/path");
    }

    #[test]
    fn test_path_join() {
        assert_eq!(path::join(&[]), "/");
        assert_eq!(path::join(&["test", "path"]), "/test/path");
        assert_eq!(path::join(&["/test/", "/path/"]), "/test/path");
        assert_eq!(path::join(&["", "test", "", "path", ""]), "/test/path");
    }

    #[test]
    fn test_path_extension() {
        assert_eq!(path::extension("file.txt"), Some("txt".to_string()));
        assert_eq!(path::extension("file.HTML"), Some("html".to_string()));
        assert_eq!(path::extension("file"), None);
        assert_eq!(path::extension("file.txt?query=1"), Some("txt".to_string()));
        assert_eq!(path::extension(""), None);
    }

    #[test]
    fn test_string_to_kebab_case() {
        assert_eq!(string::to_kebab_case("CamelCase"), "camel-case");
        assert_eq!(string::to_kebab_case("snake_case"), "snake-case");
        assert_eq!(string::to_kebab_case("Space Case"), "space-case");
        assert_eq!(string::to_kebab_case("already-kebab"), "already-kebab");
    }

    #[test]
    fn test_string_to_snake_case() {
        assert_eq!(string::to_snake_case("CamelCase"), "camel_case");
        assert_eq!(string::to_snake_case("kebab-case"), "kebab_case");
        assert_eq!(string::to_snake_case("Space Case"), "space_case");
        assert_eq!(string::to_snake_case("already_snake"), "already_snake");
    }

    #[test]
    fn test_string_truncate() {
        assert_eq!(string::truncate("short", 10), "short");
        assert_eq!(string::truncate("this is a long string", 10), "this is...");
        assert_eq!(string::truncate("exact", 5), "exact");
    }

    #[test]
    fn test_app_config_builder() {
        let site_config = SiteConfig {
            name: "Test Site".to_string(),
            description: "Test".to_string(),
            base_url: "https://test.com".to_string(),
            default_language: "en".to_string(),
            languages: vec![],
            default_theme: "default".to_string(),
            themes: vec![],
            contact_email: None,
            metadata: HashMap::new(),
        };

        let result = AppConfigBuilder::new()
            .site_config(site_config.clone())
            .build();

        assert!(result.is_ok());
        let (built_site_config, framework_config) = result.unwrap();
        assert_eq!(built_site_config.name, "Test Site");
        assert_eq!(framework_config.environment, Environment::Development);
    }

    #[test]
    fn test_app_config_builder_error() {
        let result = AppConfigBuilder::new().build();
        assert!(result.is_err());
    }

    #[cfg(feature = "ids")]
    #[test]
    fn test_id_generation() {
        let id = ids::generate();
        assert!(ids::is_valid_uuid(&id));

        let short_id = ids::generate_short();
        assert_eq!(short_id.len(), 8);
    }

    #[cfg(feature = "time")]
    #[test]
    fn test_time_utilities() {
        let now = time::now_iso();
        assert!(time::parse_iso(&now).is_ok());

        let formatted = time::format_display(&now);
        assert!(formatted.is_ok());
        assert!(formatted.unwrap().contains("UTC"));
    }
}
