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 CALENDAR_EVENTS_URL: &str = "https://www.googleapis.com/calendar/v3/calendars";

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

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

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

    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "google_calendar_create".into(),
            description: "Create a new event on a Google Calendar.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "summary": {
                        "type": "string",
                        "description": "Event title"
                    },
                    "start": {
                        "type": "string",
                        "description": "Start time in RFC3339 format (e.g., 2026-01-15T09:00:00-05:00)"
                    },
                    "end": {
                        "type": "string",
                        "description": "End time in RFC3339 format"
                    },
                    "description": {
                        "type": "string",
                        "description": "Event description"
                    },
                    "location": {
                        "type": "string",
                        "description": "Event location"
                    },
                    "attendees": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "List of attendee email addresses"
                    },
                    "calendar_id": {
                        "type": "string",
                        "description": "Calendar ID (default: 'primary')"
                    }
                },
                "required": ["summary", "start", "end"]
            }),
        }
    }

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

        let start = match arguments.get("start").and_then(|v| v.as_str()) {
            Some(s) => s,
            None => return Ok(ToolOutput::error("Missing required parameter: start")),
        };

        let end = match arguments.get("end").and_then(|v| v.as_str()) {
            Some(s) => s,
            None => return Ok(ToolOutput::error("Missing required parameter: end")),
        };

        let calendar_id = arguments
            .get("calendar_id")
            .and_then(|v| v.as_str())
            .unwrap_or("primary");

        let mut event_body = json!({
            "summary": summary,
            "start": {"dateTime": start},
            "end": {"dateTime": end},
        });

        if let Some(desc) = arguments.get("description").and_then(|v| v.as_str()) {
            event_body["description"] = json!(desc);
        }

        if let Some(loc) = arguments.get("location").and_then(|v| v.as_str()) {
            event_body["location"] = json!(loc);
        }

        if let Some(attendees) = arguments.get("attendees").and_then(|v| v.as_array()) {
            let attendee_objs: Vec<serde_json::Value> = attendees
                .iter()
                .filter_map(|a| a.as_str())
                .map(|email| json!({"email": email}))
                .collect();
            event_body["attendees"] = json!(attendee_objs);
        }

        let url = format!("{}/{}/events", CALENDAR_EVENTS_URL, calendar_id);

        match self.client.post(&url, &event_body).await {
            Ok(data) => {
                let event_id = data["id"].as_str().unwrap_or("unknown");
                let html_link = data["htmlLink"].as_str().unwrap_or("");

                let mut output = format!("Event created: {}\n", summary);
                output.push_str(&format!("  ID: {}\n", event_id));
                output.push_str(&format!("  Start: {}\n", start));
                output.push_str(&format!("  End: {}\n", end));
                if !html_link.is_empty() {
                    output.push_str(&format!("  Link: {}\n", html_link));
                }

                Ok(ToolOutput::success(output).with_metadata(json!({
                    "event_id": event_id,
                    "calendar_id": calendar_id,
                })))
            }
            Err(e) => Ok(ToolOutput::error(format!("Calendar create failed: {}", e))),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::auth::MockGoogleAuth;
    use wiremock::matchers::{method, path};
    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 = CalendarCreateTool::new(client);
        assert_eq!(tool.name(), "google_calendar_create");
        let def = tool.definition();
        assert_eq!(def.name, "google_calendar_create");
        assert!(def.input_schema["required"].as_array().unwrap().len() == 3);
    }

    #[tokio::test]
    async fn missing_summary_returns_error() {
        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let tool = CalendarCreateTool::new(client);

        let result = tool
            .execute("c1", json!({"start": "2026-01-15T09:00:00Z", "end": "2026-01-15T10:00:00Z"}), None)
            .await
            .unwrap();
        assert!(result.is_error);
        assert!(result.content.contains("summary"));
    }

    #[tokio::test]
    async fn missing_start_returns_error() {
        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let tool = CalendarCreateTool::new(client);

        let result = tool
            .execute("c1", json!({"summary": "Test", "end": "2026-01-15T10:00:00Z"}), None)
            .await
            .unwrap();
        assert!(result.is_error);
        assert!(result.content.contains("start"));
    }

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

        Mock::given(method("POST"))
            .and(path("/calendar/v3/calendars/primary/events"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "id": "evt-123",
                "summary": "Meeting",
                "htmlLink": "https://calendar.google.com/event?id=evt-123",
                "start": {"dateTime": "2026-01-15T09:00:00Z"},
                "end": {"dateTime": "2026-01-15T10:00:00Z"}
            })))
            .mount(&server)
            .await;

        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let result = client
            .post(
                &format!("{}/calendar/v3/calendars/primary/events", server.uri()),
                &json!({"summary": "Meeting", "start": {"dateTime": "2026-01-15T09:00:00Z"}, "end": {"dateTime": "2026-01-15T10:00:00Z"}}),
            )
            .await
            .unwrap();
        assert_eq!(result["id"], "evt-123");
    }

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