use async_trait::async_trait;
use enact_core::providers::{
    ChatChoice, ChatMessage, ChatRequest, ChatResponse, ModelCapabilities, ModelProvider,
};
use serde::{Deserialize, Serialize};

/// ZeroClaw-style tool specification used by provider-side tool conversion.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSpec {
    pub name: String,
    pub description: String,
    pub parameters: serde_json::Value,
}

/// ZeroClaw-style tool call shape.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
    pub id: String,
    pub name: String,
    pub arguments: String,
}

/// ZeroClaw-style chat response shape.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BridgeChatResponse {
    pub text: Option<String>,
    pub tool_calls: Vec<ToolCall>,
}

/// Provider-native tool payloads, ported from ZeroClaw semantics.
#[derive(Debug, Clone)]
pub enum ToolsPayload {
    Gemini {
        function_declarations: Vec<serde_json::Value>,
    },
    Anthropic {
        tools: Vec<serde_json::Value>,
    },
    OpenAI {
        tools: Vec<serde_json::Value>,
    },
    PromptGuided {
        instructions: String,
    },
}

/// ZeroClaw-compatible provider contract.
#[async_trait]
pub trait ZeroClawProvider: Send + Sync {
    fn provider_name(&self) -> &str;
    fn model_name(&self) -> &str;

    fn supports_native_tools(&self) -> bool {
        false
    }

    fn convert_tools(&self, tools: &[ToolSpec]) -> ToolsPayload {
        ToolsPayload::PromptGuided {
            instructions: build_tool_instructions_text(tools),
        }
    }

    async fn chat_with_history(
        &self,
        messages: &[ChatMessage],
        temperature: f32,
    ) -> anyhow::Result<BridgeChatResponse>;
}

/// Bridge adapter that exposes a ZeroClaw-style provider as Enact `ModelProvider`.
pub struct BridgeProvider<P> {
    inner: P,
}

impl<P> BridgeProvider<P> {
    pub fn new(inner: P) -> Self {
        Self { inner }
    }
}

#[async_trait]
impl<P> ModelProvider for BridgeProvider<P>
where
    P: ZeroClawProvider,
{
    fn name(&self) -> &str {
        self.inner.provider_name()
    }

    fn model(&self) -> &str {
        self.inner.model_name()
    }

    fn capabilities(&self) -> ModelCapabilities {
        ModelCapabilities {
            supports_tools: self.inner.supports_native_tools(),
            ..ModelCapabilities::default()
        }
    }

    async fn chat(&self, request: ChatRequest) -> anyhow::Result<ChatResponse> {
        let bridged = self
            .inner
            .chat_with_history(&request.messages, request.temperature.unwrap_or(0.7))
            .await?;

        // Preserve tool-calling semantics by encoding tool calls into content.
        // This keeps compatibility with existing parser-driven runner loops.
        let content = if bridged.tool_calls.is_empty() {
            bridged.text.unwrap_or_default()
        } else {
            serde_json::json!({
                "content": bridged.text.unwrap_or_default(),
                "tool_calls": bridged.tool_calls,
            })
            .to_string()
        };

        Ok(ChatResponse {
            id: format!("bridge-{}", uuid::Uuid::new_v4()),
            choices: vec![ChatChoice {
                index: 0,
                message: ChatMessage::assistant(content),
                finish_reason: Some("stop".to_string()),
            }],
            usage: None,
        })
    }
}

/// Build prompt-guided fallback instructions for tool usage.
pub fn build_tool_instructions_text(tools: &[ToolSpec]) -> String {
    let mut instructions = String::new();
    instructions.push_str("## Tool Use Protocol\n\n");
    instructions.push_str("To use a tool, wrap JSON in <tool_call></tool_call> tags:\n\n");
    instructions.push_str("<tool_call>\n");
    instructions.push_str("{\"name\": \"tool_name\", \"arguments\": {\"param\": \"value\"}}\n");
    instructions.push_str("</tool_call>\n\n");
    instructions.push_str("### Available Tools\n\n");

    for tool in tools {
        instructions.push_str(&format!("**{}**: {}\n", tool.name, tool.description));
        let parameters =
            serde_json::to_string(&tool.parameters).unwrap_or_else(|_| "{}".to_string());
        instructions.push_str(&format!("Parameters: `{parameters}`\n\n"));
    }

    instructions
}

/// Convert tool schema to Gemini `functionDeclarations` shape.
pub fn convert_tools_for_gemini(tools: &[ToolSpec]) -> Vec<serde_json::Value> {
    tools
        .iter()
        .map(|tool| {
            serde_json::json!({
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.parameters,
            })
        })
        .collect()
}

/// Convert tool schema to Anthropic `input_schema` shape.
pub fn convert_tools_for_anthropic(tools: &[ToolSpec]) -> Vec<serde_json::Value> {
    tools
        .iter()
        .map(|tool| {
            serde_json::json!({
                "name": tool.name,
                "description": tool.description,
                "input_schema": tool.parameters,
            })
        })
        .collect()
}

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

    #[test]
    fn gemini_tool_conversion_maps_fields() {
        let tools = vec![ToolSpec {
            name: "search".to_string(),
            description: "Search docs".to_string(),
            parameters: serde_json::json!({"type": "object"}),
        }];

        let payload = convert_tools_for_gemini(&tools);
        assert_eq!(payload.len(), 1);
        assert_eq!(payload[0]["name"], "search");
        assert_eq!(payload[0]["description"], "Search docs");
    }

    #[test]
    fn anthropic_tool_conversion_uses_input_schema() {
        let tools = vec![ToolSpec {
            name: "shell".to_string(),
            description: "Run command".to_string(),
            parameters: serde_json::json!({"type": "object"}),
        }];

        let payload = convert_tools_for_anthropic(&tools);
        assert_eq!(payload.len(), 1);
        assert!(payload[0].get("input_schema").is_some());
    }

    #[test]
    fn prompt_tool_instructions_include_tool_names() {
        let tools = vec![ToolSpec {
            name: "read_file".to_string(),
            description: "Read files".to_string(),
            parameters: serde_json::json!({"type": "object"}),
        }];

        let text = build_tool_instructions_text(&tools);
        assert!(text.contains("read_file"));
        assert!(text.contains("Tool Use Protocol"));
    }
}
