use std::sync::Arc;

use async_trait::async_trait;
use base64::Engine;
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 GmailReadTool {
    client: Arc<GoogleApiClient>,
}

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

/// Decode base64url-encoded Gmail body data to UTF-8 string
pub(crate) fn decode_base64url(encoded: &str) -> Option<String> {
    let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
    let bytes = engine.decode(encoded).ok()?;
    String::from_utf8(bytes).ok()
}

/// Extract body text from a Gmail message payload.
/// Handles both single-part and multipart messages.
/// Prefers text/plain, falls back to text/html.
pub(crate) fn extract_body(payload: &serde_json::Value) -> Option<String> {
    // Single-part message
    if let Some(data) = payload["body"]["data"].as_str() {
        if !data.is_empty() {
            return decode_base64url(data);
        }
    }

    // Multipart message — search parts recursively
    if let Some(parts) = payload["parts"].as_array() {
        // First pass: look for text/plain
        for part in parts {
            let mime = part["mimeType"].as_str().unwrap_or("");
            if mime == "text/plain" {
                if let Some(data) = part["body"]["data"].as_str() {
                    return decode_base64url(data);
                }
            }
            // Recurse into nested multipart
            if mime.starts_with("multipart/") {
                if let Some(body) = extract_body(part) {
                    return Some(body);
                }
            }
        }

        // Second pass: fall back to text/html
        for part in parts {
            let mime = part["mimeType"].as_str().unwrap_or("");
            if mime == "text/html" {
                if let Some(data) = part["body"]["data"].as_str() {
                    return decode_base64url(data);
                }
            }
        }
    }

    None
}

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 GmailReadTool {
    fn name(&self) -> &str {
        "google_gmail_read"
    }

    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "google_gmail_read".into(),
            description: "Read a Gmail message by its ID. Returns full message body with headers. Use google_gmail_search first to find message IDs.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "message_id": {
                        "type": "string",
                        "description": "Gmail message ID (from google_gmail_search results)"
                    }
                },
                "required": ["message_id"]
            }),
        }
    }

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

        let url = format!("{}/{}", GMAIL_MESSAGES_URL, message_id);

        match self.client.get(&url, &[("format", "full")]).await {
            Ok(data) => {
                let headers = data["payload"]["headers"]
                    .as_array()
                    .map(|a| a.as_slice())
                    .unwrap_or(&[]);

                let from = extract_header(headers, "From");
                let to = extract_header(headers, "To");
                let subject = extract_header(headers, "Subject");
                let date = extract_header(headers, "Date");

                let body = extract_body(&data["payload"]).unwrap_or_else(|| {
                    data["snippet"]
                        .as_str()
                        .unwrap_or("(no body)")
                        .to_string()
                });

                let mut output = format!("Subject: {}\n", subject);
                output.push_str(&format!("From: {}\n", from));
                output.push_str(&format!("To: {}\n", to));
                output.push_str(&format!("Date: {}\n", date));
                output.push_str(&format!("\n{}\n", body));

                Ok(ToolOutput::success(output).with_metadata(json!({
                    "message_id": message_id,
                    "body_length": body.len(),
                })))
            }
            Err(e) => Ok(ToolOutput::error(format!("Gmail read failed: {}", e))),
        }
    }
}

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

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

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

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

    #[tokio::test]
    async fn decode_base64url_plain_text() {
        // "Hello, world!" in base64url
        let encoded = "SGVsbG8sIHdvcmxkIQ";
        assert_eq!(decode_base64url(encoded), Some("Hello, world!".to_string()));
    }

    #[tokio::test]
    async fn decode_base64url_utf8() {
        // UTF-8 text with special chars
        let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
        let encoded = base64::Engine::encode(&engine, "Héllo wörld! 🌍");
        assert_eq!(
            decode_base64url(&encoded),
            Some("Héllo wörld! 🌍".to_string())
        );
    }

    #[tokio::test]
    async fn extract_body_single_part() {
        let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
        let body_data = base64::Engine::encode(&engine, "This is the body");
        let payload = json!({
            "body": {"data": body_data},
            "mimeType": "text/plain"
        });
        assert_eq!(
            extract_body(&payload),
            Some("This is the body".to_string())
        );
    }

    #[tokio::test]
    async fn extract_body_multipart_prefers_plain() {
        let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
        let plain_data = base64::Engine::encode(&engine, "Plain text body");
        let html_data = base64::Engine::encode(&engine, "<html>HTML body</html>");
        let payload = json!({
            "mimeType": "multipart/alternative",
            "body": {"size": 0},
            "parts": [
                {
                    "mimeType": "text/plain",
                    "body": {"data": plain_data}
                },
                {
                    "mimeType": "text/html",
                    "body": {"data": html_data}
                }
            ]
        });
        assert_eq!(
            extract_body(&payload),
            Some("Plain text body".to_string())
        );
    }

    #[tokio::test]
    async fn extract_body_multipart_falls_back_to_html() {
        let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
        let html_data = base64::Engine::encode(&engine, "<html>HTML only</html>");
        let payload = json!({
            "mimeType": "multipart/alternative",
            "body": {"size": 0},
            "parts": [
                {
                    "mimeType": "text/html",
                    "body": {"data": html_data}
                }
            ]
        });
        assert_eq!(
            extract_body(&payload),
            Some("<html>HTML only</html>".to_string())
        );
    }
}
