"""agent_pro_webbridge.py — Hermes agent with Kimi WebBridge as the web tool.

Same Hermes 3 + LlamaIndex foundation as agent_pro.py. The one difference:
instead of calling DuckDuckGo's HTTP API with `duckduckgo-search`, the agent
gets web access by connecting to Kimi WebBridge — Moonshot's local
browser-automation service that drives a real Chrome or Edge browser
through the Chrome DevTools Protocol.

When to use which?
    DuckDuckGo  (agent_pro.py)            — quickest, just `pip install`,
                                            snippet-only, no JS rendering, no logins.
    Kimi WebBridge (this file)            — needs install, but the agent can
                                            log in, scroll, click, fill forms,
                                            and read JS-rendered pages. Page
                                            content never leaves your machine.

Setup (do this before running):

    1. Install the Kimi Desktop App from
       https://www.kimi.com/features/webbridge
    2. Install the Kimi WebBridge Chrome / Edge extension from the
       Chrome Web Store (search "Kimi WebBridge").
    3. Open Kimi Desktop → Settings → WebBridge. Pair the desktop app
       with the extension and confirm both show as connected.
    4. Copy the MCP endpoint URL shown in the WebBridge settings panel
       (looks like http://127.0.0.1:PORT/sse) and export it:

           Mac:      export WEBBRIDGE_MCP_URL="http://127.0.0.1:PORT/sse"
           Windows:  $env:WEBBRIDGE_MCP_URL = "http://127.0.0.1:PORT/sse"

Run:
    ollama pull hermes3:8b
    pip install -r requirements.txt
    python agent_pro_webbridge.py

Try asking:
    - "Search the web for tonight's weather in Chiang Mai."   (snippet-style)
    - "Open the Songkran 2026 official site, tell me the dates."  (multi-step)
    - "Check this month's events on my temple membership site."
      (WebBridge uses your real browser — your cookies, your login)

Why the script is async:
    LlamaIndex's MCP client returns tool specs asynchronously, and the
    workflow-based ReActAgent's .run() is also async. The whole REPL is
    wrapped in asyncio.run() to give those a running event loop.
"""
import asyncio
import os
from datetime import date
from pathlib import Path

from llama_index.core.agent.workflow import ReActAgent
from llama_index.core.tools import FunctionTool
from llama_index.llms.ollama import Ollama
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec


# ── Local tools (same as agent_pro.py, minus web_search) ────────────


def multiply(a: float, b: float) -> float:
    """Multiply two numbers and return the product."""
    return a * b


def days_until(iso_date: str) -> str:
    """How many days between today and a YYYY-MM-DD date."""
    target = date.fromisoformat(iso_date)
    delta = (target - date.today()).days
    if delta == 0:
        return "today"
    return f"{delta} day(s) {'from now' if delta > 0 else 'ago'}"


def generate_image(prompt: str, filename: str = "out.png") -> str:
    """Generate an image and save it under ./images/. Returns the saved path."""
    from image_gen import generate as _gen_image

    Path("images").mkdir(exist_ok=True)
    out_path = Path("images") / filename
    _gen_image(prompt, str(out_path))
    return f"Saved image to {out_path}"


def send_line_push(user_id: str, message: str) -> str:
    """Send a LINE push message to a user, group, or room.

    user_id is a LINE user/group/room ID (starts with U, G, or R).
    """
    import requests

    token = os.getenv("LINE_CHANNEL_ACCESS_TOKEN")
    if not token:
        return "LINE_CHANNEL_ACCESS_TOKEN not set in environment."
    r = requests.post(
        "https://api.line.me/v2/bot/message/push",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        },
        json={"to": user_id, "messages": [{"type": "text", "text": message}]},
        timeout=10,
    )
    return f"LINE push status: {r.status_code} {r.text[:200]}"


LOCAL_TOOLS = [
    FunctionTool.from_defaults(multiply),
    FunctionTool.from_defaults(days_until),
    FunctionTool.from_defaults(generate_image),
    FunctionTool.from_defaults(send_line_push),
]


# ── Connect to Kimi WebBridge over MCP ──────────────────────────────


async def get_webbridge_tools() -> list:
    """Open an MCP session to the Kimi WebBridge local service and pull its
    browser tools into a list LlamaIndex can give to the agent. Each browser
    action (open page, click, type, screenshot, read text) becomes one tool
    the agent can choose during ReAct."""
    url = os.getenv("WEBBRIDGE_MCP_URL")
    if not url:
        raise SystemExit(
            "WEBBRIDGE_MCP_URL not set.\n"
            "Open Kimi Desktop → Settings → WebBridge to find the MCP URL,\n"
            'then:  $env:WEBBRIDGE_MCP_URL = "http://127.0.0.1:PORT/sse"'
        )
    client = BasicMCPClient(url)
    return await McpToolSpec(client=client).to_tool_list_async()


# ── Wire it up ──────────────────────────────────────────────────────


async def build_agent() -> ReActAgent:
    """Build the agent with WebBridge tools merged into the local tool list."""
    webbridge_tools = await get_webbridge_tools()
    llm = Ollama(model="hermes3:8b", request_timeout=300)
    return ReActAgent(
        tools=LOCAL_TOOLS + webbridge_tools,
        llm=llm,
        verbose=True,
    )


async def main() -> None:
    agent = await build_agent()
    print("Hermes (WebBridge) agent ready. Empty line to quit.\n")
    while True:
        try:
            q = input("you > ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            return
        if not q:
            return
        response = await agent.run(q)
        print(f"\nagent > {response}\n")


if __name__ == "__main__":
    asyncio.run(main())
