use std::sync::Arc;

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

use crate::skills::SkillRuntime;
use crate::storage::SkillEventRepository;
use crate::tools::traits::{Tool, ToolContext, ToolResult};
use crate::tools::check_null_args;

pub struct SkillActivateTool {
    skills: Arc<SkillRuntime>,
    events: Arc<dyn SkillEventRepository>,
}

impl SkillActivateTool {
    pub fn new(skills: Arc<SkillRuntime>, events: Arc<dyn SkillEventRepository>) -> Self {
        Self { skills, events }
    }

    fn record_event(
        &self,
        context: &ToolContext,
        event_type: &str,
        skill_name: Option<&str>,
        payload: &serde_json::Value,
    ) {
        if let Err(err) = self.events.append_skill_event(
            context.session_id.as_deref(),
            event_type,
            skill_name,
            payload,
        ) {
            tracing::warn!(error = %err, event_type, skill_name, "Failed to record skill activation event");
        }
    }
}

#[async_trait]
impl Tool for SkillActivateTool {
    fn name(&self) -> &str {
        "skill_activate"
    }

    fn description(&self) -> &str {
        "Load detailed instructions for a named skill discovered from SKILL.md files. Use when a task matches a listed skill description."
    }

    fn parameters_schema(&self) -> serde_json::Value {
        json!({
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "Skill name from the available skills list"
                }
            },
            "required": ["name"]
        })
    }

    async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
        self.execute_with_context(&ToolContext::default(), args)
            .await
    }

    async fn execute_with_context(
        &self,
        context: &ToolContext,
        args: serde_json::Value,
    ) -> anyhow::Result<ToolResult> {
        if let Some(result) = check_null_args(&args, "skill_activate") {
            return Ok(result);
        }

        let skill_name = match args.get("name").and_then(|value| value.as_str()) {
            Some(name) if !name.trim().is_empty() => name,
            _ => {
                self.record_event(
                    context,
                    "activation_failed",
                    None,
                    &json!({
                        "reason": "missing_name",
                        "arguments": args,
                    }),
                );
                return Ok(error_result("Missing required parameter: name"));
            }
        };

        match self.skills.activation_payload(skill_name) {
            Ok(output) => {
                if let Ok(payload) = self.skills.activation_event_payload(skill_name) {
                    self.record_event(context, "activated", Some(skill_name), &payload);
                }
                Ok(ToolResult {
                    success: true,
                    output,
                    error: None,
                })
            }
            Err(err) => {
                self.record_event(
                    context,
                    "activation_failed",
                    Some(skill_name),
                    &json!({
                        "reason": err,
                        "arguments": args,
                    }),
                );
                Ok(error_result(&err))
            }
        }
    }
}

fn error_result(message: &str) -> ToolResult {
    ToolResult {
        success: false,
        output: String::new(),
        error: Some(message.to_string()),
    }
}

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

    const TEST_CHANNEL: &str = "test-channel";

    #[tokio::test]
    async fn test_skill_activate_records_failed_activation_event() {
        let skills = Arc::new(SkillRuntime::default());
        let store = Arc::new(SessionStore::in_memory().unwrap());
        store.ensure_channel_session(TEST_CHANNEL, "chat-1").unwrap();
        let tool = SkillActivateTool::new(skills, store.clone());
        let context = ToolContext {
            session_id: Some(format!("{}:chat-1", TEST_CHANNEL)),
            ..ToolContext::default()
        };

        let result = tool
            .execute_with_context(&context, json!({ "name": "demo" }))
            .await
            .unwrap();

        assert!(!result.success);
        assert!(result.error.unwrap().contains("not found"));

        let events = store
            .list_skill_events(Some(&format!("{}:chat-1", TEST_CHANNEL)))
            .unwrap();
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].event_type, "activation_failed");
        assert_eq!(events[0].skill_name.as_deref(), Some("demo"));
    }

    #[tokio::test]
    async fn test_skill_activate_handles_null_args() {
        let skills = Arc::new(SkillRuntime::default());
        let store = Arc::new(SessionStore::in_memory().unwrap());
        store.ensure_channel_session(TEST_CHANNEL, "chat-1").unwrap();
        let tool = SkillActivateTool::new(skills, store.clone());
        let context = ToolContext {
            session_id: Some(format!("{}:chat-1", TEST_CHANNEL)),
            ..ToolContext::default()
        };

        let result = tool
            .execute_with_context(&context, serde_json::Value::Null)
            .await
            .unwrap();

        assert!(!result.success);
        assert!(result.error.unwrap().contains("Missing required parameters"));
    }
}
