"""Web search tools for Sentinel agents — Tavily and SerpAPI.

Requires: requests  (one-click leashed install of the detected requirement).

API keys: these services need an API key, and the leashed sandbox SCRUBS environment
variables (secrets never propagate). So put your keys in a JSON file the leashed user can
read — `~/.config/sentinel-tools/secrets.json` — under this tool's own namespace:

    {"search": {"TAVILY_API_KEY": "tvly-xxxx", "SERPAPI_API_KEY": "xxxx"}}

(An environment variable of the same name still wins if one happens to be set, and a legacy
flat top-level key — `{"TAVILY_API_KEY": "..."}` — is still honored as a fallback.) Runs in
the leashed sandbox under Leash egress control — allow the services:
`leash network allow api.tavily.com` and `leash network allow serpapi.com`. Every function
returns a short error string on failure rather than raising.
"""
from __future__ import annotations

import json
import os
from pathlib import Path

import requests

_SECRETS = Path.home() / ".config" / "sentinel-tools" / "secrets.json"
_TOOL = "search"  # this tool's secrets namespace in secrets.json


def _load_key(name: str) -> str:
    """Read an API key: env var wins, then this tool's namespace in the secrets
    file, then the legacy flat top-level key. "" if none."""
    val = os.environ.get(name)
    if val:
        return val
    try:
        data = json.loads(_SECRETS.read_text())
    except Exception:  # noqa: BLE001 - missing/unreadable file -> no key
        return ""
    ns = data.get(_TOOL)
    if isinstance(ns, dict) and ns.get(name):
        return str(ns[name])
    return str(data.get(name, "") or "")  # legacy flat fallback


def tavily_search(query: str, max_results: int = 5) -> str:
    """Search the web with Tavily and return the top results (title, URL, snippet), plus
    Tavily's answer when available.

    `query` is the search text; `max_results` caps the count (default 5). Needs a Tavily API
    key (see this file's header). Prefer this for research questions that need fresh web info."""
    key = _load_key("TAVILY_API_KEY")
    if not key:
        return "error: no TAVILY_API_KEY — add it to ~/.config/sentinel-tools/secrets.json"
    try:
        r = requests.post(
            "https://api.tavily.com/search",
            json={"api_key": key, "query": query, "max_results": max(1, max_results),
                  "include_answer": True},
            timeout=25,
        )
        r.raise_for_status()
        data = r.json()
    except Exception as exc:  # noqa: BLE001
        return f"error: tavily search failed: {exc}"
    out = []
    if data.get("answer"):
        out.append("Answer: " + str(data["answer"]))
    for res in (data.get("results") or [])[: max(1, max_results)]:
        out.append(f"- {res.get('title', '')}\n  {res.get('url', '')}\n  {str(res.get('content', ''))[:240]}")
    return "\n".join(out) or "(no results)"


def serpapi_search(query: str, num: int = 5) -> str:
    """Search Google via SerpAPI and return the top organic results (title, URL, snippet).

    `query` is the search text; `num` caps the count (default 5). Needs a SerpAPI key (see
    this file's header)."""
    key = _load_key("SERPAPI_API_KEY")
    if not key:
        return "error: no SERPAPI_API_KEY — add it to ~/.config/sentinel-tools/secrets.json"
    try:
        r = requests.get(
            "https://serpapi.com/search",
            params={"engine": "google", "q": query, "api_key": key, "num": max(1, num)},
            timeout=25,
        )
        r.raise_for_status()
        data = r.json()
    except Exception as exc:  # noqa: BLE001
        return f"error: serpapi search failed: {exc}"
    out = []
    for res in (data.get("organic_results") or [])[: max(1, num)]:
        out.append(f"- {res.get('title', '')}\n  {res.get('link', '')}\n  {str(res.get('snippet', ''))[:240]}")
    return "\n".join(out) or "(no results)"
