use std::collections::HashMap;

/// Modular system prompt builder inspired by pi's `buildSystemPrompt`.
///
/// Supports:
/// - Base prompt (replaceable or appendable)
/// - Tool snippets injected into an "Available tools" section
/// - Project context files (AGENTS.md, etc.)
/// - Skills injection
/// - Variable substitution ({{key}})
/// - Metadata (date)
///
/// # Example
/// ```rust
/// use ai::agent::prompt_builder::SystemPromptBuilder;
///
/// let prompt = SystemPromptBuilder::new()
///     .base_prompt("You are a helpful assistant.")
///     .tool_snippet("bash", "Execute shell commands")
///     .tool_snippet("read", "Read file contents")
///     .project_context("AGENTS.md", "# Project Rules\n- Follow conventions")
///     .variable("repo_name", "gitdataai")
///     .build();
/// ```
#[derive(Clone, Debug)]
pub struct SystemPromptBuilder {
    base_prompt: Option<String>,
    append_prompt: Option<String>,
    tool_snippets: Vec<(String, String)>,
    tool_guidelines: Vec<String>,
    project_contexts: Vec<(String, String)>,
    skills: Vec<String>,
    variables: HashMap<String, String>,
    date: Option<String>,
    custom_sections: Vec<(String, String)>,
}

impl SystemPromptBuilder {
    pub fn new() -> Self {
        Self {
            base_prompt: None,
            append_prompt: None,
            tool_snippets: Vec::new(),
            tool_guidelines: Vec::new(),
            project_contexts: Vec::new(),
            skills: Vec::new(),
            variables: HashMap::new(),
            date: None,
            custom_sections: Vec::new(),
        }
    }

    /// Set the base system prompt. Replaces the default prompt.
    pub fn base_prompt(mut self, prompt: impl Into<String>) -> Self {
        self.base_prompt = Some(prompt.into());
        self
    }

    /// Append additional text to the system prompt after the base.
    pub fn append_prompt(mut self, text: impl Into<String>) -> Self {
        self.append_prompt = Some(text.into());
        self
    }

    /// Add a one-line tool description snippet.
    pub fn tool_snippet(
        mut self,
        tool_name: impl Into<String>,
        description: impl Into<String>,
    ) -> Self {
        self.tool_snippets
            .push((tool_name.into(), description.into()));
        self
    }

    /// Add a guideline bullet for the tools section.
    pub fn tool_guideline(mut self, guideline: impl Into<String>) -> Self {
        self.tool_guidelines.push(guideline.into());
        self
    }

    /// Add a project context file (e.g., AGENTS.md content).
    pub fn project_context(
        mut self,
        path: impl Into<String>,
        content: impl Into<String>,
    ) -> Self {
        self.project_contexts.push((path.into(), content.into()));
        self
    }

    /// Add a skill description to inject into the prompt.
    pub fn skill(mut self, skill_description: impl Into<String>) -> Self {
        self.skills.push(skill_description.into());
        self
    }

    /// Set a variable for {{key}} substitution.
    pub fn variable(
        mut self,
        key: impl Into<String>,
        value: impl Into<String>,
    ) -> Self {
        self.variables.insert(key.into(), value.into());
        self
    }

    /// Set multiple variables from an iterator.
    pub fn variables(
        mut self,
        vars: impl IntoIterator<Item = (String, String)>,
    ) -> Self {
        self.variables.extend(vars);
        self
    }

    /// Set the date metadata (ISO format: YYYY-MM-DD).
    pub fn date(mut self, date: impl Into<String>) -> Self {
        self.date = Some(date.into());
        self
    }

    /// Add a custom named section to the prompt.
    pub fn custom_section(
        mut self,
        name: impl Into<String>,
        content: impl Into<String>,
    ) -> Self {
        self.custom_sections.push((name.into(), content.into()));
        self
    }

    /// Build the final system prompt string.
    pub fn build(self) -> String {
        let mut parts: Vec<String> = Vec::new();

        // 1. Base prompt
        if let Some(base) = &self.base_prompt {
            parts.push(base.clone());
        }

        // 2. Append prompt
        if let Some(append) = &self.append_prompt {
            parts.push(append.clone());
        }

        // 3. Tool snippets section
        if !self.tool_snippets.is_empty() {
            let mut section = String::from("\n## Available Tools\n");
            for (name, desc) in &self.tool_snippets {
                section.push_str(&format!("- `{name}`: {desc}\n"));
            }
            if !self.tool_guidelines.is_empty() {
                section.push_str("\n### Tool Guidelines\n");
                for guideline in &self.tool_guidelines {
                    section.push_str(&format!("- {guideline}\n"));
                }
            }
            parts.push(section);
        }

        // 4. Project context files
        if !self.project_contexts.is_empty() {
            let mut section = String::from("\n<project_context>\n\n");
            section
                .push_str("Project-specific instructions and guidelines:\n\n");
            for (path, content) in &self.project_contexts {
                section.push_str(&format!("<project_instructions path=\"{path}\">\n{content}\n</project_instructions>\n\n"));
            }
            section.push_str("</project_context>");
            parts.push(section);
        }

        // 5. Skills section
        if !self.skills.is_empty() {
            let mut section = String::from("\n## Available Skills\n");
            for skill in &self.skills {
                section.push_str(&format!("{skill}\n"));
            }
            parts.push(section);
        }

        // 6. Custom sections
        for (name, content) in &self.custom_sections {
            parts.push(format!("\n## {name}\n{content}"));
        }

        // 7. Metadata footer
        if let Some(date) = &self.date {
            parts.push(format!("\nCurrent date: {date}"));
        }

        let mut result = parts.join("\n");

        // 8. Variable substitution
        for (key, value) in &self.variables {
            let placeholder = format!("{{{{{}}}}}", key);
            result = result.replace(&placeholder, value);
        }

        result
    }
}

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

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

    #[test]
    fn test_basic_build() {
        let prompt = SystemPromptBuilder::new()
            .base_prompt("You are a helpful assistant.")
            .date("2026-05-29")
            .build();

        assert!(prompt.contains("You are a helpful assistant."));
        assert!(prompt.contains("Current date: 2026-05-29"));
    }

    #[test]
    fn test_variable_substitution() {
        let prompt = SystemPromptBuilder::new()
            .base_prompt("Repo: {{repo_name}}, User: {{user}}")
            .variable("repo_name", "gitdataai")
            .variable("user", "zhenyi")
            .build();

        assert!(prompt.contains("Repo: gitdataai"));
        assert!(prompt.contains("User: zhenyi"));
    }

    #[test]
    fn test_tool_snippets() {
        let prompt = SystemPromptBuilder::new()
            .base_prompt("Agent prompt.")
            .tool_snippet("bash", "Execute shell commands")
            .tool_snippet("read", "Read file contents")
            .build();

        assert!(prompt.contains("## Available Tools"));
        assert!(prompt.contains("`bash`: Execute shell commands"));
    }

    #[test]
    fn test_project_context() {
        let prompt = SystemPromptBuilder::new()
            .base_prompt("Base.")
            .project_context("AGENTS.md", "# Rules\n- Follow conventions")
            .build();

        assert!(prompt.contains("<project_context>"));
        assert!(prompt.contains("AGENTS.md"));
        assert!(prompt.contains("Follow conventions"));
    }

    #[test]
    fn test_custom_section() {
        let prompt = SystemPromptBuilder::new()
            .base_prompt("Base.")
            .custom_section("Memory", "Remember: user prefers Rust")
            .build();

        assert!(prompt.contains("## Memory"));
        assert!(prompt.contains("user prefers Rust"));
    }
}
