//! Email template engine using Tera
//!
//! Supports both text and HTML templates with Tera's built-in filters
//! (`capitalize`, `truncate`, `default`, `urlencode`, etc.).
//!
//! Template naming convention: `{lang}_{stem}_{type}` where:
//! - `lang` is the language code (e.g. `en`, `es`)
//! - `stem` is the template filename without extension
//! - `type` is `html` or `text`
//!
//! Directory layout (either form is accepted):
//! ```text
//! template_dir/
//!   html/otp_code.html   → registered as `en_otp_code_html`
//!   text/otp_code.txt    → registered as `en_otp_code_text`
//! ```
//! or language-prefixed:
//! ```text
//! template_dir/
//!   en_/html/otp_code.html
//!   es_/html/otp_code.html
//! ```

#![allow(dead_code)]

use std::collections::HashMap;
use std::path::Path;

use serde_json::Value;
use tera::{Context, Tera};

use crate::email::{EmailError, EmailResult};

/// Email template engine backed by Tera.
pub struct EmailTemplateEngine {
    tera: Tera,
    template_dir: String,
    default_language: String,
    /// Raw template sources kept for rebuild on unregister/clear.
    raw_templates: HashMap<String, String>,
}

impl std::fmt::Debug for EmailTemplateEngine {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("EmailTemplateEngine")
            .field("template_dir", &self.template_dir)
            .field("default_language", &self.default_language)
            .field("template_count", &self.raw_templates.len())
            .finish()
    }
}

impl EmailTemplateEngine {
    pub fn new(template_dir: &str) -> EmailResult<Self> {
        Self::new_with_language(template_dir, "en")
    }

    /// Empty engine for disabled-email mode — no template dir required.
    pub fn new_empty() -> Self {
        Self {
            tera: Tera::default(),
            template_dir: String::new(),
            default_language: "en".to_string(),
            raw_templates: HashMap::new(),
        }
    }

    pub fn new_with_language(template_dir: &str, default_language: &str) -> EmailResult<Self> {
        let mut engine = Self {
            tera: Tera::default(),
            template_dir: template_dir.to_string(),
            default_language: default_language.to_string(),
            raw_templates: HashMap::new(),
        };
        engine.load_templates()?;
        Ok(engine)
    }

    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
            )));
        }

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

        let mut found_templates = false;

        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()) {
                    if dir_name.ends_with('_') {
                        let lang_code = dir_name[..dir_name.len() - 1].to_string();
                        self.load_language_templates(&path.clone(), &lang_code)?;
                        found_templates = true;
                    }
                }
            }
        }

        if !found_templates {
            let default_lang = self.default_language.clone();

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

            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 {
            let short = std::env::current_dir()
                .ok()
                .and_then(|cwd| {
                    template_path
                        .strip_prefix(&cwd)
                        .ok()
                        .map(|p| p.to_path_buf())
                })
                .unwrap_or_else(|| template_path.to_path_buf());
            tracing::warn!("No email templates found in directory: {}", short.display());
        }

        Ok(())
    }

    fn load_language_templates(&mut self, lang_dir: &Path, lang_code: &str) -> EmailResult<()> {
        let html_dir = lang_dir.join("html");
        if html_dir.exists() {
            self.load_templates_from_dir(&html_dir, lang_code, "html")?;
        }
        let text_dir = lang_dir.join("text");
        if text_dir.exists() {
            self.load_templates_from_dir(&text_dir, lang_code, "text")?;
        }
        Ok(())
    }

    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() {
                continue;
            }

            let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
            if !matches!(ext, "html" | "txt" | "tera") {
                continue;
            }

            let stem = path
                .file_stem()
                .ok_or_else(|| EmailError::template("Invalid template filename"))?
                .to_string_lossy();
            let name = format!("{}_{}_{}", lang_code, stem, template_type);

            let source = std::fs::read_to_string(&path).map_err(|e| {
                EmailError::template(&format!(
                    "Failed to read template file {}: {}",
                    path.display(),
                    e
                ))
            })?;

            self.raw_templates.insert(name.clone(), source.clone());
            self.tera.add_raw_template(&name, &source).map_err(|e| {
                EmailError::template(&format!("Failed to register template {}: {}", name, e))
            })?;

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

        Ok(())
    }

    pub fn render(
        &self,
        template_name: &str,
        data: &HashMap<String, Value>,
    ) -> EmailResult<String> {
        self.render_with_language(template_name, data, &self.default_language.clone())
    }

    pub fn render_with_language(
        &self,
        template_name: &str,
        data: &HashMap<String, Value>,
        language: &str,
    ) -> EmailResult<String> {
        let ctx = Self::build_context(data)?;

        let full = format!("{}_{}", language, template_name);
        if self.tera.get_template(&full).is_ok() {
            return self.tera.render(&full, &ctx).map_err(|e| {
                EmailError::template(&format!("Failed to render template {}: {}", full, e))
            });
        }

        let default_full = format!("{}_{}", self.default_language, template_name);
        if self.tera.get_template(&default_full).is_ok() {
            tracing::warn!(
                "Template {} not found for language {}, falling back to {}",
                template_name,
                language,
                self.default_language
            );
            return self.tera.render(&default_full, &ctx).map_err(|e| {
                EmailError::template(&format!(
                    "Failed to render fallback template {}: {}",
                    default_full, e
                ))
            });
        }

        if self.tera.get_template(template_name).is_ok() {
            tracing::warn!(
                "Using legacy template without language prefix: {}",
                template_name
            );
            return self.tera.render(template_name, &ctx).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
        )))
    }

    pub fn has_template(&self, template_name: &str) -> bool {
        self.has_template_for_language(template_name, &self.default_language.clone())
    }

    pub fn has_template_for_language(&self, template_name: &str, language: &str) -> bool {
        self.tera
            .get_template(&format!("{}_{}", language, template_name))
            .is_ok()
            || self
                .tera
                .get_template(&format!("{}_{}", self.default_language, template_name))
                .is_ok()
            || self.tera.get_template(template_name).is_ok()
    }

    pub fn get_template_names(&self) -> Vec<String> {
        let mut templates = std::collections::HashSet::new();
        for name in self.tera.get_template_names() {
            if let Some(pos) = name.find('_') {
                templates.insert(name[pos + 1..].to_string());
            } else {
                templates.insert(name.to_string());
            }
        }
        templates.into_iter().collect()
    }

    pub fn get_available_languages(&self) -> Vec<String> {
        let mut languages = std::collections::HashSet::new();
        for name in self.tera.get_template_names() {
            if let Some(pos) = name.find('_') {
                languages.insert(name[..pos].to_string());
            }
        }
        languages.into_iter().collect()
    }

    pub fn register_template(&mut self, name: &str, template: &str) -> EmailResult<()> {
        self.raw_templates
            .insert(name.to_string(), template.to_string());
        self.tera.add_raw_template(name, template).map_err(|e| {
            EmailError::template(&format!("Failed to register template {}: {}", name, e))
        })
    }

    pub fn unregister_template(&mut self, name: &str) {
        self.raw_templates.remove(name);
        self.rebuild_tera();
    }

    pub fn clear_templates(&mut self) {
        self.raw_templates.clear();
        self.tera = Tera::default();
    }

    pub fn reload_templates(&mut self) -> EmailResult<()> {
        self.raw_templates.clear();
        self.tera = Tera::default();
        self.load_templates()
    }

    fn rebuild_tera(&mut self) {
        let mut tera = Tera::default();
        for (name, source) in &self.raw_templates {
            let _ = tera.add_raw_template(name, source);
        }
        self.tera = tera;
    }

    fn build_context(data: &HashMap<String, Value>) -> EmailResult<Context> {
        let value = serde_json::to_value(data).map_err(|e| {
            EmailError::template(&format!("Failed to serialize template data: {}", e))
        })?;
        Context::from_value(value)
            .map_err(|e| EmailError::template(&format!("Failed to build template context: {}", e)))
    }
}

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

    use super::*;

    #[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();
        let languages = engine.get_available_languages();
        assert!(languages.is_empty());
    }

    #[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();

        engine
            .register_template("en_test_template", "Hello {{ name }}!")
            .unwrap();

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

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

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

    #[test]
    fn test_unregister_template() {
        let temp_dir = TempDir::new().unwrap();
        let mut engine = EmailTemplateEngine::new(temp_dir.path().to_str().unwrap()).unwrap();

        engine.register_template("test_tpl", "content").unwrap();
        assert!(
            engine.has_template_for_language("test_tpl", "en")
                || engine.tera.get_template("test_tpl").is_ok()
        );

        engine.unregister_template("test_tpl");
        assert!(!engine.tera.get_template("test_tpl").is_ok());
    }
}
