//! Tera template engine wrapper

#![allow(dead_code)]

use std::collections::HashMap;

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

use crate::template::{RenderedTemplate, Result, TemplateError, TemplatePageConfig};

/// Tera template engine wrapper
#[derive(Debug)]
pub struct TemplateEngine {
    /// Tera instance
    tera: Tera,
    /// Template directory path
    #[allow(dead_code)]
    template_dir: String,
    /// Cache enabled flag
    #[allow(dead_code)]
    cache_enabled: bool,
}

#[allow(dead_code)]
impl TemplateEngine {
    /// Create a new template engine instance
    pub fn new(template_dir: impl Into<String>) -> Result<Self> {
        let template_dir = template_dir.into();
        let template_pattern = format!("{}/**/*.html", template_dir);

        let tera = Tera::new(&template_pattern)
            .map_err(|e| TemplateError::ParseError(format!("Failed to initialize Tera: {}", e)))?;

        Ok(Self {
            tera,
            template_dir,
            cache_enabled: true,
        })
    }

    /// Create a new template engine with custom Tera instance
    pub fn with_tera(tera: Tera, template_dir: impl Into<String>) -> Self {
        Self {
            tera,
            template_dir: template_dir.into(),
            cache_enabled: true,
        }
    }

    /// Enable or disable template caching
    pub fn with_cache(mut self, enabled: bool) -> Self {
        self.cache_enabled = enabled;
        self
    }

    /// Add a custom function to the template engine
    pub fn add_function<F>(&mut self, name: &str, func: F)
    where
        F: tera::Function + 'static,
    {
        self.tera.register_function(name, func);
    }

    /// Add a custom filter to the template engine
    pub fn add_filter<F>(&mut self, name: &str, filter: F)
    where
        F: tera::Filter + 'static,
    {
        self.tera.register_filter(name, filter);
    }

    /// Render a template with the given configuration
    pub fn render_template(
        &self,
        config: &TemplatePageConfig,
        source_path: &str,
    ) -> Result<RenderedTemplate> {
        // Create Tera context from the values
        let mut context = Context::new();

        // Add all values from the config
        for (key, value) in &config.values {
            context.insert(key, value);
        }

        // Add metadata if available
        if let Some(metadata) = &config.metadata {
            context.insert("metadata", metadata);
        }

        // Add some built-in variables
        context.insert("template_name", &config.template_name);
        context.insert("source_path", source_path);

        // Render the template
        let content = self
            .tera
            .render(&self.get_template_name(&config.template_name), &context)
            .map_err(|e| TemplateError::RenderError(format!("Tera render error: {}", e)))?;

        Ok(RenderedTemplate {
            content,
            config: config.clone(),
            source_path: source_path.to_string(),
        })
    }

    /// Render a template with custom context
    pub fn render_with_context(&self, template_name: &str, context: &Context) -> Result<String> {
        let template_name = self.get_template_name(template_name);
        self.tera
            .render(&template_name, context)
            .map_err(|e| TemplateError::RenderError(format!("Tera render error: {}", e)))
    }

    /// Render a template string directly
    pub fn render_string(&mut self, template_string: &str, context: &Context) -> Result<String> {
        self.tera
            .render_str(template_string, context)
            .map_err(|e| TemplateError::RenderError(format!("Tera render error: {}", e)))
    }

    /// Check if a template exists
    pub fn template_exists(&self, template_name: &str) -> bool {
        let template_name = self.get_template_name(template_name);
        self.tera.get_template(&template_name).is_ok()
    }

    /// Get the full template name with extension
    fn get_template_name(&self, template_name: &str) -> String {
        if template_name.ends_with(".html") {
            template_name.to_string()
        } else {
            format!("{}.html", template_name)
        }
    }

    /// Reload templates from disk
    pub fn reload_templates(&mut self) -> Result<()> {
        let template_pattern = format!("{}/**/*.html", self.template_dir);
        self.tera = Tera::new(&template_pattern)
            .map_err(|e| TemplateError::ParseError(format!("Failed to reload templates: {}", e)))?;
        Ok(())
    }

    /// Add a template from string
    pub fn add_template(&mut self, name: &str, content: &str) -> Result<()> {
        self.tera
            .add_raw_template(name, content)
            .map_err(|e| TemplateError::ParseError(format!("Failed to add template: {}", e)))
    }

    /// Get template names
    pub fn get_template_names(&self) -> Vec<String> {
        self.tera
            .get_template_names()
            .map(|s| s.to_string())
            .collect()
    }

    /// Create a context from a HashMap
    pub fn create_context(values: &HashMap<String, Value>) -> Context {
        let mut context = Context::new();
        for (key, value) in values {
            context.insert(key, value);
        }
        context
    }

    /// Add default filters and functions
    pub fn add_default_filters(&mut self) {
        // Add a markdown filter if needed
        self.tera.register_filter("markdown", markdown_filter);

        // Add a date formatting filter
        self.tera.register_filter("date_format", date_format_filter);

        // Add a slug filter
        self.tera.register_filter("slug", slug_filter);

        // Add an excerpt filter
        self.tera.register_filter("excerpt", excerpt_filter);
    }
}

impl Default for TemplateEngine {
    fn default() -> Self {
        Self::new("templates").unwrap_or_else(|_| {
            // Fallback to empty Tera instance
            Self::with_tera(Tera::default(), "templates")
        })
    }
}

// Built-in filters

fn markdown_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
    let content = value
        .as_str()
        .ok_or_else(|| tera::Error::msg("Markdown filter can only be applied to strings"))?;

    // Simple markdown conversion (you might want to use a proper markdown parser)
    let html = content
        .replace("\n\n", "</p><p>")
        .replace("\n", "<br>")
        .replace("**", "</strong>")
        .replace("*", "</em>");

    Ok(Value::String(format!("<p>{}</p>", html)))
}

fn date_format_filter(value: &Value, args: &HashMap<String, Value>) -> tera::Result<Value> {
    let date_str = value
        .as_str()
        .ok_or_else(|| tera::Error::msg("Date format filter can only be applied to strings"))?;

    let format = args
        .get("format")
        .and_then(|v| v.as_str())
        .unwrap_or("%Y-%m-%d");

    // For now, just return the original string
    // In a real implementation, you'd parse and format the date
    Ok(Value::String(date_str.to_string()))
}

fn slug_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
    let text = value
        .as_str()
        .ok_or_else(|| tera::Error::msg("Slug filter can only be applied to strings"))?;

    let slug = text
        .to_lowercase()
        .replace(|c: char| !c.is_alphanumeric() && c != '-', "-")
        .trim_matches('-')
        .to_string();

    Ok(Value::String(slug))
}

fn excerpt_filter(value: &Value, args: &HashMap<String, Value>) -> tera::Result<Value> {
    let text = value
        .as_str()
        .ok_or_else(|| tera::Error::msg("Excerpt filter can only be applied to strings"))?;

    let length = args.get("length").and_then(|v| v.as_u64()).unwrap_or(150) as usize;

    let excerpt = if text.len() > length {
        let mut excerpt = text.chars().take(length).collect::<String>();
        if let Some(last_space) = excerpt.rfind(' ') {
            excerpt.truncate(last_space);
        }
        format!("{}...", excerpt)
    } else {
        text.to_string()
    };

    Ok(Value::String(excerpt))
}

#[cfg(test)]
mod tests {
    use std::fs;
    use std::path::Path;

    use tempfile::TempDir;

    use super::*;

    fn create_test_template(dir: &Path, name: &str, content: &str) -> std::io::Result<()> {
        let file_path = dir.join(name);
        fs::write(file_path, content)
    }

    #[test]
    fn test_template_engine_creation() {
        let temp_dir = TempDir::new().unwrap();
        create_test_template(temp_dir.path(), "test.html", "<h1>{{title}}</h1>").unwrap();

        let engine = TemplateEngine::new(temp_dir.path().to_str().unwrap());
        assert!(engine.is_ok());
    }

    #[test]
    fn test_template_rendering() {
        let temp_dir = TempDir::new().unwrap();
        create_test_template(
            temp_dir.path(),
            "test.html",
            "<h1>{{title}}</h1><p>{{content}}</p>",
        )
        .unwrap();

        let engine = TemplateEngine::new(temp_dir.path().to_str().unwrap()).unwrap();

        let mut values = HashMap::new();
        values.insert("title".to_string(), Value::String("Test Title".to_string()));
        values.insert(
            "content".to_string(),
            Value::String("Test content".to_string()),
        );

        let config = TemplatePageConfig {
            template_name: "test".to_string(),
            values,
            metadata: None,
        };

        let result = engine.render_template(&config, "test.tpl.toml").unwrap();
        assert!(result.content.contains("Test Title"));
        assert!(result.content.contains("Test content"));
    }

    #[test]
    fn test_slug_filter() {
        let input = Value::String("Hello World Test".to_string());
        let result = slug_filter(&input, &HashMap::new()).unwrap();
        assert_eq!(result.as_str().unwrap(), "hello-world-test");
    }

    #[test]
    fn test_excerpt_filter() {
        let input = Value::String("This is a very long text that should be truncated".to_string());
        let mut args = HashMap::new();
        args.insert("length".to_string(), Value::Number(20.into()));

        let result = excerpt_filter(&input, &args).unwrap();
        let excerpt = result.as_str().unwrap();
        assert!(excerpt.len() < input.as_str().unwrap().len());
        assert!(excerpt.ends_with("..."));
    }
}
