"""
server.py - FastMCP server exposing file-system watcher tools.

Tools
-----
watch_directory   Start watching a directory; returns a watch_id.
poll_events       Drain queued events (optionally filtered by watch_id).
list_active_watches  List all currently active watches.
unwatch           Stop and remove a watch by watch_id.
"""

from __future__ import annotations

import atexit
from typing import Annotated, Any

from fastmcp import FastMCP
from pydantic import Field

from security import SecurityError, validate_watch_request
from watcher import manager

# ---------------------------------------------------------------------------
# Application
# ---------------------------------------------------------------------------

mcp = FastMCP(
    name="filesystemwatcher-mcp",
    instructions=(
        "An event-driven filesystem watcher for AI agents. "
        "Use watch_directory to register a directory, then poll_events repeatedly "
        "to receive debounced file-change notifications. "
        "Call unwatch when monitoring is no longer needed."
    ),
)

# Ensure all watchers are cleaned up when the process exits
atexit.register(manager.stop_all)

# ---------------------------------------------------------------------------
# Tool: watch_directory
# ---------------------------------------------------------------------------


@mcp.tool()
def watch_directory(
    path: Annotated[str, Field(description="Absolute path to the directory to watch.")],
    recursive: Annotated[
        bool,
        Field(
            default=False,
            description="If True, also watch all subdirectories recursively.",
        ),
    ] = False,
    extensions: Annotated[
        list[str] | None,
        Field(
            default=None,
            description=(
                "Optional list of file extensions to include, e.g. [\".py\", \".js\"]. "
                "If omitted, all extensions are reported."
            ),
        ),
    ] = None,
    pattern: Annotated[
        str | None,
        Field(
            default=None,
            description=(
                "Optional glob pattern matched against the file *name*, e.g. \"*.log\". "
                "Applied in addition to (not instead of) the extensions filter."
            ),
        ),
    ] = None,
    ignore_patterns: Annotated[
        list[str] | None,
        Field(
            default=None,
            description=(
                "Optional glob patterns to exclude, e.g. [\"*.tmp\", \".git/*\"]. "
                "Matched against both the file name and its full path."
            ),
        ),
    ] = None,
    event_types: Annotated[
        list[str] | None,
        Field(
            default=None,
            description=(
                "Optional subset of event types to report. "
                "Valid values: \"created\", \"modified\", \"deleted\", \"moved\". "
                "If omitted, all event types are reported."
            ),
        ),
    ] = None,
) -> dict[str, Any]:
    """
    Start watching a directory for file system events.

    Returns a ``watch_id`` that can be used with ``poll_events`` and ``unwatch``.
    """
    if not path or not path.strip():
        return {"success": False, "error": "path must not be empty or blank."}

    try:
        resolved = validate_watch_request(path, recursive)
    except SecurityError as exc:
        return {"success": False, "error": str(exc)}

    # Validate event_types
    valid_event_types = {"created", "modified", "deleted", "moved"}
    if event_types:
        invalid = [et for et in event_types if et.lower() not in valid_event_types]
        if invalid:
            return {
                "success": False,
                "error": f"Invalid event_types: {invalid}. Valid values are: {sorted(valid_event_types)}",
            }

    try:
        watch_id = manager.start_watch(
            path=resolved,
            recursive=recursive,
            extensions=extensions,
            pattern=pattern,
            ignore_patterns=ignore_patterns,
            event_types=event_types,
        )
    except (PermissionError, RuntimeError) as exc:
        return {"success": False, "error": str(exc)}

    return {
        "success": True,
        "watch_id": watch_id,
        "path": str(resolved),
        "recursive": recursive,
        "message": (
            f"Now watching '{resolved}'"
            + (" (recursively)" if recursive else "")
            + ". Use poll_events to retrieve events."
        ),
    }


# ---------------------------------------------------------------------------
# Tool: poll_events
# ---------------------------------------------------------------------------


@mcp.tool()
def poll_events(
    watch_id: Annotated[
        str | None,
        Field(
            default=None,
            description=(
                "Optional watch_id to filter results. "
                "If omitted, events from all active watches are returned."
            ),
        ),
    ] = None,
    max_events: Annotated[
        int,
        Field(
            default=50,
            ge=1,
            le=500,
            description="Maximum number of events to return in a single call (1–500, default 50).",
        ),
    ] = 50,
) -> dict[str, Any]:
    """
    Drain and return queued file system events.

    Events are removed from the queue once returned (consume-once semantics).
    Call repeatedly to receive all pending events.
    """
    if watch_id is not None:
        active_ids = {w["watch_id"] for w in manager.list_watches()}
        if watch_id not in active_ids:
            return {
                "count": 0,
                "events": [],
                "warning": (
                    f"No active watch found with id '{watch_id}'. "
                    "The watch may have been stopped or never existed. "
                    "Use list_active_watches to see current watch IDs."
                ),
            }

    events = manager.poll_events(watch_id=watch_id, max_events=max_events)
    return {
        "count": len(events),
        "events": events,
    }


# ---------------------------------------------------------------------------
# Tool: list_active_watches
# ---------------------------------------------------------------------------


@mcp.tool()
def list_active_watches() -> dict[str, Any]:
    """
    Return a list of all currently active directory watches.

    Each entry includes the ``watch_id``, ``path``, ``recursive`` flag,
    and ``created_at`` Unix timestamp.
    """
    watches = manager.list_watches()
    return {
        "count": len(watches),
        "watches": watches,
    }


# ---------------------------------------------------------------------------
# Tool: unwatch
# ---------------------------------------------------------------------------


@mcp.tool()
def unwatch(
    watch_id: Annotated[str, Field(description="The watch_id returned by watch_directory.")],
) -> dict[str, Any]:
    """
    Stop and remove an active directory watch.
    """
    removed = manager.stop_watch(watch_id)
    if removed:
        return {"success": True, "message": f"Watch '{watch_id}' has been stopped and removed."}
    return {"success": False, "error": f"No active watch found with id '{watch_id}'."}


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------


def main() -> None:
    mcp.run()


if __name__ == "__main__":
    main()
