// Text Transport (for file-based tool definitions and execution)
use crate::providers::base::Provider;
use crate::providers::text::TextProvider;
use crate::tools::Tool;
use crate::transports::{stream::StreamResult, ClientTransport};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::fs;
use tokio::process::Command;

pub struct TextTransport {
    base_path: Option<PathBuf>,
}

impl TextTransport {
    pub fn new() -> Self {
        Self { base_path: None }
    }

    pub fn with_base_path(mut self, path: PathBuf) -> Self {
        self.base_path = Some(path);
        self
    }

    async fn load_tools_from_file(&self, path: &PathBuf) -> Result<Vec<Tool>> {
        let contents = fs::read_to_string(path).await?;

        // Try to parse as JSON array of tools
        if let Ok(tools) = serde_json::from_str::<Vec<Tool>>(&contents) {
            return Ok(tools);
        }

        // Try to parse as UTCP manifest
        if let Ok(manifest) = serde_json::from_str::<Value>(&contents) {
            if let Some(tools_array) = manifest.get("tools").and_then(|v| v.as_array()) {
                let mut tools = Vec::new();
                for tool_value in tools_array {
                    if let Ok(tool) = serde_json::from_value::<Tool>(tool_value.clone()) {
                        tools.push(tool);
                    }
                }
                return Ok(tools);
            }
        }

        Ok(vec![])
    }
}

#[async_trait]
impl ClientTransport for TextTransport {
    async fn register_tool_provider(&self, _prov: &dyn Provider) -> Result<Vec<Tool>> {
        // Load tools from text file
        let base_path = _prov
            .as_any()
            .downcast_ref::<TextProvider>()
            .and_then(|p| p.base_path.clone())
            .or_else(|| self.base_path.clone());

        if let Some(base_path) = base_path {
            let tools_file = base_path.join("tools.json");
            if tools_file.exists() {
                return self.load_tools_from_file(&tools_file).await;
            }
        }
        Ok(vec![])
    }

    async fn deregister_tool_provider(&self, _prov: &dyn Provider) -> Result<()> {
        Ok(())
    }

    async fn call_tool(
        &self,
        tool_name: &str,
        args: HashMap<String, Value>,
        _prov: &dyn Provider,
    ) -> Result<Value> {
        // Text transport might execute tools by:
        // 1. Looking up tool script in base_path
        // 2. Executing the script with args
        // 3. Returning the result

        let base_path = _prov
            .as_any()
            .downcast_ref::<TextProvider>()
            .and_then(|p| p.base_path.clone())
            .or_else(|| self.base_path.clone());

        if let Some(base_path) = base_path {
            let tool_script = base_path.join(format!("{}.js", tool_name));

            if tool_script.exists() {
                // Execute script with JSON args as stdin
                let args_json = serde_json::to_string(&args)?;

                let output = Command::new("node")
                    .arg(tool_script)
                    .arg(args_json) // Pass args as a command-line argument
                    .stdin(std::process::Stdio::null()) // No stdin needed
                    .stdout(std::process::Stdio::piped())
                    .stderr(std::process::Stdio::piped())
                    .spawn()?
                    .wait_with_output()
                    .await?;
                if output.status.success() {
                    let result_str = String::from_utf8_lossy(&output.stdout);
                    if let Ok(result) = serde_json::from_str::<Value>(&result_str) {
                        return Ok(result);
                    }
                    return Ok(Value::String(result_str.to_string()));
                } else {
                    let error = String::from_utf8_lossy(&output.stderr);
                    return Err(anyhow!("Tool execution failed: {}", error));
                }
            }
        }

        Err(anyhow!(
            "Text transport requires base_path configuration and tool script: {}",
            tool_name
        ))
    }

    async fn call_tool_stream(
        &self,
        _tool_name: &str,
        _args: HashMap<String, Value>,
        _prov: &dyn Provider,
    ) -> Result<Box<dyn StreamResult>> {
        Err(anyhow!("Streaming not supported by Text transport"))
    }
}

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

    #[tokio::test]
    async fn test_text_transport_call_tool() {
        // Create a temporary directory
        let temp_dir = tempdir().unwrap();
        let base_path = temp_dir.path().to_path_buf();

        // Create a tool script
        let tool_script_path = base_path.join("test_tool.js");
        let mut tool_script_file = File::create(&tool_script_path).unwrap();
        writeln!(
            tool_script_file,
            "{}",
            r#"
            const args = JSON.parse(process.argv[2]);
            const result = { message: `Hello, ${args.name}!` };
            console.log(JSON.stringify(result));
            "#
        )
        .unwrap();

        // Make the script executable (on Unix-like systems)
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut permissions = std::fs::metadata(&tool_script_path).unwrap().permissions();
            permissions.set_mode(0o755); // rwxr-xr-x
            std::fs::set_permissions(&tool_script_path, permissions).unwrap();
        }

        // Create a TextTransport with the base path
        let transport = TextTransport::new().with_base_path(base_path.clone());

        // Define tool arguments
        let mut args = HashMap::new();
        args.insert("name".to_string(), Value::String("World".to_string()));

        // Call the tool
        let result = transport
            .call_tool("test_tool", args, &MockProvider)
            .await
            .unwrap();

        // Assert the result
        assert_eq!(result["message"], "Hello, World!");

        // Clean up the temporary directory
        temp_dir.close().unwrap();
    }

    // Mock Provider for testing
    #[derive(Debug)]
    struct MockProvider;

    impl Provider for MockProvider {
        fn type_(&self) -> crate::providers::base::ProviderType {
            crate::providers::base::ProviderType::Http
        }

        fn name(&self) -> String {
            "mock".to_string()
        }

        fn as_any(&self) -> &dyn std::any::Any {
            self
        }
    }
}
