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

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

    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "google_sheets_append".into(),
            description: "Append rows to a Google Spreadsheet. New rows are inserted after the last row with data.".into(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "spreadsheet_id": {
                        "type": "string",
                        "description": "Google Spreadsheet ID"
                    },
                    "range": {
                        "type": "string",
                        "description": "Target range in A1 notation (e.g., 'Sheet1!A:D'). Data is appended after the last row."
                    },
                    "values": {
                        "type": "array",
                        "items": {
                            "type": "array",
                            "items": {"type": "string"}
                        },
                        "description": "2D array of row values to append, e.g., [[\"new\",\"row\"]]"
                    }
                },
                "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/{}:append",
            SHEETS_URL, spreadsheet_id, range
        );

        let body = json!({
            "range": range,
            "majorDimension": "ROWS",
            "values": values,
        });

        // Append uses POST with query params for input option and insert mode
        let query = [
            ("valueInputOption", "USER_ENTERED"),
            ("insertDataOption", "INSERT_ROWS"),
        ];

        // Build the request manually since we need POST with query params
        match self.client.post_with_query(&url, &query, &body).await {
            Ok(data) => {
                let updates = &data["updates"];
                let updated_range = updates["updatedRange"].as_str().unwrap_or(range);
                let updated_rows = updates["updatedRows"].as_u64().unwrap_or(0);
                let updated_cells = updates["updatedCells"].as_u64().unwrap_or(0);

                let output = format!(
                    "Appended to {}\n  Rows added: {}, Cells written: {}",
                    updated_range, updated_rows, updated_cells
                );

                Ok(ToolOutput::success(output).with_metadata(json!({
                    "spreadsheet_id": spreadsheet_id,
                    "updated_range": updated_range,
                    "updated_rows": updated_rows,
                    "updated_cells": updated_cells,
                })))
            }
            Err(e) => Ok(ToolOutput::error(format!("Sheets append 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 = SheetsAppendTool::new(client);
        assert_eq!(tool.name(), "google_sheets_append");
        let def = tool.definition();
        assert_eq!(def.name, "google_sheets_append");
        assert!(def.description.contains("Append"));
    }

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

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

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

        Mock::given(method("POST"))
            .and(path("/v4/spreadsheets/abc123/values/Sheet1!A:D:append"))
            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
                "updates": {
                    "updatedRange": "Sheet1!A5:D5",
                    "updatedRows": 1,
                    "updatedColumns": 4,
                    "updatedCells": 4
                }
            })))
            .mount(&server)
            .await;

        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let body = json!({
            "range": "Sheet1!A:D",
            "majorDimension": "ROWS",
            "values": [["new", "row", "data", "here"]],
        });
        let result = client
            .post_with_query(
                &format!("{}/v4/spreadsheets/abc123/values/Sheet1!A:D:append", server.uri()),
                &[("valueInputOption", "USER_ENTERED"), ("insertDataOption", "INSERT_ROWS")],
                &body,
            )
            .await
            .unwrap();
        assert_eq!(result["updates"]["updatedRows"], 1);
    }

    #[tokio::test]
    async fn schema_requires_all_fields() {
        let auth = Arc::new(MockGoogleAuth::new("t"));
        let client = Arc::new(GoogleApiClient::new(auth));
        let tool = SheetsAppendTool::new(client);
        let def = tool.definition();
        let required = def.input_schema["required"].as_array().unwrap();
        assert_eq!(required.len(), 3);
    }
}
