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 SheetsWriteTool {
    client: Arc<GoogleApiClient>,
}

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

    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "google_sheets_write".into(),
            description: "Write (overwrite) cell values in a Google Spreadsheet range.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "spreadsheet_id": {
                        "type": "string",
                        "description": "Google Spreadsheet ID"
                    },
                    "range": {
                        "type": "string",
                        "description": "Cell range in A1 notation (e.g., 'Sheet1!A1:B3')"
                    },
                    "values": {
                        "type": "array",
                        "items": {
                            "type": "array",
                            "items": {"type": "string"}
                        },
                        "description": "2D array of cell values, e.g., [[\"a\",\"b\"],[\"c\",\"d\"]]"
                    }
                },
                "required": ["spreadsheet_id", "range", "values"]
            }),
        }
    }

    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 values = match arguments.get("values").and_then(|v| v.as_array()) {
            Some(v) => v.clone(),
            None => return Ok(ToolOutput::error("Missing required parameter: values (must be a 2D array)")),
        };

        let url = format!("{}/{}/values/{}", SHEETS_URL, spreadsheet_id, range);
        let body = json!({
            "range": range,
            "majorDimension": "ROWS",
            "values": values,
        });

        match self
            .client
            .put(&url, &[("valueInputOption", "USER_ENTERED")], &body)
            .await
        {
            Ok(data) => {
                let updated_range = data["updatedRange"].as_str().unwrap_or(range);
                let updated_rows = data["updatedRows"].as_u64().unwrap_or(0);
                let updated_cols = data["updatedColumns"].as_u64().unwrap_or(0);
                let updated_cells = data["updatedCells"].as_u64().unwrap_or(0);

                let output = format!(
                    "Written to {}\n  Rows: {}, Columns: {}, Cells: {}",
                    updated_range, updated_rows, updated_cols, updated_cells
                );

                Ok(ToolOutput::success(output).with_metadata(json!({
                    "spreadsheet_id": spreadsheet_id,
                    "updated_range": updated_range,
                    "updated_rows": updated_rows,
                    "updated_columns": updated_cols,
                    "updated_cells": updated_cells,
                })))
            }
            Err(e) => Ok(ToolOutput::error(format!("Sheets write 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 = SheetsWriteTool::new(client);
        assert_eq!(tool.name(), "google_sheets_write");
        let def = tool.definition();
        assert_eq!(def.name, "google_sheets_write");
    }

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

    #[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 = SheetsWriteTool::new(client);
        let result = tool
            .execute("c1", json!({"range": "A1:B2", "values": [["a"]]}), None)
            .await
            .unwrap();
        assert!(result.is_error);
        assert!(result.content.contains("spreadsheet_id"));
    }

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

        Mock::given(method("PUT"))
            .and(path("/v4/spreadsheets/abc123/values/Sheet1!A1:B2"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "updatedRange": "Sheet1!A1:B2",
                "updatedRows": 2,
                "updatedColumns": 2,
                "updatedCells": 4
            })))
            .mount(&server)
            .await;

        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let body = json!({
            "range": "Sheet1!A1:B2",
            "majorDimension": "ROWS",
            "values": [["a", "b"], ["c", "d"]],
        });
        let result = client
            .put(
                &format!("{}/v4/spreadsheets/abc123/values/Sheet1!A1:B2", server.uri()),
                &[("valueInputOption", "USER_ENTERED")],
                &body,
            )
            .await
            .unwrap();
        assert_eq!(result["updatedCells"], 4);
    }

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