//! Email template engine using Handlebars
//!
//! This module provides template rendering capabilities for emails using Handlebars
//! templating engine. It supports both text and HTML templates with custom helpers.

#![allow(dead_code)]

use crate::email::{EmailError, EmailResult};
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;

/// Email template engine
#[derive(Debug)]
pub struct EmailTemplateEngine {
    handlebars: Handlebars<'static>,
    template_dir: String,
    default_language: String,
}

#[allow(dead_code)]
impl EmailTemplateEngine {
    /// Create a new template engine
    pub fn new(template_dir: &str) -> EmailResult<Self> {
        Self::new_with_language(template_dir, "en")
    }

    /// Create a new template engine with default language
    pub fn new_with_language(template_dir: &str, default_language: &str) -> EmailResult<Self> {
        let mut handlebars = Handlebars::new();

        // Register custom helpers
        handlebars.register_helper("date_format", Box::new(date_format_helper));
        handlebars.register_helper("capitalize", Box::new(capitalize_helper));
        handlebars.register_helper("truncate", Box::new(truncate_helper));
        handlebars.register_helper("default", Box::new(default_helper));
        handlebars.register_helper("url_encode", Box::new(url_encode_helper));

        // Set strict mode to catch template errors
        handlebars.set_strict_mode(true);

        let mut engine = Self {
            handlebars,
            template_dir: template_dir.to_string(),
            default_language: default_language.to_string(),
        };

        // Load all templates from directory
        engine.load_templates()?;

        Ok(engine)
    }

    /// Load templates from the template directory
    pub fn load_templates(&mut self) -> EmailResult<()> {
        let template_dir_clone = self.template_dir.clone();
        let template_path = Path::new(&template_dir_clone);

        if !template_path.exists() {
            return Err(EmailError::template(&format!(
                "Template directory does not exist: {}",
                template_dir_clone
            )));
        }

        // Scan for language directories
        let entries = std::fs::read_dir(template_path).map_err(|e| {
            EmailError::template(&format!("Failed to read template directory: {}", e))
        })?;

        let mut found_templates = false;

        // Collect all language directories first to avoid borrowing issues
        let language_dirs: Vec<(std::path::PathBuf, String)> = Vec::new();

        let entries_vec: Vec<_> = entries.collect::<Result<Vec<_>, _>>().map_err(|e| {
            EmailError::template(&format!("Failed to read directory entries: {}", e))
        })?;

        for entry in entries_vec {
            let path = entry.path();
            if path.is_dir() {
                if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
                    // Check if this is a language directory (e.g., en_, es_, fr_)
                    if dir_name.ends_with('_') {
                        let lang_code = dir_name[..dir_name.len() - 1].to_string();
                        let path_clone = path.clone();
                        self.load_language_templates(&path_clone, &lang_code)?;
                        found_templates = true;
                    }
                }
            }
        }

        // If no language-specific templates found, try loading from direct subdirectories
        if !found_templates {
            let default_lang = self.default_language.clone();

            // Load HTML templates
            let html_dir = template_path.join("html");
            if html_dir.exists() {
                self.load_templates_from_dir(&html_dir, &default_lang, "html")?;
                found_templates = true;
            }

            // Load text templates
            let text_dir = template_path.join("text");
            if text_dir.exists() {
                self.load_templates_from_dir(&text_dir, &default_lang, "text")?;
                found_templates = true;
            }
        }

        if !found_templates {
            tracing::warn!(
                "No email templates found in directory: {}",
                self.template_dir
            );
        }

        Ok(())
    }

    /// Load templates for a specific language
    fn load_language_templates(&mut self, lang_dir: &Path, lang_code: &str) -> EmailResult<()> {
        // Load HTML templates
        let html_dir = lang_dir.join("html");
        if html_dir.exists() {
            self.load_templates_from_dir(&html_dir, lang_code, "html")?;
        }

        // Load text templates
        let text_dir = lang_dir.join("text");
        if text_dir.exists() {
            self.load_templates_from_dir(&text_dir, lang_code, "text")?;
        }

        Ok(())
    }

    /// Load templates from a specific directory
    fn load_templates_from_dir(
        &mut self,
        dir: &Path,
        lang_code: &str,
        template_type: &str,
    ) -> EmailResult<()> {
        let entries = std::fs::read_dir(dir).map_err(|e| {
            EmailError::template(&format!("Failed to read template directory: {}", e))
        })?;

        for entry in entries {
            let entry = entry.map_err(|e| {
                EmailError::template(&format!("Failed to read directory entry: {}", e))
            })?;

            let path = entry.path();
            if path.is_file() {
                if let Some(extension) = path.extension() {
                    if extension == "hbs" || extension == "handlebars" {
                        let template_name = path
                            .file_stem()
                            .ok_or_else(|| EmailError::template("Invalid template filename"))?
                            .to_string_lossy();

                        let full_template_name =
                            format!("{}_{}_{}", lang_code, template_name, template_type);

                        self.handlebars
                            .register_template_file(&full_template_name, &path)
                            .map_err(|e| {
                                EmailError::template(&format!(
                                    "Failed to register template {}: {}",
                                    full_template_name, e
                                ))
                            })?;

                        tracing::debug!("Loaded email template: {}", full_template_name);
                    }
                }
            }
        }

        Ok(())
    }

    /// Render a template with data and language
    #[allow(dead_code)]
    pub fn render(
        &self,
        template_name: &str,
        data: &HashMap<String, Value>,
    ) -> EmailResult<String> {
        self.render_with_language(template_name, data, &self.default_language)
    }

    /// Render a template with data and specific language
    pub fn render_with_language(
        &self,
        template_name: &str,
        data: &HashMap<String, Value>,
        language: &str,
    ) -> EmailResult<String> {
        let full_template_name = format!("{}_{}", language, template_name);

        // Try the requested language first
        if self.handlebars.has_template(&full_template_name) {
            return self
                .handlebars
                .render(&full_template_name, data)
                .map_err(|e| {
                    EmailError::template(&format!(
                        "Failed to render template {}: {}",
                        full_template_name, e
                    ))
                });
        }

        // Fall back to default language
        let default_template_name = format!("{}_{}", self.default_language, template_name);
        if self.handlebars.has_template(&default_template_name) {
            tracing::warn!(
                "Template {} not found for language {}, falling back to {}",
                template_name,
                language,
                self.default_language
            );
            return self
                .handlebars
                .render(&default_template_name, data)
                .map_err(|e| {
                    EmailError::template(&format!(
                        "Failed to render fallback template {}: {}",
                        default_template_name, e
                    ))
                });
        }

        // Last resort: try template without language prefix (legacy support)
        if self.handlebars.has_template(template_name) {
            tracing::warn!(
                "Using legacy template without language prefix: {}",
                template_name
            );
            return self.handlebars.render(template_name, data).map_err(|e| {
                EmailError::template(&format!(
                    "Failed to render legacy template {}: {}",
                    template_name, e
                ))
            });
        }

        Err(EmailError::template(&format!(
            "Template not found: {} (tried languages: {}, {})",
            template_name, language, self.default_language
        )))
    }

    /// Check if a template exists for any language
    #[allow(dead_code)]
    pub fn has_template(&self, template_name: &str) -> bool {
        self.has_template_for_language(template_name, &self.default_language)
    }

    /// Check if a template exists for a specific language
    pub fn has_template_for_language(&self, template_name: &str, language: &str) -> bool {
        let full_template_name = format!("{}_{}", language, template_name);
        self.handlebars.has_template(&full_template_name)
            || self
                .handlebars
                .has_template(&format!("{}_{}", self.default_language, template_name))
            || self.handlebars.has_template(template_name) // legacy support
    }

    /// Get all available template names (without language prefixes)
    pub fn get_template_names(&self) -> Vec<String> {
        let mut templates = std::collections::HashSet::new();

        for template_name in self.handlebars.get_templates().keys() {
            // Remove language prefix if present
            if let Some(underscore_pos) = template_name.find('_') {
                let without_lang = &template_name[underscore_pos + 1..];
                templates.insert(without_lang.to_string());
            } else {
                templates.insert(template_name.clone());
            }
        }

        templates.into_iter().collect()
    }

    /// Get available languages for templates
    pub fn get_available_languages(&self) -> Vec<String> {
        let mut languages = std::collections::HashSet::new();

        for template_name in self.handlebars.get_templates().keys() {
            if let Some(underscore_pos) = template_name.find('_') {
                let lang = &template_name[..underscore_pos];
                languages.insert(lang.to_string());
            }
        }

        languages.into_iter().collect()
    }

    /// Register a new template from string
    pub fn register_template(&mut self, name: &str, template: &str) -> EmailResult<()> {
        self.handlebars
            .register_template_string(name, template)
            .map_err(|e| {
                EmailError::template(&format!("Failed to register template {}: {}", name, e))
            })
    }

    /// Unregister a template
    pub fn unregister_template(&mut self, name: &str) {
        self.handlebars.unregister_template(name);
    }

    /// Clear all templates
    pub fn clear_templates(&mut self) {
        self.handlebars.clear_templates();
    }

    /// Reload templates from directory
    pub fn reload_templates(&mut self) -> EmailResult<()> {
        self.clear_templates();
        self.load_templates()
    }
}

/// Helper function to format dates
fn date_format_helper(
    h: &Helper,
    _: &Handlebars,
    _: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    let param = h.param(0);
    let format = h
        .param(1)
        .and_then(|v| v.value().as_str())
        .unwrap_or("%Y-%m-%d %H:%M:%S UTC");

    if let Some(param) = param {
        if let Some(date_str) = param.value().as_str() {
            if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(date_str) {
                let formatted = datetime.format(format);
                out.write(&formatted.to_string())?;
            } else {
                out.write(date_str)?;
            }
        }
    }

    Ok(())
}

/// Helper function to capitalize text
fn capitalize_helper(
    h: &Helper,
    _: &Handlebars,
    _: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    if let Some(param) = h.param(0) {
        if let Some(text) = param.value().as_str() {
            let capitalized = text.chars().next().map_or(String::new(), |first| {
                first.to_uppercase().collect::<String>() + &text[1..]
            });
            out.write(&capitalized)?;
        }
    }

    Ok(())
}

/// Helper function to truncate text
fn truncate_helper(
    h: &Helper,
    _: &Handlebars,
    _: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    if let Some(param) = h.param(0) {
        if let Some(text) = param.value().as_str() {
            let length = h.param(1).and_then(|v| v.value().as_u64()).unwrap_or(100) as usize;

            let truncated = if text.len() > length {
                format!("{}...", &text[..length])
            } else {
                text.to_string()
            };

            out.write(&truncated)?;
        }
    }

    Ok(())
}

/// Helper function to provide default values
fn default_helper(
    h: &Helper,
    _: &Handlebars,
    _: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    if let Some(param) = h.param(0) {
        if !param.value().is_null() {
            if let Some(text) = param.value().as_str() {
                if !text.is_empty() {
                    out.write(text)?;
                    return Ok(());
                }
            }
        }
    }

    if let Some(default) = h.param(1) {
        if let Some(text) = default.value().as_str() {
            out.write(text)?;
        }
    }

    Ok(())
}

/// Helper function to URL encode text
fn url_encode_helper(
    h: &Helper,
    _: &Handlebars,
    _: &Context,
    _: &mut RenderContext,
    out: &mut dyn Output,
) -> HelperResult {
    if let Some(param) = h.param(0) {
        if let Some(text) = param.value().as_str() {
            let encoded = urlencoding::encode(text);
            out.write(&encoded)?;
        }
    }

    Ok(())
}

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

    #[test]
    fn test_template_engine_creation() {
        let temp_dir = TempDir::new().unwrap();
        let engine = EmailTemplateEngine::new(temp_dir.path().to_str().unwrap());
        assert!(engine.is_ok());
    }

    #[test]
    fn test_language_support() {
        let temp_dir = TempDir::new().unwrap();
        let engine =
            EmailTemplateEngine::new_with_language(temp_dir.path().to_str().unwrap(), "en")
                .unwrap();

        // Test language detection
        let languages = engine.get_available_languages();
        assert!(languages.is_empty()); // No templates loaded in empty dir
    }

    #[test]
    fn test_template_rendering_with_fallback() {
        let temp_dir = TempDir::new().unwrap();
        let mut engine =
            EmailTemplateEngine::new_with_language(temp_dir.path().to_str().unwrap(), "en")
                .unwrap();

        // Register a template manually for testing
        engine
            .register_template("en_test_template", "Hello {{name}}!")
            .unwrap();

        let mut data = HashMap::new();
        data.insert("name".to_string(), Value::String("John".to_string()));

        // Test rendering with language
        let result = engine.render_with_language("test_template", &data, "en");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "Hello John!");

        // Test fallback to default language
        let result = engine.render_with_language("test_template", &data, "es");
        assert!(result.is_ok()); // Should fall back to "en"
    }
}
