//! download_file AI 工具声明式注册(跨平台 URL → 文件流式下载)。
//!
//! 工具职责:GET 远程 URL → 流式写本地文件(reqwest bytes_stream + tokio::fs)。
//! 替代 run_command 的 curl/wget/Invoke-WebRequest(踩 PowerShell alias/编码/跨平台陷阱)。
//!
//! 风险:High(下载文件副作用:写磁盘 + 触发外发网络 + 来源不可信)。SSRF 防护复用
//! commands/ai/http.rs(validate_url / resolve_and_check_host / build_client /
//! execute_with_redirects,经 pub(crate) 暴露)。路径校验复用 tool_registry 的
//! validate_path + resolve_workspace_path_with_allowed(与 write_file 同款,授权目录白名单)。
//!
//! handler 在 commands/ai/download_file.rs::execute_download_file,声明式注册收敛样板。
//! 闭包捕获 allowed_dirs:Arc<RwLock<AllowedDirs>>(download_file 写文件需路径授权,与 write_file 同源),
//! 在闭包内解析 output_path(白名单快照 + symlink 逃逸拦截),把已解析的绝对路径字符串传给 handler。
//!
//! ## 注册位置说明
//!
//! download_file 既涉网络(SSRF)又涉文件(路径校验落盘),按「副作用类型」归类:
//! 写文件(授权目录白名单)是核心安全约束,与 write_file/patch_file 同源(均持 allowed_dirs),
//! 故在 register_file_tools 注册(tool_registry.rs),而非 register_http_tools(后者不传 allowed_dirs)。

use std::sync::Arc;

use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
use df_ai::declare_tool;
use tokio::sync::RwLock;

use crate::commands::ai::tool_registry::resolve_workspace_path_with_allowed;
use crate::state::AllowedDirs;

/// 注册 download_file 工具到 `$registry`。
///
/// - name/desc/schema 面向 LLM 的工具说明
/// - risk=High(写磁盘 + 外发网络 + 来源不可信)
/// - handler 捕获 allowed_dirs,解析 output_path 后转调 download_file.rs::execute_download_file
///   (SSRF 防护 + 流式写 + 大小上限 + 原子写全在那)
pub fn register(
    registry: &mut AiToolRegistry,
    allowed_dirs: &Arc<RwLock<AllowedDirs>>,
) {
    // schema:url(必填)+ output_path(必填,落盘路径)+ timeout_secs(可选)。
    let schema = {
        let mut props = serde_json::Map::new();
        props.insert("url".into(), serde_json::json!({ "type": "string", "description": "要下载的文件 URL,仅 http/https(拒私网/localhost,SSRF 防护含 DNS resolve 后校验防重绑定,重定向≤3 跳每跳重校验)" }));
        props.insert("output_path".into(), serde_json::json!({ "type": "string", "description": "落盘文件路径(相对路径锚定到授权项目根,绝对路径须在授权目录内)。父目录不存在自动创建,覆盖既有同名文件。拒路径遍历(..)与敏感系统目录" }));
        props.insert("timeout_secs".into(), serde_json::json!({ "type": "integer", "description": "超时秒数(默认 60,上限 600)", "minimum": 1, "maximum": 600 }));
        serde_json::json!({
            "type": "object",
            "properties": props,
            "required": ["url", "output_path"],
        })
    };
    declare_tool!(
        registry,
        allowed_dirs: Arc<RwLock<AllowedDirs>>,
        "download_file",
        "下载远程 URL 文件到本地磁盘(跨平台流式,替代 curl/wget/Invoke-WebRequest 的 alias/编码/跨平台陷阱)。参数:url(http/https,output_path(落盘路径,须在授权目录内),timeout_secs(默认 60,上限 600)。安全:仅 http/https + 拒私网/保留 IP(SSRF 防护)+ 路径白名单(拒遍历/越界)+ 200MB 大小上限(超限中止删半成品)+ 原子写(tmp→rename)。返回 {path, bytes_written, status, url, elapsed_ms}。下载二进制/大文件优先用此工具(不进 prompt)",
        RiskLevel::High,
        schema: schema,
        args => {
            // 路径解析:取 allowed_dirs 快照,解析 output_path(白名单 + symlink 逃逸拦截)。
            // 与 write_file 同款:相对路径锚定首个持久授权目录,绝对路径走白名单校验。
            let snap = allowed_dirs.read().await.clone();
            let output_path_raw = args["output_path"].as_str()
                .ok_or_else(|| anyhow::anyhow!("缺少 output_path 参数"))?;
            let resolved = resolve_workspace_path_with_allowed(output_path_raw, &snap)?;
            let resolved_str = resolved.to_str()
                .ok_or_else(|| anyhow::anyhow!("output_path 含非法字符"))?
                .to_string();
            // 转调 download_file.rs handler(SSRF 防护 + 流式写 + 大小上限 + 原子写全在那)
            crate::commands::ai::download_file::execute_download_file(args, resolved_str).await
        }
    );
}
