//! Template Engine
//!
//! Renders Tera templates for Nickel metaprogramming (.ncl.j2 files).
//!
//! Supports:
//! - Loading and rendering .ncl.j2 templates
//! - Template loops, conditionals, and filters
//! - Passing form results as context to templates

use crate::error::ErrorWrapper;
use crate::Result;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;

#[cfg(feature = "templates")]
use tera::{Context, Tera};

use super::field_mapper::FieldMapper;

/// Template engine for rendering .ncl.j2 templates
pub struct TemplateEngine {
    #[cfg(feature = "templates")]
    tera: Tera,
}

impl TemplateEngine {
    /// Create a new template engine
    pub fn new() -> Self {
        #[cfg(feature = "templates")]
        {
            let tera = Tera::default();
            TemplateEngine { tera }
        }

        #[cfg(not(feature = "templates"))]
        {
            TemplateEngine {}
        }
    }

    /// Render a template file with given values
    ///
    /// # Arguments
    ///
    /// * `template_path` - Path to the template file
    /// * `values` - HashMap of values to pass to template
    /// * `mapper` - Optional FieldMapper for dual-key context (aliases + flat_names)
    pub fn render_file(
        &mut self,
        template_path: &Path,
        values: &HashMap<String, Value>,
        mapper: Option<&FieldMapper>,
    ) -> Result<String> {
        #[cfg(feature = "templates")]
        {
            // Read template file
            let template_content = fs::read_to_string(template_path)
                .map_err(|e| ErrorWrapper::new(format!("Failed to read template file: {}", e)))?;

            // Add template to engine
            let template_name = template_path
                .file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("template");

            self.tera
                .add_raw_template(template_name, &template_content)
                .map_err(|e| ErrorWrapper::new(format!("Failed to add template: {}", e)))?;

            // Build context from values with dual-key support if mapper provided
            let mut context = Context::new();
            self.build_dual_key_context(&mut context, values, mapper)?;

            // Render template
            self.tera
                .render(template_name, &context)
                .map_err(|e| ErrorWrapper::new(format!("Failed to render template: {}", e)))
        }

        #[cfg(not(feature = "templates"))]
        {
            Err(ErrorWrapper::new(
                "Template feature not enabled. Enable with --features templates".to_string(),
            ))
        }
    }

    /// Render a template string with given values
    ///
    /// # Arguments
    ///
    /// * `template` - Template content as string
    /// * `values` - HashMap of values to pass to template
    /// * `mapper` - Optional FieldMapper for dual-key context (aliases + flat_names)
    pub fn render_str(
        &mut self,
        template: &str,
        values: &HashMap<String, Value>,
        mapper: Option<&FieldMapper>,
    ) -> Result<String> {
        #[cfg(feature = "templates")]
        {
            // Add template to engine
            self.tera
                .add_raw_template("inline", template)
                .map_err(|e| ErrorWrapper::new(format!("Failed to add template: {}", e)))?;

            // Build context from values with dual-key support if mapper provided
            let mut context = Context::new();
            self.build_dual_key_context(&mut context, values, mapper)?;

            // Render template
            self.tera
                .render("inline", &context)
                .map_err(|e| ErrorWrapper::new(format!("Failed to render template: {}", e)))
        }

        #[cfg(not(feature = "templates"))]
        {
            Err(ErrorWrapper::new(
                "Template feature not enabled. Enable with --features templates".to_string(),
            ))
        }
    }

    /// Build context with dual-key support (aliases + flat_names)
    #[cfg(feature = "templates")]
    fn build_dual_key_context(
        &self,
        context: &mut Context,
        values: &HashMap<String, Value>,
        mapper: Option<&FieldMapper>,
    ) -> Result<()> {
        if let Some(m) = mapper {
            // Dual-key mode: insert under both alias and flat_name if different
            for (key, value) in values {
                context.insert(key, value);

                // Also try to find field by key and insert under alternative name
                if let Some(field) = m.get(key) {
                    if let Some(alias) = &field.alias {
                        if alias != key {
                            // Insert under alias if it's different from the key
                            context.insert(alias, value);
                        }
                    } else {
                        // No alias, try to insert under flat_name if different
                        if &field.flat_name != key {
                            context.insert(&field.flat_name, value);
                        }
                    }
                }
            }
        } else {
            // Legacy mode: direct insertion only
            for (key, value) in values {
                context.insert(key, value);
            }
        }

        Ok(())
    }
}

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

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

    #[cfg(feature = "templates")]
    #[test]
    fn test_template_engine_new() {
        let _engine = TemplateEngine::new();
        // Just verify it can be created
    }

    #[cfg(feature = "templates")]
    #[test]
    fn test_render_simple_template() {
        let mut engine = TemplateEngine::new();
        let mut values = HashMap::new();
        values.insert("name".to_string(), json!("Alice"));
        values.insert("age".to_string(), json!(30));

        let template = r#"
name : String = "{{ name }}"
age : Number = {{ age }}
"#;

        let result = engine.render_str(template, &values, None);
        assert!(result.is_ok());
        let output = result.unwrap();
        assert!(output.contains("Alice"));
        assert!(output.contains("30"));
    }

    #[cfg(feature = "templates")]
    #[test]
    fn test_render_with_loop() {
        let mut engine = TemplateEngine::new();
        let mut values = HashMap::new();
        let tags = vec![json!("rust"), json!("nickel"), json!("forms")];
        values.insert("tags".to_string(), Value::Array(tags));

        let template = r#"tags = [
{% for tag in tags %}
  "{{ tag }}",
{% endfor %}
]"#;

        let result = engine.render_str(template, &values, None);
        assert!(result.is_ok());
        let output = result.unwrap();
        assert!(output.contains("rust"));
        assert!(output.contains("nickel"));
    }

    #[cfg(feature = "templates")]
    #[test]
    fn test_render_with_conditional() {
        let mut engine = TemplateEngine::new();
        let mut values = HashMap::new();
        values.insert("enabled".to_string(), json!(true));
        values.insert("feature".to_string(), json!("monitoring"));

        let template = r#"{% if enabled %}
{{ feature }}_enabled : Bool = true
{% endif %}"#;

        let result = engine.render_str(template, &values, None);
        assert!(result.is_ok());
        let output = result.unwrap();
        assert!(output.contains("monitoring_enabled"));
    }
}
