/**
 * Web 搜索 tool 包装 —— services/data 的 web 搜索端点。
 *
 * D-10 新增：后端用 ddgs 聚合多引擎（Bing/DuckDuckGo/Google/Brave），
 * 零 API key，自动按中文/英文选最优引擎。
 */
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

import { resolveRequestToken } from "../auth.js";
import { getSettings } from "../config.js";

type ToolRequestContext = { authToken?: string; get?: (key: string) => unknown };

/**
 * 工具层兜底超时（ms）。Node fetch 无默认超时——data 服务异常无响应时会让
 * 整个 orchestrator 轮次永久挂起。设得大于 data 端最坏耗时（web search 非 CJK
 * query 的 bing→duckduckgo fallback 最坏 ~40s），只在真正异常时兜底，不误杀
 * 正常 fallback。AbortSignal.timeout 触发的 AbortError 由各 tool 的 catch 收为
 * status=engine_error。
 */
const WEB_TOOL_FETCH_TIMEOUT_MS = 50_000;

async function getBaseUrl(): Promise<string> {
  const settings = getSettings();
  return settings.dataServiceUrl;
}

async function getAuthHeaders(ctx?: ToolRequestContext): Promise<Record<string, string>> {
  const token = await resolveRequestToken(ctx);
  return { Authorization: `Bearer ${token}` };
}

// ────────────────────────────────────────────────────────────────────
// web.search
// ────────────────────────────────────────────────────────────────────

export const webSearchTool = createTool({
  id: "web.search",
  description: `
    搜索互联网获取实时信息。后端用 ddgs 聚合多引擎（Bing/DuckDuckGo/Google/Brave），
    零 API key，自动按中文/英文选最优引擎。

    何时用：
    - 研究前补充最新信息："茅台 2026 Q1 财报" / "BTC ETF 最新消息"
    - 用户问"最近有什么新闻"但你现有的数据源没有
    - 验证 LLM 训练记忆里的信息是否过时
    - deep_dive 之前预搜索，把搜索结果喂给 analysts

    何时不用：
    - 已有 akshare/yfinance 专用数据源覆盖的场景（优先用专用数据源）
    - 实时价格 → data.get_ticker
    - 历史 K 线 → data.get_bars

    坑：
    - 返回带 status 字段：no_results=真没搜到（可当弱证据）；timeout / rate_limited /
      engine_error=引擎故障，**不能当"无证据"用**——按 hint 换数据源，不要重试同 query
    - 搜索结果质量因 engine 而异；中文自动走 bing 后端
    - 不要在循环里高频调用
  `.trim(),
  inputSchema: z.object({
    query: z.string().min(1).max(500).describe("搜索关键词"),
    backend: z.enum(["auto", "bing", "duckduckgo", "google", "brave"]).default("auto")
      .describe("搜索后端；auto 自动检测中文→bing"),
    maxResults: z.number().int().min(1).max(20).default(10),
  }),
  execute: async (inputData, ctx) => {
    const tc = ctx?.requestContext as ToolRequestContext | undefined;
    const baseUrl = await getBaseUrl();
    const headers = await getAuthHeaders(tc);
    const url = new URL(`${baseUrl}/web/search`);
    url.searchParams.set("query", inputData.query);
    url.searchParams.set("backend", inputData.backend ?? "auto");
    url.searchParams.set("max_results", String(inputData.maxResults ?? 10));
    try {
      const r = await fetch(url.toString(), { headers, signal: AbortSignal.timeout(WEB_TOOL_FETCH_TIMEOUT_MS) });
      if (!r.ok) return { results: [], status: "engine_error", error: `HTTP ${r.status}` };
      return await r.json();
    } catch (err) {
      return { results: [], status: "engine_error", error: String(err) };
    }
  },
});

// ────────────────────────────────────────────────────────────────────
// web.search_news
// ────────────────────────────────────────────────────────────────────

export const webSearchNewsTool = createTool({
  id: "web.search_news",
  description: `
    搜索新闻。后端用 ddgs 的 news 模式，返回最近新闻头条。

    何时用：用户问"最近有什么新闻" / 研究前了解最新动态
    何时不用：
    - 已有专用数据源（如 sentiment analyst 会自动调用）
    - A股市场级"今天为什么涨跌"类快讯 → data.get_market_news（专业财经源，免引擎噪声）

    坑：
    - ddgs 新闻源无中文财经内容：中文 query 服务端自动降级为网页搜索（backend 会标
      text-fallback），并经 hint 字段建议改用市场级快讯工具
    - 返回带 status 字段：timeout / rate_limited / engine_error 是引擎故障，
      **不能当"无新闻"解读**；no_results 才是真没搜到
  `.trim(),
  inputSchema: z.object({
    query: z.string().min(1).max(500).describe("新闻搜索关键词"),
    maxResults: z.number().int().min(1).max(20).default(10),
  }),
  execute: async (inputData, ctx) => {
    const tc = ctx?.requestContext as ToolRequestContext | undefined;
    const baseUrl = await getBaseUrl();
    const headers = await getAuthHeaders(tc);
    const url = new URL(`${baseUrl}/web/news`);
    url.searchParams.set("query", inputData.query);
    url.searchParams.set("max_results", String(inputData.maxResults ?? 10));
    try {
      const r = await fetch(url.toString(), { headers, signal: AbortSignal.timeout(WEB_TOOL_FETCH_TIMEOUT_MS) });
      if (!r.ok) return { results: [], status: "engine_error", error: `HTTP ${r.status}` };
      return await r.json();
    } catch (err) {
      return { results: [], status: "engine_error", error: String(err) };
    }
  },
});

// ────────────────────────────────────────────────────────────────────
// web.fetch
// ────────────────────────────────────────────────────────────────────

export const webFetchTool = createTool({
  id: "web.fetch",
  description: `
    抓取一个 URL 的网页正文（trafilatura 抽正文 + 标题 + 发布日期）。
    证据链最后一公里：web.search 只有标题 + snippet，结论级证据要用本 tool
    读原文（财报 / 公告 / 新闻稿 / transcript 页面）。

    何时用：
    - web.search / web.search_news 找到关键来源后，引用其内容下结论前先读原文
    - 给证据定强度等级（强 / 中 / 弱）需要确认页面实际说了什么
    - 需要页面发布日期来标注数据截止

    何时不用：
    - 行情 / K 线 / 财务指标 → data.*（结构化数据源更准）
    - 还没确定哪个 URL 值得读 → 先 web.search 筛选，别逐个乱抓

    坑：
    - 内网 / 私有地址被拒（SSRF 护栏）；非 HTML 文本类型被拒（PDF 读不了）
    - 正文超长会截断（truncated=true）；published_at 抽不到时为 null，
      此时不要编日期，按"日期待核"处理
    - 部分站点反爬返 403/超时——错误时换一个来源，不要对同一 URL 反复重试
  `.trim(),
  inputSchema: z.object({
    url: z.string().url().max(2048).describe("要抓取的 http/https URL"),
    // tool 侧默认 2 万字符（≈5k-20k token）：深扫一轮常 fetch 5+ 篇，4 万默认会快速
    // 堆高消息历史；真需要长文显式传大值（服务端 4 万上限仍钳制）
    maxChars: z.number().int().min(100).max(200_000).default(20_000)
      .describe("正文字符上限（默认 2 万；需要更长显式传，受服务端上限钳制）"),
  }),
  execute: async (inputData, ctx) => {
    const tc = ctx?.requestContext as ToolRequestContext | undefined;
    const baseUrl = await getBaseUrl();
    const headers = await getAuthHeaders(tc);
    const url = new URL(`${baseUrl}/web/fetch`);
    url.searchParams.set("url", inputData.url);
    if (inputData.maxChars) url.searchParams.set("max_chars", String(inputData.maxChars));
    try {
      const r = await fetch(url.toString(), { headers, signal: AbortSignal.timeout(WEB_TOOL_FETCH_TIMEOUT_MS) });
      if (!r.ok) return { url: inputData.url, text: "", error: `HTTP ${r.status}` };
      return await r.json();
    } catch (err) {
      return { url: inputData.url, text: "", error: String(err) };
    }
  },
});

export const webTools = [webSearchTool, webSearchNewsTool, webFetchTool] as const;
