//! Template service for handling localized template rendering

#![allow(dead_code)]

use crate::template::{
    RenderedTemplate, Result, TemplateConfig, TemplateEngine, TemplateError, TemplateLoader,
    TemplatePageConfig,
};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tracing::{debug, info, warn};

/// Template service that manages template loading and rendering
#[derive(Debug)]
pub struct TemplateService {
    /// Template engine
    engine: Arc<RwLock<TemplateEngine>>,
    /// Template loader
    loader: Arc<RwLock<TemplateLoader>>,
    /// Template configuration
    config: TemplateConfig,
    /// Cache for rendered templates
    render_cache: Arc<RwLock<HashMap<String, RenderedTemplate>>>,
    /// Cache enabled flag
    cache_enabled: bool,
}

impl TemplateService {
    /// Create a new template service
    pub fn new(template_dir: impl Into<String>, content_dir: impl Into<String>) -> Result<Self> {
        let template_dir = template_dir.into();
        let content_dir = content_dir.into();

        let config = TemplateConfig::new(&template_dir, &content_dir);
        let mut engine = TemplateEngine::new(&template_dir)?;

        // Add default filters
        engine.add_default_filters();

        let loader = TemplateLoader::new(config.clone());

        Ok(Self {
            engine: Arc::new(RwLock::new(engine)),
            loader: Arc::new(RwLock::new(loader)),
            config,
            render_cache: Arc::new(RwLock::new(HashMap::new())),
            cache_enabled: true,
        })
    }

    /// Create template service with custom configuration
    pub fn with_config(config: TemplateConfig) -> Result<Self> {
        let mut engine = TemplateEngine::new(&config.template_dir)?;
        engine.add_default_filters();

        let loader = TemplateLoader::new(config.clone());

        Ok(Self {
            engine: Arc::new(RwLock::new(engine)),
            loader: Arc::new(RwLock::new(loader)),
            config,
            render_cache: Arc::new(RwLock::new(HashMap::new())),
            cache_enabled: true,
        })
    }

    /// Set available languages
    pub fn with_languages(self, languages: Vec<String>) -> Self {
        if let Ok(mut loader) = self.loader.write() {
            *loader = std::mem::take(&mut *loader).with_languages(languages);
        }
        self
    }

    /// Set default language
    pub fn with_default_language(self, lang: impl Into<String>) -> Self {
        if let Ok(mut loader) = self.loader.write() {
            *loader = std::mem::take(&mut *loader).with_default_lang(lang);
        }
        self
    }

    /// Enable or disable caching
    pub fn with_cache(mut self, enabled: bool) -> Self {
        self.cache_enabled = enabled;
        if let Ok(mut loader) = self.loader.write() {
            *loader = std::mem::take(&mut *loader).with_cache(enabled);
        }
        self
    }

    /// Render a template page
    ///
    /// For URL `/page:content-name` with language `lang`, this will:
    /// 1. Load the template configuration from `{lang}_content-name.tpl.toml`
    /// 2. Render the template using the Tera engine
    /// 3. Return the rendered content
    pub async fn render_page(&self, content_name: &str, lang: &str) -> Result<RenderedTemplate> {
        let cache_key = format!("{}_{}", lang, content_name);

        // Check render cache first
        if self.cache_enabled {
            if let Ok(cache) = self.render_cache.read() {
                if let Some(cached) = cache.get(&cache_key) {
                    debug!("Serving cached template for {}", cache_key);
                    return Ok(cached.clone());
                }
            }
        }

        // Load template configuration
        let config = {
            let mut loader = self.loader.write().map_err(|_| {
                TemplateError::RenderError("Failed to acquire loader lock".to_string())
            })?;
            loader.load_page_config_with_fallback(content_name, lang)?
        };

        // Render template
        let source_path = {
            let loader = self.loader.read().map_err(|_| {
                TemplateError::RenderError("Failed to acquire loader lock".to_string())
            })?;
            loader.get_template_file_path(content_name, lang)
        };

        let rendered = {
            let engine = self.engine.read().map_err(|_| {
                TemplateError::RenderError("Failed to acquire engine lock".to_string())
            })?;
            engine.render_template(&config, &source_path)?
        };

        // Cache the result
        if self.cache_enabled {
            if let Ok(mut cache) = self.render_cache.write() {
                cache.insert(cache_key, rendered.clone());
            }
        }

        info!(
            "Rendered template '{}' for content '{}' in language '{}'",
            config.template_name, content_name, lang
        );

        Ok(rendered)
    }

    /// Check if a template page exists
    pub fn page_exists(&self, content_name: &str, lang: &str) -> bool {
        let loader = match self.loader.read() {
            Ok(loader) => loader,
            Err(_) => return false,
        };

        loader.exists(content_name, lang)
    }

    /// Get all available content for a specific language
    pub async fn get_available_content(&self, lang: &str) -> Result<Vec<String>> {
        let loader = self
            .loader
            .read()
            .map_err(|_| TemplateError::RenderError("Failed to acquire loader lock".to_string()))?;

        loader.get_available_content(lang)
    }

    /// Get available languages
    pub fn get_available_languages(&self) -> Vec<String> {
        let loader = match self.loader.read() {
            Ok(loader) => loader,
            Err(_) => return vec!["en".to_string()],
        };

        loader.get_available_languages().to_vec()
    }

    /// Get default language
    pub fn get_default_language(&self) -> String {
        let loader = match self.loader.read() {
            Ok(loader) => loader,
            Err(_) => return "en".to_string(),
        };

        loader.get_default_language().to_string()
    }

    /// Parse page URL to extract content name
    ///
    /// Converts `/page:content-name` to `content-name`
    pub fn parse_page_url(&self, url_path: &str) -> Option<String> {
        let loader = self.loader.read().ok()?;
        loader.parse_page_url(url_path)
    }

    /// Generate page URL from content name
    ///
    /// Converts `content-name` to `/page:content-name`
    pub fn generate_page_url(&self, content_name: &str) -> String {
        let loader = match self.loader.read() {
            Ok(loader) => loader,
            Err(_) => return format!("/page:{}", content_name),
        };

        loader.generate_page_url(content_name)
    }

    /// Clear all caches
    pub async fn clear_cache(&self) -> Result<()> {
        // Clear render cache
        if let Ok(mut cache) = self.render_cache.write() {
            cache.clear();
        }

        // Clear loader cache
        if let Ok(mut loader) = self.loader.write() {
            loader.clear_cache();
        }

        info!("Template caches cleared");
        Ok(())
    }

    /// Reload templates from disk
    pub async fn reload_templates(&self) -> Result<()> {
        // Reload engine templates
        if let Ok(mut engine) = self.engine.write() {
            engine.reload_templates()?;
        }

        // Clear all caches
        self.clear_cache().await?;

        info!("Templates reloaded from disk");
        Ok(())
    }

    /// Get template engine statistics
    pub fn get_engine_stats(&self) -> HashMap<String, serde_json::Value> {
        let mut stats = HashMap::new();

        if let Ok(engine) = self.engine.read() {
            let template_names = engine.get_template_names();
            stats.insert("template_count".to_string(), template_names.len().into());
            stats.insert("template_names".to_string(), template_names.into());
        }

        if let Ok(cache) = self.render_cache.read() {
            stats.insert("render_cache_size".to_string(), cache.len().into());
        }

        if let Ok(loader) = self.loader.read() {
            let (cache_size, cache_enabled) = loader.get_cache_stats();
            stats.insert("loader_cache_size".to_string(), cache_size.into());
            stats.insert("loader_cache_enabled".to_string(), cache_enabled.into());
        }

        stats.insert("cache_enabled".to_string(), self.cache_enabled.into());

        stats
    }

    /// Render a template with custom context (for testing or special cases)
    pub async fn render_with_context(
        &self,
        template_name: &str,
        context: HashMap<String, serde_json::Value>,
    ) -> Result<String> {
        let engine = self
            .engine
            .read()
            .map_err(|_| TemplateError::RenderError("Failed to acquire engine lock".to_string()))?;

        let tera_context = TemplateEngine::create_context(&context);
        engine.render_with_context(template_name, &tera_context)
    }

    /// Add a custom filter to the template engine
    pub fn add_filter<F>(&self, name: &str, filter: F) -> Result<()>
    where
        F: tera::Filter + 'static,
    {
        let mut engine = self
            .engine
            .write()
            .map_err(|_| TemplateError::RenderError("Failed to acquire engine lock".to_string()))?;

        engine.add_filter(name, filter);
        Ok(())
    }

    /// Add a custom function to the template engine
    pub fn add_function<F>(&self, name: &str, func: F) -> Result<()>
    where
        F: tera::Function + 'static,
    {
        let mut engine = self
            .engine
            .write()
            .map_err(|_| TemplateError::RenderError("Failed to acquire engine lock".to_string()))?;

        engine.add_function(name, func);
        Ok(())
    }

    /// Preload templates for a specific language
    pub async fn preload_language(&self, lang: &str) -> Result<usize> {
        let content_names = self.get_available_content(lang).await?;
        let mut loaded_count = 0;

        for content_name in content_names {
            match self.render_page(&content_name, lang).await {
                Ok(_) => {
                    loaded_count += 1;
                    debug!("Preloaded template: {}_{}", lang, content_name);
                }
                Err(e) => {
                    warn!(
                        "Failed to preload template {}_{}: {}",
                        lang, content_name, e
                    );
                }
            }
        }

        info!(
            "Preloaded {} templates for language '{}'",
            loaded_count, lang
        );
        Ok(loaded_count)
    }

    /// Get template configuration for a specific page
    pub async fn get_page_config(
        &self,
        content_name: &str,
        lang: &str,
    ) -> Result<TemplatePageConfig> {
        let mut loader = self
            .loader
            .write()
            .map_err(|_| TemplateError::RenderError("Failed to acquire loader lock".to_string()))?;

        loader.load_page_config_with_fallback(content_name, lang)
    }

    /// Check if template engine has a specific template
    pub fn has_template(&self, template_name: &str) -> bool {
        let engine = match self.engine.read() {
            Ok(engine) => engine,
            Err(_) => return false,
        };

        engine.template_exists(template_name)
    }
}

impl Clone for TemplateService {
    fn clone(&self) -> Self {
        Self {
            engine: Arc::clone(&self.engine),
            loader: Arc::clone(&self.loader),
            config: self.config.clone(),
            render_cache: Arc::clone(&self.render_cache),
            cache_enabled: self.cache_enabled,
        }
    }
}

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

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

    #[tokio::test]
    async fn test_template_service_creation() {
        let temp_dir = TempDir::new().unwrap();
        let template_dir = temp_dir.path().join("templates");
        let content_dir = temp_dir.path().join("content");

        fs::create_dir_all(&template_dir).unwrap();
        fs::create_dir_all(&content_dir).unwrap();

        // Create a test template
        create_test_template_file(&template_dir, "test.html", "<h1>{{title}}</h1>").unwrap();

        let service = TemplateService::new(
            template_dir.to_str().unwrap(),
            content_dir.to_str().unwrap(),
        );

        assert!(service.is_ok());
    }

    #[tokio::test]
    async fn test_render_page() {
        let temp_dir = TempDir::new().unwrap();
        let template_dir = temp_dir.path().join("templates");
        let content_dir = temp_dir.path().join("content");

        fs::create_dir_all(&template_dir).unwrap();
        fs::create_dir_all(&content_dir).unwrap();

        // Create template
        create_test_template_file(
            &template_dir,
            "blog-post.html",
            "<h1>{{title}}</h1><p>{{content}}</p>",
        )
        .unwrap();

        // Create template config
        let config_content = r#"
template_name = "blog-post"

[values]
title = "Test Blog Post"
content = "This is test content"
"#;

        create_test_template_file(&content_dir, "en_my-blog.tpl.toml", config_content).unwrap();

        let service = TemplateService::new(
            template_dir.to_str().unwrap(),
            content_dir.to_str().unwrap(),
        )
        .unwrap();

        let result = service.render_page("my-blog", "en").await.unwrap();
        assert!(result.content.contains("Test Blog Post"));
        assert!(result.content.contains("This is test content"));
    }

    #[tokio::test]
    async fn test_language_fallback() {
        let temp_dir = TempDir::new().unwrap();
        let template_dir = temp_dir.path().join("templates");
        let content_dir = temp_dir.path().join("content");

        fs::create_dir_all(&template_dir).unwrap();
        fs::create_dir_all(&content_dir).unwrap();

        // Create template
        create_test_template_file(&template_dir, "blog-post.html", "<h1>{{title}}</h1>").unwrap();

        // Create only English config
        let config_content = r#"
template_name = "blog-post"

[values]
title = "English Title"
"#;

        create_test_template_file(&content_dir, "en_my-blog.tpl.toml", config_content).unwrap();

        let service = TemplateService::new(
            template_dir.to_str().unwrap(),
            content_dir.to_str().unwrap(),
        )
        .unwrap();

        // Try to render in French, should fallback to English
        let result = service.render_page("my-blog", "fr").await.unwrap();
        assert!(result.content.contains("English Title"));
    }

    #[tokio::test]
    async fn test_parse_page_url() {
        let temp_dir = TempDir::new().unwrap();
        let template_dir = temp_dir.path().join("templates");
        let content_dir = temp_dir.path().join("content");

        fs::create_dir_all(&template_dir).unwrap();
        fs::create_dir_all(&content_dir).unwrap();

        let service = TemplateService::new(
            template_dir.to_str().unwrap(),
            content_dir.to_str().unwrap(),
        )
        .unwrap();

        assert_eq!(
            service.parse_page_url("/page:my-blog"),
            Some("my-blog".to_string())
        );
        assert_eq!(
            service.parse_page_url("/page:about-us"),
            Some("about-us".to_string())
        );
        assert_eq!(service.parse_page_url("/other-path"), None);
    }

    #[tokio::test]
    async fn test_generate_page_url() {
        let temp_dir = TempDir::new().unwrap();
        let template_dir = temp_dir.path().join("templates");
        let content_dir = temp_dir.path().join("content");

        fs::create_dir_all(&template_dir).unwrap();
        fs::create_dir_all(&content_dir).unwrap();

        let service = TemplateService::new(
            template_dir.to_str().unwrap(),
            content_dir.to_str().unwrap(),
        )
        .unwrap();

        assert_eq!(service.generate_page_url("my-blog"), "/page:my-blog");
        assert_eq!(service.generate_page_url("about-us"), "/page:about-us");
    }
}
