use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use async_trait::async_trait;
use reqwest::Url;
use serde_json::{json, Value};
use zeroclaw::skills::{Skill, SkillTool};
use zeroclaw::tools::{Tool, ToolResult};

use crate::browser::BrowserBackend;
use crate::pipe::Action;

pub struct BrowserScriptSkillTool {
    tool_name: String,
    tool_description: String,
    skill_root: PathBuf,
    script_path: PathBuf,
    args: HashMap<String, String>,
    browser_tool: Arc<dyn BrowserBackend>,
}

impl BrowserScriptSkillTool {
    pub fn new(
        skill_name: &str,
        tool: &SkillTool,
        skill_root: &Path,
        browser_tool: Arc<dyn BrowserBackend>,
    ) -> anyhow::Result<Self> {
        let script_path = resolve_browser_script_path(skill_root, &tool.command)?;

        Ok(Self {
            tool_name: format!("{}.{}", skill_name, tool.name),
            tool_description: tool.description.clone(),
            skill_root: skill_root.to_path_buf(),
            script_path,
            args: tool.args.clone(),
            browser_tool,
        })
    }

    fn build_parameters_schema(&self) -> Value {
        let mut properties = serde_json::Map::new();
        let mut required = vec![Value::String("expected_domain".to_string())];

        properties.insert(
            "expected_domain".to_string(),
            json!({
                "type": "string",
                "description": "Bare hostname for the current page, for example www.zhihu.com."
            }),
        );

        for (name, description) in &self.args {
            properties.insert(
                name.clone(),
                json!({
                    "type": "string",
                    "description": description
                }),
            );
            required.push(Value::String(name.clone()));
        }

        json!({
            "type": "object",
            "properties": properties,
            "required": required
        })
    }
}

#[async_trait]
impl Tool for BrowserScriptSkillTool {
    fn name(&self) -> &str {
        &self.tool_name
    }

    fn description(&self) -> &str {
        &self.tool_description
    }

    fn parameters_schema(&self) -> Value {
        self.build_parameters_schema()
    }

    async fn execute(&self, args: Value) -> anyhow::Result<ToolResult> {
        let tool = SkillTool {
            name: self.tool_name.clone(),
            description: self.tool_description.clone(),
            kind: "browser_script".to_string(),
            command: self.script_path.to_string_lossy().into_owned(),
            args: self.args.clone(),
        };

        execute_browser_script_tool(&tool, &self.skill_root, self.browser_tool.as_ref(), args).await
    }
}

pub fn build_browser_script_skill_tools(
    skills: &[Skill],
    browser_tool: Arc<dyn BrowserBackend>,
) -> Result<Vec<Box<dyn Tool>>, anyhow::Error> {
    let mut tools: Vec<Box<dyn Tool>> = Vec::new();

    if !browser_tool.supports_eval() {
        return Ok(tools);
    }

    for skill in skills {
        let Some(location) = skill.location.as_ref() else {
            continue;
        };
        let Some(skill_root) = location.parent() else {
            continue;
        };

        for tool in &skill.tools {
            if tool.kind != "browser_script" {
                continue;
            }
            tools.push(Box::new(BrowserScriptSkillTool::new(
                &skill.name,
                tool,
                skill_root,
                browser_tool.clone(),
            )?));
        }
    }

    Ok(tools)
}

pub async fn execute_browser_script_tool(
    tool: &SkillTool,
    skill_root: &Path,
    browser_tool: &dyn BrowserBackend,
    args: Value,
) -> anyhow::Result<ToolResult> {
    if tool.kind != "browser_script" {
        return Ok(failed_tool_result(format!(
            "browser script tool kind must be browser_script, got {}",
            tool.kind
        )));
    }

    execute_browser_script_impl(tool, skill_root, browser_tool, args)
}

fn execute_browser_script_impl(
    tool: &SkillTool,
    skill_root: &Path,
    browser_tool: &dyn BrowserBackend,
    args: Value,
) -> anyhow::Result<ToolResult> {
    eprintln!("[execute_browser_script_impl] 开始执行");
    eprintln!("[execute_browser_script_impl]   tool.name: {}", tool.name);
    eprintln!("[execute_browser_script_impl]   tool.command: {}", tool.command);
    eprintln!("[execute_browser_script_impl]   skill_root: {:?}", skill_root);
    eprintln!("[execute_browser_script_impl]   args: {:?}", args);
    
    let script_path = resolve_browser_script_path(skill_root, &tool.command)?;
    eprintln!("[execute_browser_script_impl]   script_path: {:?}", script_path);
    
    // 检查脚本文件是否存在
    if !script_path.exists() {
        eprintln!("[execute_browser_script_impl]   脚本文件不存在!");
    } else {
        eprintln!("[execute_browser_script_impl]   脚本文件存在");
    }

    let mut args = match args {
        Value::Object(args) => args,
        other => {
            eprintln!("[execute_browser_script_impl]   args 不是 Object: {:?}", other);
            return Ok(failed_tool_result(format!(
                "expected object arguments, got {other}"
            )))
        }
    };

    let raw_expected_domain = match args.remove("expected_domain") {
        Some(Value::String(value)) if !value.trim().is_empty() => value,
        Some(other) => {
            eprintln!("[execute_browser_script_impl]   expected_domain 格式错误: {:?}", other);
            return Ok(failed_tool_result(format!(
                "expected_domain must be a non-empty string, got {other}"
            )))
        }
        None => {
            eprintln!("[execute_browser_script_impl]   缺少 expected_domain");
            return Ok(failed_tool_result(
                "missing required field expected_domain".to_string(),
            ))
        }
    };
    eprintln!("[execute_browser_script_impl]   raw_expected_domain: {}", raw_expected_domain);
    
    let expected_domain = match normalize_domain_like(&raw_expected_domain) {
        Some(value) => value,
        None => {
            eprintln!("[execute_browser_script_impl]   expected_domain 解析失败");
            return Ok(failed_tool_result(format!(
                "expected_domain must resolve to a hostname, got {raw_expected_domain:?}"
            )))
        }
    };
    eprintln!("[execute_browser_script_impl]   expected_domain: {}", expected_domain);
    args.insert("expected_domain".to_string(), Value::String(expected_domain.clone()));

    for required_arg in tool.args.keys() {
        if !args.contains_key(required_arg) {
            eprintln!("[execute_browser_script_impl]   缺少必需参数: {}", required_arg);
            return Ok(failed_tool_result(format!(
                "missing required field {required_arg}"
            )));
        }
    }

    let script_body = match fs::read_to_string(&script_path) {
        Ok(value) => {
            eprintln!("[execute_browser_script_impl]   脚本读取成功, 长度: {} 字节", value.len());
            value
        }
        Err(err) => {
            eprintln!("[execute_browser_script_impl]   脚本读取失败: {}", err);
            return Ok(failed_tool_result(format!(
                "failed to read browser script {}: {err}",
                script_path.display()
            )))
        }
    };

    let wrapped_script = wrap_browser_script(&script_body, &Value::Object(args.clone()));
    eprintln!("[execute_browser_script_impl]   包装后脚本长度: {} 字节", wrapped_script.len());
    eprintln!("[execute_browser_script_impl]   包装后脚本前500字符: {}", 
        if wrapped_script.len() > 500 { &wrapped_script[..500] } else { &wrapped_script });
    eprintln!("[execute_browser_script_impl]   调用 browser_tool.invoke(Action::Eval)...");
    
    let target_url = format!("http://{}", expected_domain);
    let result = match browser_tool.invoke(
        Action::Eval,
        json!({
            "script": wrapped_script,
            "target_url": target_url,
        }),
        &expected_domain,
    ) {
        Ok(result) => {
            eprintln!("[execute_browser_script_impl]   invoke 成功, result.success: {}", result.success);
            result
        }
        Err(err) => {
            eprintln!("[execute_browser_script_impl]   invoke 失败: {}", err);
            return Ok(failed_tool_result(err.to_string()))
        }
    };

    if !result.success {
        eprintln!("[execute_browser_script_impl]   result.success=false, data: {:?}", result.data);
        return Ok(failed_tool_result(format_browser_script_error(&result.data)));
    }

    let payload = result
        .data
        .get("text")
        .cloned()
        .unwrap_or_else(|| result.data.clone());
    eprintln!("[execute_browser_script_impl]   返回成功, payload 长度: {:?}", payload.to_string().len());
    Ok(ToolResult {
        success: true,
        output: stringify_tool_payload(&payload)?,
        error: None,
    })
}

fn wrap_browser_script(script_body: &str, args: &Value) -> String {
    format!(
        "(function() {{\nconst args = {};\n{}\n}})()",
        serde_json::to_string(args).unwrap_or_else(|_| "{}".to_string()),
        script_body
    )
}

fn resolve_browser_script_path(skill_root: &Path, command: &str) -> anyhow::Result<PathBuf> {
    let script_path = PathBuf::from(command);
    let script_path = if script_path.is_absolute() {
        script_path
    } else {
        skill_root.join(script_path)
    };
    let canonical_skill_root = skill_root
        .canonicalize()
        .unwrap_or_else(|_| skill_root.to_path_buf());
    let canonical_script_path = script_path.canonicalize().map_err(|err| {
        anyhow::anyhow!(
            "failed to resolve browser script {}: {err}",
            script_path.display()
        )
    })?;
    if !canonical_script_path.starts_with(&canonical_skill_root) {
        anyhow::bail!(
            "browser script path escapes skill root: {}",
            canonical_script_path.display()
        );
    }

    Ok(canonical_script_path)
}

fn stringify_tool_payload(payload: &Value) -> anyhow::Result<String> {
    Ok(match payload {
        Value::String(value) => value.clone(),
        Value::Null => "null".to_string(),
        Value::Bool(_) | Value::Number(_) | Value::Array(_) | Value::Object(_) => {
            serde_json::to_string(payload)?
        }
    })
}

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

fn format_browser_script_error(data: &Value) -> String {
    data.get("error")
        .and_then(|value| value.get("message"))
        .and_then(Value::as_str)
        .map(str::to_string)
        .unwrap_or_else(|| format!("browser script failed: {data}"))
}

fn normalize_domain_like(raw: &str) -> Option<String> {
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return None;
    }

    if let Ok(url) = Url::parse(trimmed) {
        return url.host_str().map(|host| host.to_ascii_lowercase());
    }

    let host = trimmed
        .trim_start_matches("https://")
        .trim_start_matches("http://")
        .split(['/', '?', '#'])
        .next()
        .unwrap_or_default()
        .split(':')
        .next()
        .unwrap_or_default()
        .trim()
        .to_ascii_lowercase();

    (!host.is_empty()).then_some(host)
}
