use std::sync::Arc;

use async_trait::async_trait;
use serde_json::json;
use tokio::sync::mpsc;

use soul_core::error::SoulResult;
use soul_core::tool::{Tool, ToolOutput};
use soul_core::types::ToolDefinition;

use crate::client::GoogleApiClient;

const GMAIL_MESSAGES_URL: &str = "https://www.googleapis.com/gmail/v1/users/me/messages";

pub struct GmailSearchTool {
    client: Arc<GoogleApiClient>,
}

impl GmailSearchTool {
    pub fn new(client: Arc<GoogleApiClient>) -> Self {
        Self { client }
    }
}

fn extract_header<'a>(headers: &'a [serde_json::Value], name: &str) -> &'a str {
    headers
        .iter()
        .find(|h| h["name"].as_str() == Some(name))
        .and_then(|h| h["value"].as_str())
        .unwrap_or("")
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Tool for GmailSearchTool {
    fn name(&self) -> &str {
        "google_gmail_search"
    }

    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "google_gmail_search".into(),
            description: "Search Gmail messages. Returns message summaries (from, subject, snippet). Use google_gmail_read to get full message body.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Gmail search query (same syntax as Gmail search bar, e.g., 'from:user@example.com', 'subject:invoice', 'in:inbox')"
                    },
                    "max_results": {
                        "type": "integer",
                        "description": "Maximum number of messages to return (default: 20)"
                    }
                },
                "required": ["query"]
            }),
        }
    }

    async fn execute(
        &self,
        _call_id: &str,
        arguments: serde_json::Value,
        _partial_tx: Option<mpsc::UnboundedSender<String>>,
    ) -> SoulResult<ToolOutput> {
        let query = match arguments.get("query").and_then(|v| v.as_str()) {
            Some(q) => q,
            None => return Ok(ToolOutput::error("Missing required parameter: query")),
        };

        let max_results = arguments
            .get("max_results")
            .and_then(|v| v.as_u64())
            .unwrap_or(20)
            .to_string();

        // Step 1: Get message IDs
        let list_result = self
            .client
            .get(GMAIL_MESSAGES_URL, &[("q", query), ("maxResults", &max_results)])
            .await;

        let list_data = match list_result {
            Ok(data) => data,
            Err(e) => return Ok(ToolOutput::error(format!("Gmail search failed: {}", e))),
        };

        let message_ids: Vec<&str> = match list_data["messages"].as_array() {
            Some(msgs) => msgs.iter().filter_map(|m| m["id"].as_str()).collect(),
            None => {
                return Ok(ToolOutput::success("No messages found matching your query.").with_metadata(json!({"message_count": 0})));
            }
        };

        if message_ids.is_empty() {
            return Ok(ToolOutput::success("No messages found matching your query.").with_metadata(json!({"message_count": 0})));
        }

        // Step 2: Fetch metadata for each message
        let mut output = format!("Found {} messages:\n\n", message_ids.len());
        let mut fetched = 0;

        for (i, msg_id) in message_ids.iter().enumerate() {
            let msg_url = format!("{}/{}", GMAIL_MESSAGES_URL, msg_id);
            let msg_result = self
                .client
                .get(&msg_url, &[("format", "metadata"), ("metadataHeaders", "From"), ("metadataHeaders", "Subject"), ("metadataHeaders", "Date")])
                .await;

            match msg_result {
                Ok(msg_data) => {
                    let headers = msg_data["payload"]["headers"]
                        .as_array()
                        .map(|a| a.as_slice())
                        .unwrap_or(&[]);

                    let from = extract_header(headers, "From");
                    let subject = extract_header(headers, "Subject");
                    let date = extract_header(headers, "Date");
                    let snippet = msg_data["snippet"].as_str().unwrap_or("");

                    output.push_str(&format!("{}. [{}] {}\n", i + 1, msg_id, subject));
                    output.push_str(&format!("   From: {}\n", from));
                    output.push_str(&format!("   Date: {}\n", date));
                    if !snippet.is_empty() {
                        output.push_str(&format!("   Preview: {}\n", snippet));
                    }
                    output.push('\n');
                    fetched += 1;
                }
                Err(e) => {
                    output.push_str(&format!("{}. [{}] (failed to fetch: {})\n\n", i + 1, msg_id, e));
                }
            }
        }

        Ok(ToolOutput::success(output).with_metadata(json!({
            "message_count": fetched,
            "query": query,
        })))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::auth::MockGoogleAuth;
    use wiremock::matchers::{method, path, query_param};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[tokio::test]
    async fn tool_name_and_definition() {
        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let tool = GmailSearchTool::new(client);
        assert_eq!(tool.name(), "google_gmail_search");
        let def = tool.definition();
        assert_eq!(def.name, "google_gmail_search");
        assert!(def.description.contains("Gmail"));
    }

    #[tokio::test]
    async fn missing_query_returns_error() {
        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let tool = GmailSearchTool::new(client);
        let result = tool.execute("c1", json!({}), None).await.unwrap();
        assert!(result.is_error);
        assert!(result.content.contains("query"));
    }

    #[tokio::test]
    async fn schema_requires_query() {
        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let tool = GmailSearchTool::new(client);
        let def = tool.definition();
        let required = def.input_schema["required"].as_array().unwrap();
        assert!(required.contains(&json!("query")));
    }

    #[tokio::test]
    async fn handles_no_messages() {
        let server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/gmail/v1/users/me/messages"))
            .and(query_param("q", "from:nobody@test.com"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "resultSizeEstimate": 0
            })))
            .mount(&server)
            .await;

        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let result = client
            .get(
                &format!("{}/gmail/v1/users/me/messages", server.uri()),
                &[("q", "from:nobody@test.com")],
            )
            .await
            .unwrap();
        assert!(result.get("messages").is_none());
    }

    #[tokio::test]
    async fn extract_header_works() {
        let headers = vec![
            json!({"name": "From", "value": "alice@test.com"}),
            json!({"name": "Subject", "value": "Hello"}),
        ];
        assert_eq!(extract_header(&headers, "From"), "alice@test.com");
        assert_eq!(extract_header(&headers, "Subject"), "Hello");
        assert_eq!(extract_header(&headers, "Missing"), "");
    }
}
