//! Prompt Template Engine (Phase-81: T81.6)
//!
//! Provides a template system for managing and rendering prompt templates
//! with variable substitution.
//!
//! # Features
//!
//! - Template CRUD with DashMap (concurrent) + JSON file persistence
//! - `{{variable}}` placeholder substitution
//! - Built-in default templates (system, user, rag, summary categories)
//! - Template versioning support
//! - Category-based organization

use std::collections::HashMap;
use std::path::{Path, PathBuf};

use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};

use super::error::{LlmError, LlmResult};

// ============================================================================
// PromptTemplate
// ============================================================================

/// A prompt template with variable placeholders.
///
/// Templates use `{{variable_name}}` syntax for placeholders that are
/// replaced at render time.
///
/// # Example
///
/// ```text
/// You are a helpful assistant for {{product_name}}.
/// The user's name is {{user_name}}.
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptTemplate {
    /// Unique template ID.
    pub id: String,
    /// Human-readable template name.
    pub name: String,
    /// Template content with `{{variable}}` placeholders.
    pub content: String,
    /// Description of what this template does.
    #[serde(default)]
    pub description: String,
    /// List of variable names used in this template.
    pub variables: Vec<String>,
    /// Template category (e.g., "system", "user", "rag", "summary").
    #[serde(default = "default_category")]
    pub category: String,
    /// Template version string.
    #[serde(default = "default_version")]
    pub version: String,
    /// Whether this is a default/built-in template.
    #[serde(default)]
    pub is_builtin: bool,
    /// Whether this template is the default for its category.
    #[serde(default)]
    pub is_default: bool,
    /// Optional metadata.
    #[serde(default)]
    pub metadata: serde_json::Value,
    /// Created timestamp (ms since epoch).
    pub created_at: i64,
    /// Updated timestamp (ms since epoch).
    pub updated_at: i64,
}

fn default_category() -> String {
    "user".to_string()
}

fn default_version() -> String {
    "1.0.0".to_string()
}

impl PromptTemplate {
    /// Create a new prompt template.
    pub fn new(
        id: impl Into<String>,
        name: impl Into<String>,
        content: impl Into<String>,
    ) -> Self {
        let content = content.into();
        let variables = Self::extract_variables(&content);
        let now = now_ms();
        Self {
            id: id.into(),
            name: name.into(),
            content,
            description: String::new(),
            variables,
            category: default_category(),
            version: default_version(),
            is_builtin: false,
            is_default: false,
            metadata: serde_json::Value::Null,
            created_at: now,
            updated_at: now,
        }
    }

    /// Set the description.
    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
        self.description = desc.into();
        self
    }

    /// Set the category.
    pub fn with_category(mut self, category: impl Into<String>) -> Self {
        self.category = category.into();
        self
    }

    /// Set as builtin template.
    pub fn as_builtin(mut self) -> Self {
        self.is_builtin = true;
        self
    }

    /// Set as default template for its category.
    pub fn as_default(mut self) -> Self {
        self.is_default = true;
        self
    }

    /// Set the version.
    pub fn with_version(mut self, version: impl Into<String>) -> Self {
        self.version = version.into();
        self
    }

    /// Update the content and re-extract variables.
    pub fn set_content(&mut self, content: impl Into<String>) {
        self.content = content.into();
        self.variables = Self::extract_variables(&self.content);
        self.updated_at = now_ms();
    }

    /// Extract `{{variable}}` names from template content.
    pub fn extract_variables(content: &str) -> Vec<String> {
        let mut vars = Vec::new();
        let mut seen = std::collections::HashSet::new();
        let mut i = 0;
        let bytes = content.as_bytes();
        while i + 3 < bytes.len() {
            if bytes[i] == b'{' && bytes[i + 1] == b'{' {
                // Find closing }}
                if let Some(end) = content[i + 2..].find("}}") {
                    let var_name = content[i + 2..i + 2 + end].trim();
                    if !var_name.is_empty() && seen.insert(var_name.to_string()) {
                        vars.push(var_name.to_string());
                    }
                    i = i + 2 + end + 2;
                    continue;
                }
            }
            i += 1;
        }
        vars
    }

    /// Render the template by replacing `{{variable}}` placeholders with values.
    ///
    /// Returns an error if any required variable is missing.
    pub fn render(&self, variables: &HashMap<String, String>) -> LlmResult<String> {
        let mut result = self.content.clone();
        let mut missing = Vec::new();

        for var_name in &self.variables {
            let placeholder = format!("{{{{{}}}}}", var_name);
            if let Some(value) = variables.get(var_name) {
                result = result.replace(&placeholder, value);
            } else {
                missing.push(var_name.clone());
            }
        }

        if !missing.is_empty() {
            return Err(LlmError::InvalidInput(format!(
                "Missing template variables: {}",
                missing.join(", ")
            )));
        }

        Ok(result)
    }

    /// Render the template with best-effort: missing variables are left as-is.
    pub fn render_partial(&self, variables: &HashMap<String, String>) -> String {
        let mut result = self.content.clone();
        for (key, value) in variables {
            let placeholder = format!("{{{{{}}}}}", key);
            result = result.replace(&placeholder, value);
        }
        result
    }
}

// ============================================================================
// PromptTemplateStore
// ============================================================================

/// Persistent store for prompt templates.
///
/// Uses DashMap for concurrent in-memory access + JSON file persistence.
pub struct PromptTemplateStore {
    templates: DashMap<String, PromptTemplate>,
    file_path: PathBuf,
    dirty: std::sync::atomic::AtomicBool,
}

impl PromptTemplateStore {
    /// Open or create a prompt template store at the given directory.
    pub fn open(dir: &Path) -> LlmResult<Self> {
        std::fs::create_dir_all(dir)?;
        let file_path = dir.join("prompt_templates.json");
        let templates = DashMap::new();

        if file_path.exists() {
            let data = std::fs::read_to_string(&file_path)?;
            let stored: Vec<PromptTemplate> = serde_json::from_str(&data)
                .map_err(|e| LlmError::SerializationError(format!("Failed to parse prompt templates: {e}")))?;
            for t in stored {
                templates.insert(t.id.clone(), t);
            }
            info!(count = templates.len(), "Loaded prompt templates from disk");
        }

        let store = Self {
            templates,
            file_path,
            dirty: std::sync::atomic::AtomicBool::new(false),
        };

        // Ensure built-in templates exist
        store.ensure_builtins();

        Ok(store)
    }

    /// Insert or update a template.
    pub fn upsert(&self, mut template: PromptTemplate) -> LlmResult<PromptTemplate> {
        template.updated_at = now_ms();
        // Re-extract variables in case content was modified externally
        template.variables = PromptTemplate::extract_variables(&template.content);

        // If setting as default, clear other defaults in same category
        if template.is_default {
            let category = template.category.clone();
            let template_id = template.id.clone();
            for mut entry in self.templates.iter_mut() {
                if entry.category == category && entry.id != template_id && entry.is_default {
                    entry.is_default = false;
                }
            }
        }

        let result = template.clone();
        self.templates.insert(template.id.clone(), template);
        self.save()?;
        debug!(id = %result.id, "Upserted prompt template");
        Ok(result)
    }

    /// Get a template by ID.
    pub fn get(&self, id: &str) -> Option<PromptTemplate> {
        self.templates.get(id).map(|e| e.value().clone())
    }

    /// Delete a template by ID. Returns true if found and deleted.
    pub fn delete(&self, id: &str) -> LlmResult<bool> {
        if let Some((_, template)) = self.templates.remove(id) {
            if template.is_builtin {
                // Don't allow deleting built-in templates
                self.templates.insert(id.to_string(), template);
                return Err(LlmError::PermissionDenied(
                    "Cannot delete built-in template".into(),
                ));
            }
            self.save()?;
            debug!(id = %id, "Deleted prompt template");
            Ok(true)
        } else {
            Ok(false)
        }
    }

    /// List all templates, optionally filtered by category.
    pub fn list(&self, category: Option<&str>) -> Vec<PromptTemplate> {
        let mut result: Vec<PromptTemplate> = self
            .templates
            .iter()
            .filter(|e| {
                if let Some(cat) = category {
                    e.category == cat
                } else {
                    true
                }
            })
            .map(|e| e.value().clone())
            .collect();
        result.sort_by(|a, b| a.name.cmp(&b.name));
        result
    }

    /// Get the default template for a given category.
    pub fn get_default(&self, category: &str) -> Option<PromptTemplate> {
        self.templates
            .iter()
            .find(|e| e.category == category && e.is_default)
            .map(|e| e.value().clone())
    }

    /// Render a template by ID with the given variables.
    pub fn render(&self, id: &str, variables: &HashMap<String, String>) -> LlmResult<String> {
        let template = self
            .get(id)
            .ok_or_else(|| LlmError::NotFound(format!("Prompt template '{}' not found", id)))?;
        template.render(variables)
    }

    /// Render a template with partial substitution (missing vars left as-is).
    pub fn render_partial(&self, id: &str, variables: &HashMap<String, String>) -> LlmResult<String> {
        let template = self
            .get(id)
            .ok_or_else(|| LlmError::NotFound(format!("Prompt template '{}' not found", id)))?;
        Ok(template.render_partial(variables))
    }

    /// Save all templates to disk.
    fn save(&self) -> LlmResult<()> {
        let all: Vec<PromptTemplate> = self
            .templates
            .iter()
            .map(|e| e.value().clone())
            .collect();
        let json = serde_json::to_string_pretty(&all)?;
        std::fs::write(&self.file_path, json)?;
        self.dirty.store(false, std::sync::atomic::Ordering::Relaxed);
        Ok(())
    }

    /// Ensure built-in default templates exist.
    fn ensure_builtins(&self) {
        let builtins = builtin_templates();
        let mut added = 0;
        for t in builtins {
            if !self.templates.contains_key(&t.id) {
                self.templates.insert(t.id.clone(), t);
                added += 1;
            }
        }
        if added > 0 {
            if let Err(e) = self.save() {
                warn!("Failed to save built-in templates: {e}");
            }
            info!(count = added, "Added built-in prompt templates");
        }
    }

    /// Get template count.
    pub fn len(&self) -> usize {
        self.templates.len()
    }

    /// Check if store is empty.
    pub fn is_empty(&self) -> bool {
        self.templates.is_empty()
    }
}

// ============================================================================
// Built-in Templates
// ============================================================================

/// Create the set of built-in default prompt templates.
fn builtin_templates() -> Vec<PromptTemplate> {
    vec![
        // ── System Templates ─────────────────────────────────────
        PromptTemplate::new(
            "system-default",
            "Default System Prompt",
            "You are a helpful, accurate, and friendly AI assistant. \
             Answer the user's questions clearly and concisely. \
             If you don't know something, say so honestly.",
        )
        .with_category("system")
        .with_description("General-purpose system prompt for chat conversations")
        .as_builtin()
        .as_default(),

        PromptTemplate::new(
            "system-coder",
            "Coding Assistant System Prompt",
            "You are an expert software engineer and coding assistant. \
             Help the user with programming tasks, code reviews, debugging, \
             and software architecture. Provide clear, well-documented code \
             examples. Follow best practices for {{language}}.",
        )
        .with_category("system")
        .with_description("System prompt optimized for coding tasks")
        .as_builtin(),

        PromptTemplate::new(
            "system-analyst",
            "Data Analyst System Prompt",
            "You are a skilled data analyst. Help the user analyze data, \
             create queries, interpret results, and provide insights. \
             When working with {{database_type}}, use appropriate syntax \
             and best practices.",
        )
        .with_category("system")
        .with_description("System prompt for data analysis tasks")
        .as_builtin(),

        // ── RAG Templates ────────────────────────────────────────
        PromptTemplate::new(
            "rag-default",
            "Default RAG Context Template",
            "Use the following context to answer the user's question. \
             If the context doesn't contain relevant information, say so.\n\n\
             ## Context\n{{context}}\n\n\
             ## Question\n{{question}}",
        )
        .with_category("rag")
        .with_description("Standard RAG template with context injection")
        .as_builtin()
        .as_default(),

        PromptTemplate::new(
            "rag-strict",
            "Strict RAG Template",
            "Answer the following question based ONLY on the provided context. \
             Do NOT use any external knowledge. If the answer is not in the context, \
             respond with \"I cannot find this information in the provided context.\"\n\n\
             ## Context (relevance score: {{score}})\n{{context}}\n\n\
             ## Question\n{{question}}",
        )
        .with_category("rag")
        .with_description("Strict RAG template — answers only from context")
        .as_builtin(),

        // ── Summary Templates ────────────────────────────────────
        PromptTemplate::new(
            "summary-default",
            "Default Summary Template",
            "Please provide a concise summary of the following conversation:\n\n\
             {{conversation}}\n\n\
             Summary:",
        )
        .with_category("summary")
        .with_description("Summarize a conversation into key points")
        .as_builtin()
        .as_default(),

        PromptTemplate::new(
            "summary-title",
            "Conversation Title Generator",
            "Generate a short, descriptive title (max 8 words) for this conversation:\n\n\
             {{conversation}}\n\n\
             Title:",
        )
        .with_category("summary")
        .with_description("Generate a title for a conversation")
        .as_builtin(),

        // ── User Templates ───────────────────────────────────────
        PromptTemplate::new(
            "user-translate",
            "Translation Template",
            "Translate the following text from {{source_language}} to {{target_language}}. \
             Maintain the original tone and meaning.\n\n\
             Text: {{text}}",
        )
        .with_category("user")
        .with_description("Multi-language translation template")
        .as_builtin(),

        PromptTemplate::new(
            "user-explain",
            "Explain Like I'm 5",
            "Explain the following concept in simple terms that a {{audience}} could understand:\n\n\
             {{concept}}",
        )
        .with_category("user")
        .with_description("Simplify complex concepts for any audience")
        .as_builtin(),
    ]
}

// ============================================================================
// Helpers
// ============================================================================

fn now_ms() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as i64
}

// ============================================================================
// Tests
// ============================================================================

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

    #[test]
    fn test_extract_variables() {
        let vars = PromptTemplate::extract_variables("Hello {{name}}, welcome to {{place}}!");
        assert_eq!(vars, vec!["name", "place"]);
    }

    #[test]
    fn test_extract_variables_empty() {
        let vars = PromptTemplate::extract_variables("No variables here.");
        assert!(vars.is_empty());
    }

    #[test]
    fn test_extract_variables_duplicate() {
        let vars = PromptTemplate::extract_variables("{{a}} and {{b}} and {{a}} again");
        assert_eq!(vars, vec!["a", "b"]);
    }

    #[test]
    fn test_extract_variables_whitespace() {
        let vars = PromptTemplate::extract_variables("{{ name }} and {{  place  }}");
        assert_eq!(vars, vec!["name", "place"]);
    }

    #[test]
    fn test_render_success() {
        let t = PromptTemplate::new("t1", "Test", "Hello {{name}}, you are in {{city}}.");
        let mut vars = HashMap::new();
        vars.insert("name".to_string(), "Alice".to_string());
        vars.insert("city".to_string(), "Tokyo".to_string());
        let result = t.render(&vars).unwrap();
        assert_eq!(result, "Hello Alice, you are in Tokyo.");
    }

    #[test]
    fn test_render_missing_variable() {
        let t = PromptTemplate::new("t2", "Test", "Hello {{name}}, you are in {{city}}.");
        let mut vars = HashMap::new();
        vars.insert("name".to_string(), "Bob".to_string());
        let result = t.render(&vars);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("city"));
    }

    #[test]
    fn test_render_partial() {
        let t = PromptTemplate::new("t3", "Test", "Hello {{name}}, you are in {{city}}.");
        let mut vars = HashMap::new();
        vars.insert("name".to_string(), "Bob".to_string());
        let result = t.render_partial(&vars);
        assert_eq!(result, "Hello Bob, you are in {{city}}.");
    }

    #[test]
    fn test_prompt_template_new() {
        let t = PromptTemplate::new("id1", "My Template", "Use {{tool}} for {{task}}");
        assert_eq!(t.id, "id1");
        assert_eq!(t.name, "My Template");
        assert_eq!(t.variables, vec!["tool", "task"]);
        assert_eq!(t.category, "user");
        assert_eq!(t.version, "1.0.0");
        assert!(!t.is_builtin);
        assert!(!t.is_default);
    }

    #[test]
    fn test_builtin_templates() {
        let builtins = builtin_templates();
        assert!(builtins.len() >= 9);
        // All builtins should be marked
        for t in &builtins {
            assert!(t.is_builtin, "Template {} should be builtin", t.id);
        }
        // Check system default exists
        let sys_default = builtins.iter().find(|t| t.id == "system-default").unwrap();
        assert!(sys_default.is_default);
        assert_eq!(sys_default.category, "system");
    }

    #[test]
    fn test_set_content_updates_variables() {
        let mut t = PromptTemplate::new("t4", "Test", "Hello {{name}}");
        assert_eq!(t.variables, vec!["name"]);
        t.set_content("Goodbye {{farewell_target}} from {{sender}}");
        assert_eq!(t.variables, vec!["farewell_target", "sender"]);
    }

    #[test]
    fn test_store_crud() {
        let dir = tempfile::tempdir().unwrap();
        let store = PromptTemplateStore::open(dir.path()).unwrap();

        // Built-in templates should be loaded
        let initial_count = store.len();
        assert!(initial_count >= 9);

        // Create custom template
        let t = PromptTemplate::new("custom-1", "Custom", "Hello {{who}}");
        let created = store.upsert(t).unwrap();
        assert_eq!(created.id, "custom-1");
        assert_eq!(store.len(), initial_count + 1);

        // Read
        let fetched = store.get("custom-1").unwrap();
        assert_eq!(fetched.name, "Custom");

        // Update
        let mut updated = fetched.clone();
        updated.name = "Updated Custom".to_string();
        store.upsert(updated).unwrap();
        assert_eq!(store.get("custom-1").unwrap().name, "Updated Custom");

        // Delete
        let deleted = store.delete("custom-1").unwrap();
        assert!(deleted);
        assert!(store.get("custom-1").is_none());
        assert_eq!(store.len(), initial_count);
    }

    #[test]
    fn test_store_cannot_delete_builtin() {
        let dir = tempfile::tempdir().unwrap();
        let store = PromptTemplateStore::open(dir.path()).unwrap();
        let result = store.delete("system-default");
        assert!(result.is_err());
    }

    #[test]
    fn test_store_list_by_category() {
        let dir = tempfile::tempdir().unwrap();
        let store = PromptTemplateStore::open(dir.path()).unwrap();

        let system_templates = store.list(Some("system"));
        assert!(system_templates.len() >= 3);
        for t in &system_templates {
            assert_eq!(t.category, "system");
        }

        let rag_templates = store.list(Some("rag"));
        assert!(rag_templates.len() >= 2);
    }

    #[test]
    fn test_store_get_default() {
        let dir = tempfile::tempdir().unwrap();
        let store = PromptTemplateStore::open(dir.path()).unwrap();

        let default_sys = store.get_default("system").unwrap();
        assert_eq!(default_sys.id, "system-default");
        assert!(default_sys.is_default);

        let default_rag = store.get_default("rag").unwrap();
        assert_eq!(default_rag.id, "rag-default");
    }

    #[test]
    fn test_store_render() {
        let dir = tempfile::tempdir().unwrap();
        let store = PromptTemplateStore::open(dir.path()).unwrap();

        let mut vars = HashMap::new();
        vars.insert("context".to_string(), "AI is transforming industries.".to_string());
        vars.insert("question".to_string(), "How is AI being used?".to_string());
        let rendered = store.render("rag-default", &vars).unwrap();
        assert!(rendered.contains("AI is transforming industries."));
        assert!(rendered.contains("How is AI being used?"));
    }

    #[test]
    fn test_store_persistence() {
        let dir = tempfile::tempdir().unwrap();

        // Create store and add template
        {
            let store = PromptTemplateStore::open(dir.path()).unwrap();
            let t = PromptTemplate::new("persist-1", "Persistent", "Hello {{name}}");
            store.upsert(t).unwrap();
        }

        // Reopen and verify
        {
            let store = PromptTemplateStore::open(dir.path()).unwrap();
            let t = store.get("persist-1").unwrap();
            assert_eq!(t.name, "Persistent");
        }
    }

    #[test]
    fn test_store_set_default() {
        let dir = tempfile::tempdir().unwrap();
        let store = PromptTemplateStore::open(dir.path()).unwrap();

        // Create a new system template and set as default
        let t = PromptTemplate::new("system-custom", "Custom System", "Be brief.")
            .with_category("system")
            .as_default();
        store.upsert(t).unwrap();

        // The old default should be cleared
        let old_default = store.get("system-default").unwrap();
        assert!(!old_default.is_default, "Old default should be cleared");

        let new_default = store.get_default("system").unwrap();
        assert_eq!(new_default.id, "system-custom");
    }
}
