use std::sync::Arc;

use async_trait::async_trait;

use crate::security::policy::{SecurityPolicy, ValidationResult};
use crate::{Result, ZeniiError};

use super::traits::{Tool, ToolResult};

pub struct ShellTool {
    policy: Arc<SecurityPolicy>,
    timeout_secs: u64,
}

impl ShellTool {
    pub fn new(policy: Arc<SecurityPolicy>, timeout_secs: u64) -> Self {
        Self {
            policy,
            timeout_secs,
        }
    }
}

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

    fn risk_level(&self) -> crate::security::RiskLevel {
        crate::security::RiskLevel::High
    }

    fn description(&self) -> &str {
        "Execute a shell command"
    }

    fn parameters_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "command": { "type": "string", "description": "The shell command to execute" }
            },
            "required": ["command"]
        })
    }

    fn needs_approval(&self, args: &serde_json::Value) -> Option<String> {
        let command = args.get("command").and_then(|v| v.as_str())?;
        match self.policy.validate_command(command) {
            ValidationResult::NeedsApproval => Some(format!("Command needs approval: {command}")),
            _ => None,
        }
    }

    async fn execute(&self, args: serde_json::Value) -> Result<ToolResult> {
        let command = args
            .get("command")
            .and_then(|v| v.as_str())
            .ok_or_else(|| ZeniiError::Tool("missing 'command' argument".into()))?;

        match self.policy.validate_command(command) {
            ValidationResult::Allowed | ValidationResult::NeedsApproval => {}
            ValidationResult::Denied(reason) => {
                return Ok(ToolResult::err(format!("Denied: {reason}")));
            }
        }

        let output = tokio::time::timeout(std::time::Duration::from_secs(self.timeout_secs), {
            #[cfg(unix)]
            {
                tokio::process::Command::new("sh")
                    .arg("-c")
                    .arg(command)
                    .output()
            }
            #[cfg(windows)]
            {
                tokio::process::Command::new("cmd")
                    .args(["/C", command])
                    .output()
            }
        })
        .await
        .map_err(|_| ZeniiError::Tool("command timed out".into()))?
        .map_err(|e| ZeniiError::Tool(format!("command failed: {e}")))?;

        let stdout = String::from_utf8_lossy(&output.stdout).to_string();
        let stderr = String::from_utf8_lossy(&output.stderr).to_string();

        if output.status.success() {
            Ok(ToolResult::ok(stdout))
        } else {
            Ok(ToolResult::err(
                format!("{stdout}\n{stderr}").trim().to_string(),
            ))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::security::policy::AutonomyLevel;

    fn policy(level: AutonomyLevel) -> Arc<SecurityPolicy> {
        Arc::new(SecurityPolicy::new(level, None, vec![], 60, 60, 100))
    }

    #[tokio::test]
    async fn echo_succeeds() {
        let tool = ShellTool::new(policy(AutonomyLevel::Full), 30);
        let result = tool
            .execute(serde_json::json!({"command": "echo hello"}))
            .await
            .unwrap();
        assert!(result.success);
        assert!(result.output.contains("hello"));
    }

    #[tokio::test]
    async fn missing_command_arg_errors() {
        let tool = ShellTool::new(policy(AutonomyLevel::Full), 30);
        let result = tool.execute(serde_json::json!({})).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn blocked_by_readonly() {
        let tool = ShellTool::new(policy(AutonomyLevel::ReadOnly), 30);
        let result = tool
            .execute(serde_json::json!({"command": "mkdir /tmp/test"}))
            .await
            .unwrap();
        assert!(!result.success);
    }

    #[tokio::test]
    async fn blocked_rm_always() {
        let tool = ShellTool::new(policy(AutonomyLevel::Full), 30);
        let result = tool
            .execute(serde_json::json!({"command": "rm -rf /"}))
            .await
            .unwrap();
        assert!(!result.success);
        assert!(result.output.contains("Denied"));
    }

    #[tokio::test]
    async fn shell_executes_on_current_os() {
        let tool = ShellTool::new(policy(AutonomyLevel::Full), 30);
        let result = tool
            .execute(serde_json::json!({"command": "echo cross-platform"}))
            .await
            .unwrap();
        assert!(result.success);
        assert!(result.output.contains("cross-platform"));
    }

    #[test]
    fn schema_is_valid_json_object() {
        let tool = ShellTool::new(
            Arc::new(SecurityPolicy::new(
                AutonomyLevel::Supervised,
                None,
                vec![],
                60,
                60,
                100,
            )),
            30,
        );
        let schema = tool.parameters_schema();
        assert!(schema.is_object());
        assert!(schema.get("properties").is_some());
    }

    // TA.9 — needs_approval returns Some for medium-risk commands when autonomy is Supervised
    #[test]
    fn needs_approval_supervised_medium_risk() {
        use crate::tools::traits::Tool;

        let tool = ShellTool::new(policy(AutonomyLevel::Supervised), 30);
        let result = tool.needs_approval(&serde_json::json!({"command": "cargo build"}));
        assert!(
            result.is_some(),
            "Medium-risk command in Supervised mode should need approval"
        );
        assert!(result.unwrap().contains("needs approval"));
    }

    // TA.10 — needs_approval returns None for simple commands when autonomy is Full
    #[test]
    fn needs_approval_full_simple_command() {
        use crate::tools::traits::Tool;

        let tool = ShellTool::new(policy(AutonomyLevel::Full), 30);
        let result = tool.needs_approval(&serde_json::json!({"command": "echo hello"}));
        assert!(
            result.is_none(),
            "Low-risk command in Full mode should not need approval"
        );
    }
}
