use async_trait::async_trait;
use serde_json::{json, Map, Value};
use zeroclaw::tools::{Tool, ToolResult};

use crate::pipe::{Action, BrowserPipeTool, Transport};

pub const BROWSER_ACTION_TOOL_NAME: &str = "browser_action";

pub struct ZeroClawBrowserTool<T: Transport> {
    browser_tool: BrowserPipeTool<T>,
}

impl<T: Transport> ZeroClawBrowserTool<T> {
    pub fn new(browser_tool: BrowserPipeTool<T>) -> Self {
        Self { browser_tool }
    }
}

#[async_trait]
impl<T: Transport + 'static> Tool for ZeroClawBrowserTool<T> {
    fn name(&self) -> &str {
        BROWSER_ACTION_TOOL_NAME
    }

    fn description(&self) -> &str {
        "Execute browser actions in SuperRPA through the existing sgClaw pipe protocol."
    }

    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "required": ["action", "expected_domain"],
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["click", "type", "navigate", "getText"]
                },
                "expected_domain": {
                    "type": "string"
                },
                "selector": {
                    "type": "string"
                },
                "text": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "clear_first": {
                    "type": "boolean"
                }
            }
        })
    }

    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        let request = match parse_browser_action_request(args) {
            Ok(request) => request,
            Err(err) => return Ok(failed_tool_result(err.to_string())),
        };

        let result = match self.browser_tool.invoke(
            request.action,
            request.params,
            &request.expected_domain,
        ) {
            Ok(result) => result,
            Err(err) => return Ok(failed_tool_result(err.to_string())),
        };

        let output = serde_json::to_string(&json!({
            "seq": result.seq,
            "success": result.success,
            "data": result.data,
            "aom_snapshot": result.aom_snapshot,
            "timing": result.timing
        }))?;

        Ok(ToolResult {
            success: result.success,
            output,
            error: (!result.success).then(|| "browser action returned success=false".to_string()),
        })
    }
}

struct BrowserActionRequest {
    action: Action,
    expected_domain: String,
    params: Value,
}

fn parse_browser_action_request(args: Value) -> Result<BrowserActionRequest, BrowserActionAdapterError> {
    let mut args = match args {
        Value::Object(args) => args,
        other => {
            return Err(BrowserActionAdapterError::InvalidArguments(format!(
                "expected object arguments, got {other}"
            )))
        }
    };

    let action_name = take_required_string(&mut args, "action")?;
    let expected_domain = take_required_string(&mut args, "expected_domain")?;
    let action = parse_action(&action_name)?;

    Ok(BrowserActionRequest {
        action,
        expected_domain,
        params: Value::Object(args),
    })
}

fn parse_action(action_name: &str) -> Result<Action, BrowserActionAdapterError> {
    match action_name {
        "click" => Ok(Action::Click),
        "type" => Ok(Action::Type),
        "navigate" => Ok(Action::Navigate),
        "getText" => Ok(Action::GetText),
        other => Err(BrowserActionAdapterError::UnsupportedAction(
            other.to_string(),
        )),
    }
}

fn take_required_string(
    args: &mut Map<String, Value>,
    key: &'static str,
) -> Result<String, BrowserActionAdapterError> {
    match args.remove(key) {
        Some(Value::String(value)) if !value.trim().is_empty() => Ok(value),
        Some(other) => Err(BrowserActionAdapterError::InvalidArguments(format!(
            "{key} must be a non-empty string, got {other}"
        ))),
        None => Err(BrowserActionAdapterError::MissingField(key)),
    }
}

fn failed_tool_result(error: String) -> ToolResult {
    ToolResult {
        success: false,
        output: String::new(),
        error: Some(error),
    }
}

#[derive(Debug, thiserror::Error)]
enum BrowserActionAdapterError {
    #[error("unsupported action: {0}")]
    UnsupportedAction(String),
    #[error("missing required field: {0}")]
    MissingField(&'static str),
    #[error("invalid tool arguments: {0}")]
    InvalidArguments(String),
}
