/*
 * Copyright 2017-2025 noear.org and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.noear.solon.ai.skills.crawler;

import org.noear.snack4.ONode;
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.ai.chat.skill.AbsSkill;
import org.noear.solon.annotation.Param;
import org.noear.solon.lang.Preview;
import org.noear.solon.net.http.HttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 网页抓取工具包：为 AI 代理提供实时互联网信息的“阅读器”。
 *
 * <p>该工具包通过高度优化的爬虫驱动，将复杂的 HTML 页面转化为 LLM 友好的 Markdown 格式：
 * <ul>
 * <li><b>多驱动支持</b>：内置 {@code JINA}（极简高效）与 {@code FIRECRAWL}（深度降噪）双引擎。</li>
 * <li><b>智能降噪</b>：自动剔除网页中的侧边栏、广告、导航栏及冗余脚本，仅保留正文核心。</li>
 * <li><b>反爬策略自适应</b>：封装了标准 {@code User-Agent} 与请求头，支持令牌（API Key）鉴权以获取更高级的抓取频率。</li>
 * <li><b>时效性增强</b>：支持强制禁用缓存（No-Cache），确保 AI 获取的是目标 URL 的最新快照。</li>
 * </ul>
 * </p>
 *
 * @author noear
 * @since 3.9.1
 */
@Preview("3.9.1")
public class WebCrawlerDriverSkill extends AbsSkill {
    private static final Logger LOG = LoggerFactory.getLogger(WebCrawlerDriverSkill.class);

    private final String apiKey;
    private final CrawlerDriver driver;

    public WebCrawlerDriverSkill(CrawlerDriver driver, String apiKey) {
        this.driver = driver;
        this.apiKey = apiKey;
    }

    @Override
    public String name() {
        return "web_crawler";
    }

    @Override
    public String description() {
        return "阅读助手：抓取网页内容并转换为 LLM 友好的 Markdown。";
    }

    @ToolMapping(name = "crawl_url", description = "读取并分析指定 URL 网页的详细内容")
    public String crawl(@Param("url") String url) {
        if (url == null || !url.startsWith("http")) return "错误：URL 无效。";

        if (LOG.isTraceEnabled()) {
            LOG.trace("Crawl url [{}]: {}", driver, url);
        }

        try {
            return driver.crawl(url, apiKey);
        } catch (Exception e) {
            LOG.error("Crawl failed [{}]: {}", driver, url, e);
            return "抓取失败: " + e.getMessage();
        }
    }

    // --- 驱动接口 ---
    public interface CrawlerDriver {
        String crawl(String url, String apiKey) throws Exception;
    }

    // --- 驱动实现：Jina Reader (极简 GET) ---
    public static final CrawlerDriver JINA = (url, key) -> {
        HttpUtils http = HttpUtils.http("https://r.jina.ai/" + url);

        // 1. 必须：设置模拟浏览器的 User-Agent
        http.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
                .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
                .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
                .header("Cache-Control", "no-cache")
                .header("Pragma", "no-cache");

        // 2. Jina 专用 Header
        http.header("X-Return-Format", "markdown")
                .header("X-No-Cache", "true"); // 避免获取旧的截断缓存

        // 3. 如果有 Key 则添加
        if (key != null && !key.isEmpty()) {
            http.header("Authorization", "Bearer " + key);
        }

        return http.get();
    };

    // --- 驱动实现：Firecrawl (标准 POST) ---
    public static final CrawlerDriver FIRECRAWL = (url, key) -> {
        // Firecrawl v1/v2 统一接口，明确要求 markdown 格式
        String json = HttpUtils.http("https://api.firecrawl.dev/v1/scrape")
                .header("Authorization", "Bearer " + key)
                .header("Content-Type", "application/json")
                .bodyOfJson(new ONode()
                        .set("url", url)
                        .set("formats", new ONode().add("markdown"))
                        .set("onlyMainContent", true) // 默认剔除导航和广告
                        .toJson())
                .post();

        ONode res = ONode.ofJson(json);

        // 增加安全校验：Firecrawl 的标准结构是 { success: true, data: { markdown: "..." } }
        if (res.get("success").getBoolean()) {
            return res.get("data").get("markdown").getString();
        } else {
            throw new RuntimeException(res.get("error").getString());
        }
    };
}