"""Web Search integration — search the web from skills and CLI.

Default backend: DuckDuckGo (zero config, free).
Optional backends: Tavily (API key), SearXNG (self-hosted URL).
Auto-detection order: Tavily → SearXNG → DuckDuckGo.

Usage in skills:

    from alive.integrations.web_search import search_web, search_and_read, search_news

    results = search_web("query", max_results=5)     # [{title, url, snippet}]
    text = search_and_read("query")                   # search + fetch top result
    news = search_news("AI open source 2026")          # recent news
"""

from __future__ import annotations

import json
import logging
import re
from typing import Any

_log = logging.getLogger("alive")


# ── Config helpers ──────────────────────────────────────────────────────────


def _load_config() -> dict[str, Any]:
    """Load Alive config."""
    from alive.integrations import _load_config as _cfg

    return _cfg()


def _get_backend() -> str:
    """Detect which search backend to use.

    Priority: tavily (if key set) → searxng (if url set) → duckduckgo.
    """
    cfg = _load_config()
    if cfg.get("tavily_key"):
        return "tavily"
    if cfg.get("searxng_url"):
        return "searxng"
    return "duckduckgo"


# ── DuckDuckGo backend ─────────────────────────────────────────────────────


def _search_ddg(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search using DuckDuckGo (no API key needed)."""
    from duckduckgo_search import DDGS

    with DDGS() as ddgs:
        results = list(ddgs.text(query, max_results=max_results))
        return [
            {
                "title": r.get("title", ""),
                "url": r.get("href", ""),
                "snippet": r.get("body", ""),
            }
            for r in results
        ]


def _news_ddg(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search recent news using DuckDuckGo."""
    from duckduckgo_search import DDGS

    with DDGS() as ddgs:
        results = list(ddgs.news(query, max_results=max_results))
        return [
            {
                "title": r.get("title", ""),
                "url": r.get("url", ""),
                "date": r.get("date", ""),
                "snippet": r.get("body", ""),
            }
            for r in results
        ]


# ── Tavily backend ─────────────────────────────────────────────────────────


def _search_tavily(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search using Tavily API."""
    import httpx

    cfg = _load_config()
    api_key = cfg.get("tavily_key", "")

    resp = httpx.post(
        "https://api.tavily.com/search",
        json={
            "api_key": api_key,
            "query": query,
            "max_results": max_results,
            "search_depth": "basic",
        },
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()

    return [
        {
            "title": r.get("title", ""),
            "url": r.get("url", ""),
            "snippet": r.get("content", ""),
        }
        for r in data.get("results", [])
    ]


def _news_tavily(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search news using Tavily API."""
    import httpx

    cfg = _load_config()
    api_key = cfg.get("tavily_key", "")

    resp = httpx.post(
        "https://api.tavily.com/search",
        json={
            "api_key": api_key,
            "query": query,
            "max_results": max_results,
            "search_depth": "basic",
            "topic": "news",
        },
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()

    return [
        {
            "title": r.get("title", ""),
            "url": r.get("url", ""),
            "date": r.get("published_date", ""),
            "snippet": r.get("content", ""),
        }
        for r in data.get("results", [])
    ]


# ── SearXNG backend ────────────────────────────────────────────────────────


def _search_searxng(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search using a self-hosted SearXNG instance."""
    import httpx

    cfg = _load_config()
    base_url = cfg.get("searxng_url", "").rstrip("/")

    resp = httpx.get(
        f"{base_url}/search",
        params={
            "q": query,
            "format": "json",
            "categories": "general",
        },
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()

    results = data.get("results", [])[:max_results]
    return [
        {
            "title": r.get("title", ""),
            "url": r.get("url", ""),
            "snippet": r.get("content", ""),
        }
        for r in results
    ]


def _news_searxng(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search news using SearXNG."""
    import httpx

    cfg = _load_config()
    base_url = cfg.get("searxng_url", "").rstrip("/")

    resp = httpx.get(
        f"{base_url}/search",
        params={
            "q": query,
            "format": "json",
            "categories": "news",
        },
        timeout=15,
    )
    resp.raise_for_status()
    data = resp.json()

    results = data.get("results", [])[:max_results]
    return [
        {
            "title": r.get("title", ""),
            "url": r.get("url", ""),
            "date": r.get("publishedDate", ""),
            "snippet": r.get("content", ""),
        }
        for r in results
    ]


# ── Public API ──────────────────────────────────────────────────────────────


def search_web(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search the web. Returns list of {title, url, snippet}.

    Automatically selects the best available backend:
    Tavily (if key set) → SearXNG (if URL set) → DuckDuckGo (always works).

    Args:
        query: Search query string.
        max_results: Maximum number of results to return.

    Returns:
        List of result dicts with keys: title, url, snippet.
    """
    backend = _get_backend()
    _log.debug("Web search backend: %s, query: %s", backend, query)

    if backend == "tavily":
        return _search_tavily(query, max_results)
    elif backend == "searxng":
        return _search_searxng(query, max_results)
    else:
        return _search_ddg(query, max_results)


def search_and_read(query: str) -> str:
    """Search web and return full content of the best result.

    Searches, fetches the top result page, strips HTML, and returns
    plain text (capped at 5000 chars).

    Args:
        query: Search query string.

    Returns:
        Plain text content of the top search result.
    """
    results = search_web(query, max_results=3)
    if not results:
        return "No results found."

    import httpx

    for result in results:
        url = result.get("url", "")
        if not url:
            continue
        try:
            resp = httpx.get(url, follow_redirects=True, timeout=15, headers={
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                              "AppleWebKit/537.36 (KHTML, like Gecko) "
                              "Chrome/120.0.0.0 Safari/537.36"
            })
            resp.raise_for_status()
            # Basic HTML to text extraction
            text = re.sub(r"<script[^>]*>.*?</script>", " ", resp.text, flags=re.DOTALL)
            text = re.sub(r"<style[^>]*>.*?</style>", " ", text, flags=re.DOTALL)
            text = re.sub(r"<[^>]+>", " ", text)
            text = re.sub(r"\s+", " ", text).strip()
            return text[:5000]
        except Exception as e:
            _log.debug("Failed to fetch %s: %s", url, e)
            continue

    # Fallback: return snippets
    return "\n".join(
        f"- {r['title']}: {r['snippet']}" for r in results if r.get("snippet")
    )


def search_news(query: str, max_results: int = 5) -> list[dict[str, str]]:
    """Search recent news. Returns list of {title, url, date, snippet}.

    Args:
        query: News search query.
        max_results: Maximum number of results.

    Returns:
        List of news result dicts with keys: title, url, date, snippet.
    """
    backend = _get_backend()
    _log.debug("News search backend: %s, query: %s", backend, query)

    if backend == "tavily":
        return _news_tavily(query, max_results)
    elif backend == "searxng":
        return _news_searxng(query, max_results)
    else:
        return _news_ddg(query, max_results)


def display_search_results(
    results: list[dict[str, str]],
    *,
    title: str = "Web Search Results",
    console=None,
) -> None:
    """Display search results as a rich panel."""
    from rich.console import Console
    from rich.panel import Panel

    con = console or Console()

    if not results:
        con.print(Panel("[dim]No results found.[/]", title=f"[bold]{title}[/]", border_style="yellow"))
        return

    lines = []
    for i, r in enumerate(results, 1):
        lines.append(f"[bold]{i}. {r.get('title', 'Untitled')}[/]")
        snippet = r.get("snippet", "")
        if snippet:
            lines.append(f"   {snippet[:120]}")
        date = r.get("date", "")
        if date:
            lines.append(f"   [dim]{date}[/]")
        url = r.get("url", "")
        if url:
            lines.append(f"   [dim]{url}[/]")
        lines.append("")

    con.print(Panel(
        "\n".join(lines).rstrip(),
        title=f"[bold cyan]{title}[/]",
        border_style="cyan",
        padding=(1, 2),
    ))


def test_search(console=None) -> bool:
    """Run a test search and display results. Returns True on success."""
    from rich.console import Console

    con = console or Console()
    con.print("[bold cyan]Testing web search...[/]\n")

    backend = _get_backend()
    con.print(f"[dim]Backend: {backend}[/]\n")

    try:
        results = search_web("Python programming language", max_results=3)
        if results:
            display_search_results(results, title="Test Search Results", console=con)
            con.print(f"\n[bold green]✓ Web search working! ({len(results)} results)[/]")
            return True
        else:
            con.print("[bold red]✗ Search returned no results.[/]")
            return False
    except Exception as e:
        con.print(f"[bold red]✗ Search failed: {e}[/]")
        return False
