"""agent_pro.py — Hermes agent with web search, image gen, and LINE tools.

Builds on agent.py from the Mac Stack. Same Ollama + Hermes 3 + LlamaIndex
foundation. Three new tools let the agent reach the world:

    web_search       — DuckDuckGo, no API key
    generate_image   — local Stable Diffusion XL Turbo on Apple Silicon
    send_line_push   — send a LINE message via the Messaging API

The cron / event-driven pattern is NOT a tool. It is a separate script
(daily_word.py) that you run on a schedule with launchd. The agent doesn't
schedule itself — the operating system schedules the script that uses it.

Before running:
    ollama pull hermes3:8b
    pip install -r requirements.txt

Then:
    python agent_pro.py

Try asking:
    - "Search the web for tonight's weather in Chiang Mai."
    - "Make an image of a Lanna night market with red lanterns."
    - "Send 'I'll be late for dinner' to LINE user Uxxxxx." (needs token)
"""
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

# Gmail tools (Phase 1-2): outbound send, inbox search, and threaded reply.
# Requires gmail_tool.py + credentials.json + token.json in the same
# directory. See gmail_setup.md for the OAuth setup.
from gmail_tool import send_email, read_emails, reply_to_email


# ── Tools the agent can choose to call ──────────────────────────────


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 web_search(query: str) -> str:
    """Search the web with DuckDuckGo and return the top 5 result snippets.

    Use this whenever the user asks about current events, prices, weather,
    sports scores, or anything you wouldn't already know.
    """
    from duckduckgo_search import DDGS

    with DDGS() as ddg:
        results = list(ddg.text(query, max_results=5))
    if not results:
        return "No results found."
    return "\n\n".join(
        f"{r['title']}\n{r['href']}\n{r['body']}" for r in results
    )


def generate_image(prompt: str, filename: str = "out.png") -> str:
    """Generate an image from a text prompt and save it under ./images/.

    Returns the path of the saved PNG. Use this when the user asks for a
    picture, illustration, drawing, or any visual content. The prompt
    should be a short English description; SDXL Turbo handles ~75 tokens.
    """
    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).
    message is the text body. Returns the HTTP status code.
    """
    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]}"


TOOLS = [
    FunctionTool.from_defaults(multiply),
    FunctionTool.from_defaults(days_until),
    FunctionTool.from_defaults(web_search),
    FunctionTool.from_defaults(generate_image),
    FunctionTool.from_defaults(send_line_push),
    FunctionTool.from_defaults(send_email),
    FunctionTool.from_defaults(read_emails),
    FunctionTool.from_defaults(reply_to_email),
]


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


def build_agent() -> ReActAgent:
    """Build the agent once and return it. line_bot.py and daily_word.py reuse this."""
    llm = Ollama(model="hermes3:8b", request_timeout=300)
    return ReActAgent(tools=TOOLS, llm=llm, verbose=True)


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


if __name__ == "__main__":
    main()
