"""
Hackathon Track 09: MCP Web Researcher

🎓 LEARNING OBJECTIVE:
See how Flock agents can use **Model Context Protocol (MCP)** tools
to browse the web, collect information, and save a report – all without
needing extra API keys or cloud accounts.

KEY CONCEPTS:
- Adding simple local MCP servers (DuckDuckGo search, website reader)
- Using `.with_mcps(...)` to give an agent access to tools
- Combining MCP tools with local `@flock_tool` helpers
- End-to-end workflow: ResearchTask → ResearchReport → Markdown file

🎛️  CONFIGURATION: Set USE_DASHBOARD to switch between CLI and Dashboard modes
"""

import asyncio
from pathlib import Path

from pydantic import BaseModel, Field

from flock import Flock
from flock.mcp import StdioServerParameters
from flock.registry import flock_tool, flock_type


# ============================================================================
# 🎛️  CONFIGURATION: Switch between CLI and Dashboard modes
# ============================================================================
USE_DASHBOARD = False  # Set to True for dashboard mode, False for CLI mode
# ============================================================================


# ============================================================================
# STEP 1: Define Tools and Types
# ============================================================================
# We'll define:
# - A local tool to write markdown reports to disk
# - Types for the research task and the resulting report
# ============================================================================


@flock_tool
def write_markdown_report(content: str, file_name: str) -> str:
    """
    Write a markdown report to .flock/<file_name> and return the path.

    This is a simple **local** tool. The agent can call it after doing web
    research via MCP to persist the results.
    """
    base_dir = Path(".flock")
    base_dir.mkdir(parents=True, exist_ok=True)
    file_path = base_dir / file_name
    file_path.write_text(content, encoding="utf-8")
    print(f"✍️  Wrote report to: {file_path}")
    return str(file_path)


@flock_type
class ResearchTask(BaseModel):
    """A request to research a topic using web search + website reading."""

    topic: str = Field(description="Main topic to research")
    focus: str = Field(
        default="practical tips",
        description="What kind of information to prioritize (e.g. theory, tips, pitfalls)",
    )
    max_sources: int = Field(
        default=5,
        ge=1,
        le=10,
        description="Maximum number of web pages to consult",
    )


@flock_type
class ResearchReport(BaseModel):
    """Structured research report generated by the agent."""

    topic: str
    focus: str
    summary: str = Field(description="High-level summary in plain language")
    key_findings: list[str] = Field(
        description="Bullet points with the most important insights"
    )
    sources: list[str] = Field(
        description="List of URLs that were consulted for this report"
    )
    report_file: str | None = Field(
        default=None,
        description="Path to the markdown report file (if written)",
    )


# ============================================================================
# STEP 2: Create the Orchestrator and Register MCP Servers
# ============================================================================
# We'll add two **simple local MCP servers**:
# - DuckDuckGo search (via `uvx duckduckgo-mcp-server`)
# - A website reader (via `npx @just-every/mcp-read-website-fast`)
#
# Both are key-free: no API keys or signup required. If the tools are not
# installed, we just print a warning and continue; the agent will then rely
# more on the base LLM.
# ============================================================================

flock = Flock()

try:
    flock.add_mcp(
        name="search_web",
        enable_tools_feature=True,
        connection_params=StdioServerParameters(
            command="uvx",
            args=["duckduckgo-mcp-server"],
        ),
    )
    print("✅ Added DuckDuckGo search MCP (search_web)")
except Exception as e:  # pragma: no cover - environment dependent
    print(f"⚠️  Could not add search MCP (is uvx + duckduckgo-mcp-server installed?): {e}")

try:
    flock.add_mcp(
        name="read_website",
        enable_tools_feature=True,
        connection_params=StdioServerParameters(
            command="npx",
            args=["-y", "@just-every/mcp-read-website-fast"],
        ),
    )
    print("✅ Added website reader MCP (read_website)")
except Exception as e:  # pragma: no cover - environment dependent
    print(
        f"⚠️  Could not add website reader MCP "
        f"(is npm + @just-every/mcp-read-website-fast installed?): {e}"
    )


# ============================================================================
# STEP 3: Define the Web Researcher Agent
# ============================================================================
# This agent:
# - Consumes ResearchTask
# - Uses MCP tools:
#     - search_web: search the web for relevant pages
#     - read_website: fetch + convert HTML pages to text/markdown
# - Uses local tool write_markdown_report() to save a report file
# - Publishes ResearchReport artifacts
#
# We don't write any explicit tool-calling code here; instead, we:
# - Give the agent access to MCP servers with .with_mcps(...)
# - Describe in natural language how to use the tools
# DSPy + Flock handle the rest.
# ============================================================================

web_researcher = (
    flock.agent("web_researcher")
    .description(
        "An expert research assistant that uses web search and website reading tools "
        "to gather up-to-date information about a topic. It should:\n"
        "1) Use the `search_web` MCP server to find relevant pages.\n"
        "2) Use the `read_website` MCP server to read promising URLs in depth.\n"
        "3) Extract key findings and write a clear summary.\n"
        "4) Use the local `write_markdown_report` tool to save a markdown report.\n"
        "Always respect the max_sources field when choosing how many pages to read."
    )
    .consumes(ResearchTask)
    .with_mcps(
        {
            # Give the agent access to search tools
            "search_web": {
                # Tool names are defined by the MCP server; "search" is common.
                # The agent will see these tools and decide how to call them.
                "tool_whitelist": ["search"],
            },
            # Give the agent access to website reading tools
            "read_website": {
                "tool_whitelist": ["read_website", "read_url", "read"],
            },
        }
    )
    .publishes(ResearchReport)
)


# ============================================================================
# STEP 4: Run the System (CLI + Dashboard)
# ============================================================================


async def main_cli() -> None:
    """CLI mode: Run the web researcher and display the report."""
    print("=" * 80)
    print("🧠 MCP WEB RESEARCHER - Hackathon Track 09")
    print("=" * 80)
    print()

    task = ResearchTask(
        topic="blackboard architecture for AI agents",
        focus="simple explanations and real-world use cases",
        max_sources=3,
    )

    print("📥 Research Task")
    print("----------------")
    print(f"Topic      : {task.topic}")
    print(f"Focus      : {task.focus}")
    print(f"Max sources: {task.max_sources}")
    print()
    print("⏳ Running web researcher agent...")
    print()

    await flock.publish(task)
    await flock.run_until_idle()

    reports = await flock.store.get_by_type(ResearchReport)

    if not reports:
        print("❌ No ResearchReport artifacts were generated.")
        print("   - Check MCP installation messages above.")
        print("   - The agent may still fall back to base LLM-only behavior.")
        return

    report = reports[0]

    print("=" * 80)
    print("✅ Research Report")
    print("=" * 80)
    print()
    print(f"🧵 Topic : {report.topic}")
    print(f"🎯 Focus : {report.focus}")
    print()
    print("📚 Summary")
    print("----------")
    print(report.summary)
    print()
    print("💡 Key Findings")
    print("---------------")
    for i, finding in enumerate(report.key_findings, start=1):
        print(f"{i:2d}. {finding}")
    print()
    print("🔗 Sources Used")
    print("---------------")
    if report.sources:
        for src in report.sources:
            print(f"- {src}")
    else:
        print("(No sources recorded)")
    print()
    if report.report_file:
        print(f"📝 Report saved to: {report.report_file}")
    else:
        print("📝 No markdown file was created (agent may have skipped the tool).")


async def main_dashboard() -> None:
    """Dashboard mode: Serve with interactive web interface."""
    print("🌐 Starting Flock Dashboard for MCP Web Researcher...")
    print("   Visit http://localhost:8344 to:")
    print("   - Publish ResearchTask artifacts")
    print("   - Watch the web_researcher agent execute")
    print("   - Inspect ResearchReport artifacts and their sources")
    print()
    await flock.serve(dashboard=True)


async def main() -> None:
    if USE_DASHBOARD:
        await main_dashboard()
    else:
        await main_cli()


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


# ============================================================================
# 🎓 NOW IT'S YOUR TURN!
# ============================================================================
#
# EXPERIMENT 1: Change the Research Domain
# ----------------------------------------
# Try different research topics:
#   - "history of blackboard systems in AI"
#   - "good onboarding patterns for hackathons"
#   - "LLM safety best practices for tools"
#
# How does the report style change when you tweak the `focus` field?
# For example:
#   focus="common pitfalls"
#   focus="step-by-step tutorial"
#   focus="pros and cons comparison"
#
#
# EXPERIMENT 2: Control the Number of Sources
# -------------------------------------------
# Change `max_sources` in ResearchTask:
#   max_sources=1
#   max_sources=5
#   max_sources=10
#
# Do you see more or fewer URLs listed in the report?
# How does this affect the level of detail vs. redundancy?
#
#
# EXPERIMENT 3: Customize the Markdown Report
# -------------------------------------------
# Update the write_markdown_report tool to:
#   - Include a YAML front matter block with metadata
#   - Append a timestamp to the file name
#   - Save reports into topic-specific subfolders
#
# Example:
#   def write_markdown_report(content: str, file_name: str) -> str:
#       # Add 'topic' or date to file name
#       ...
#
# Re-run the example and inspect the files under .flock/
#
#
# EXPERIMENT 4: Add a Second Agent
# --------------------------------
# Create a new agent that consumes ResearchReport and produces:
#   - A 10-bullet executive summary
#   - A "tweetstorm" version of the findings
#
# For example:
#   @flock_type
#   class Tweetstorm(BaseModel):
#       topic: str
#       tweets: list[str]
#
#   tweet_agent = (
#       flock.agent("tweet_agent")
#       .description("Turns research reports into tweetstorms")
#       .consumes(ResearchReport)
#       .publishes(Tweetstorm)
#   )
#
# What does the dashboard show when both agents are active?
#
#
# EXPERIMENT 5: MCP Failure Modes
# -------------------------------
# Temporarily uninstall or break one MCP server (e.g. rename the command)
# and run the example:
#   - How does the system behave when MCP is missing?
#   - Does the agent still produce a basic report using only the base LLM?
#
# This is a good way to understand graceful degradation patterns.
#
# ============================================================================

