use std::collections::HashSet;
use std::sync::Arc;

use async_trait::async_trait;
use serde_json::json;

use crate::skills::{Skill, SkillsLoader};
use crate::tools::traits::{Tool, ToolResult};

pub struct GetSkillTool {
    skills_loader: Arc<SkillsLoader>,
    allowed: Option<HashSet<String>>,
}

impl GetSkillTool {
    pub fn new(skills_loader: Arc<SkillsLoader>) -> Self {
        Self {
            skills_loader,
            allowed: None,
        }
    }

    pub fn scoped(skills_loader: Arc<SkillsLoader>, allowed: &[String]) -> Self {
        Self {
            skills_loader,
            allowed: Some(allowed.iter().cloned().collect()),
        }
    }

    fn format_skill(&self, skill: &Skill) -> String {
        let mut result = format!("# Skill: {}\n\n{}", skill.name, skill.description);

        if let Some(path) = &skill.path {
            result.push_str(&format!(
                "\n\n**Skill Root Directory:** `{}`\n\nAll files and references in this skill are relative to this directory.",
                path.to_string_lossy()
            ));
        }

        result.push_str(&format!("\n\n---\n\n{}", skill.content));
        result
    }
}

#[async_trait]
impl Tool for GetSkillTool {
    fn runtime_injected(&self) -> bool {
        true
    }

    fn name(&self) -> &str {
        "get_skill"
    }

    fn description(&self) -> &str {
        "Get complete content and guidance for a specified skill. Use this when you need detailed instructions for a specific type of task."
    }

    fn parameters_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["get", "list"],
                    "description": "操作类型: get 获取指定 skill 完整内容, list 列出所有可用 skill"
                },
                "skill_name": {
                    "type": "string",
                    "description": "Name of the skill to retrieve，仅在 action 为 get 时必填"
                }
            },
            "required": []
        })
    }

    fn read_only(&self) -> bool {
        true
    }

    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
        let action = args.get("action").and_then(|v| v.as_str()).unwrap_or("get");

        match action {
            "list" => self.list_skills_full(),
            _ => self.get_skill_by_name(&args),
        }
    }
}

impl GetSkillTool {
    fn get_skill_by_name(&self, args: &serde_json::Value) -> anyhow::Result<ToolResult> {
        let skill_name = match args.get("skill_name").and_then(|v| v.as_str()) {
            Some(name) => name,
            None => {
                return Ok(ToolResult {
                    success: false,
                    output: String::new(),
                    error: Some("Missing required parameter: skill_name".to_string()),
                });
            }
        };

        if self
            .allowed
            .as_ref()
            .is_some_and(|allowed| !allowed.contains(skill_name))
        {
            return Ok(ToolResult {
                success: false,
                output: String::new(),
                error: Some(format!(
                    "Skill '{skill_name}' is not allowed for this Agent"
                )),
            });
        }

        match self.skills_loader.get_skill(skill_name) {
            Some(skill) => {
                let formatted = self.format_skill(&skill);
                Ok(ToolResult {
                    success: true,
                    output: formatted,
                    error: None,
                })
            }
            None => {
                let available = self.skills_loader.list_skills();
                let available_str = if available.is_empty() {
                    "No skills available".to_string()
                } else {
                    available
                        .iter()
                        .map(|(name, _)| name.as_str())
                        .collect::<Vec<_>>()
                        .join(", ")
                };
                Ok(ToolResult {
                    success: false,
                    output: String::new(),
                    error: Some(format!(
                        "Skill '{}' not found. Available skills: {}",
                        skill_name, available_str
                    )),
                })
            }
        }
    }

    fn list_skills_full(&self) -> anyhow::Result<ToolResult> {
        let skills: Vec<_> = self
            .skills_loader
            .get_loaded_skills()
            .into_iter()
            .filter(|skill| {
                self.allowed
                    .as_ref()
                    .is_none_or(|allowed| allowed.contains(&skill.name))
            })
            .collect();
        if skills.is_empty() {
            return Ok(ToolResult {
                success: true,
                output: "当前没有安装任何 skill".to_string(),
                error: None,
            });
        }
        let mut output = format!("可用 skill (共 {} 个):\n", skills.len());
        for s in &skills {
            let always_mark = if s.always { " [常驻]" } else { "" };
            let path_str = s
                .path
                .as_ref()
                .map(|p| p.to_string_lossy().to_string())
                .unwrap_or_else(|| "—".to_string());
            output.push_str(&format!(
                "- {}{}\n  简介: {}\n  路径: {}\n",
                s.name, always_mark, s.description, path_str
            ));
        }
        Ok(ToolResult {
            success: true,
            output,
            error: None,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::Write;
    use std::path::PathBuf;
    use tempfile::tempdir;

    #[tokio::test]
    async fn test_get_existing_skill() {
        let temp_dir = tempdir().unwrap();

        let skill_dir = temp_dir.path().join("test-skill");
        std::fs::create_dir(&skill_dir).unwrap();

        let mut skill_file = File::create(skill_dir.join("SKILL.md")).unwrap();
        writeln!(skill_file, "---").unwrap();
        writeln!(skill_file, "name: test-skill").unwrap();
        writeln!(skill_file, "description: A test skill").unwrap();
        writeln!(skill_file, "---").unwrap();
        writeln!(skill_file, "# Test Skill").unwrap();
        writeln!(skill_file, "This is the test content.").unwrap();

        let loader = SkillsLoader::new_for_testing(
            temp_dir.path().to_path_buf(),
            PathBuf::from("/nonexistent"),
        );
        loader.load_skills();

        let tool = GetSkillTool::new(Arc::new(loader));

        let result = tool
            .execute(json!({ "skill_name": "test-skill" }))
            .await
            .unwrap();

        assert!(result.success);
        assert!(result.output.contains("test-skill"));
        assert!(result.output.contains("test content"));
    }

    #[tokio::test]
    async fn test_get_nonexistent_skill() {
        let loader = SkillsLoader::new();
        let tool = GetSkillTool::new(Arc::new(loader));

        let result = tool
            .execute(json!({ "skill_name": "nonexistent" }))
            .await
            .unwrap();

        assert!(!result.success);
        assert!(result.error.is_some());
    }
}
