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 SHEETS_URL: &str = "https://sheets.googleapis.com/v4/spreadsheets";

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

impl SheetsReadTool {
    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 SheetsReadTool {
    fn name(&self) -> &str {
        "google_sheets_read"
    }

    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "google_sheets_read".into(),
            description: "Read cell values from a Google Spreadsheet. Returns rows and columns as a formatted table.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "spreadsheet_id": {
                        "type": "string",
                        "description": "Google Spreadsheet ID (from the URL)"
                    },
                    "range": {
                        "type": "string",
                        "description": "Cell range in A1 notation (e.g., 'Sheet1!A1:D10', 'A1:B5')"
                    }
                },
                "required": ["spreadsheet_id", "range"]
            }),
        }
    }

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

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

        let url = format!("{}/{}/values/{}", SHEETS_URL, spreadsheet_id, range);

        match self.client.get(&url, &[]).await {
            Ok(data) => {
                let actual_range = data["range"].as_str().unwrap_or(range);
                let values = data["values"].as_array();

                let mut output = format!("Range: {}\n\n", actual_range);

                match values {
                    Some(rows) if !rows.is_empty() => {
                        output.push_str(&format!("{} rows returned:\n", rows.len()));
                        for (i, row) in rows.iter().enumerate() {
                            if let Some(cells) = row.as_array() {
                                let cell_strs: Vec<&str> = cells
                                    .iter()
                                    .map(|c| c.as_str().unwrap_or(""))
                                    .collect();
                                output.push_str(&format!(
                                    "  Row {}: {}\n",
                                    i + 1,
                                    cell_strs.join(" | ")
                                ));
                            }
                        }
                    }
                    _ => {
                        output.push_str("No data found in the specified range.");
                    }
                }

                Ok(ToolOutput::success(output).with_metadata(json!({
                    "spreadsheet_id": spreadsheet_id,
                    "range": actual_range,
                    "row_count": values.map(|v| v.len()).unwrap_or(0),
                })))
            }
            Err(e) => Ok(ToolOutput::error(format!("Sheets read 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 = SheetsReadTool::new(client);
        assert_eq!(tool.name(), "google_sheets_read");
        let def = tool.definition();
        assert_eq!(def.name, "google_sheets_read");
        assert!(def.description.contains("Spreadsheet"));
    }

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

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

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

        Mock::given(method("GET"))
            .and(path("/v4/spreadsheets/abc123/values/Sheet1!A1:B3"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "range": "Sheet1!A1:B3",
                "values": [
                    ["Name", "Score"],
                    ["Alice", "95"],
                    ["Bob", "87"]
                ]
            })))
            .mount(&server)
            .await;

        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let result = client
            .get(
                &format!("{}/v4/spreadsheets/abc123/values/Sheet1!A1:B3", server.uri()),
                &[],
            )
            .await
            .unwrap();
        let values = result["values"].as_array().unwrap();
        assert_eq!(values.len(), 3);
    }

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