import datetime as dt
import logging
from typing import Optional

from mcp.server.fastmcp import Context, FastMCP

from nextcloud_mcp_server.auth import require_scopes
from nextcloud_mcp_server.context import get_client
from nextcloud_mcp_server.models.calendar import (
    Calendar,
    ListCalendarsResponse,
    ListTodosResponse,
    Todo,
)
from nextcloud_mcp_server.observability.metrics import instrument_tool

logger = logging.getLogger(__name__)


def configure_calendar_tools(mcp: FastMCP):
    # Calendar tools
    @mcp.tool()
    @require_scopes("calendar:read")
    @instrument_tool
    async def nc_calendar_list_calendars(ctx: Context) -> ListCalendarsResponse:
        """List all available calendars for the user"""
        client = await get_client(ctx)
        calendars_data = await client.calendar.list_calendars()

        calendars = [Calendar(**cal_data) for cal_data in calendars_data]
        return ListCalendarsResponse(calendars=calendars, total_count=len(calendars))

    @mcp.tool()
    @require_scopes("calendar:write")
    @instrument_tool
    async def nc_calendar_create_event(
        calendar_name: str,
        title: str,
        start_datetime: str,
        ctx: Context,
        end_datetime: str = "",
        all_day: bool = False,
        description: str = "",
        location: str = "",
        categories: str = "",
        recurring: bool = False,
        recurrence_rule: str = "",
        recurrence_end_date: str = "",
        reminder_minutes: int = 15,
        reminder_email: bool = False,
        status: str = "CONFIRMED",
        priority: int = 5,
        privacy: str = "PUBLIC",
        attendees: str = "",
        url: str = "",
        color: str = "",
    ):
        """Create a comprehensive calendar event with full feature support

        Args:
            calendar_name: Name of the calendar to create the event in
            title: Event title
            start_datetime: ISO format: "2025-01-15T14:00:00" or "2025-01-15" for all-day
            ctx: MCP context
            end_datetime: ISO format end time, empty for all-day events
            all_day: Whether this is an all-day event
            description: Event description/details
            location: Event location
            categories: Comma-separated categories (e.g., "work,meeting")
            recurring: Whether this is a recurring event
            recurrence_rule: RFC5545 RRULE (e.g., "FREQ=WEEKLY;BYDAY=MO,WE,FR")
            recurrence_end_date: When to stop recurring
            reminder_minutes: Minutes before event to send reminder
            reminder_email: Whether to send email notification
            status: Event status: CONFIRMED, TENTATIVE, or CANCELLED
            priority: Priority level 1-9 (1=highest, 9=lowest, 5=normal)
            privacy: Privacy level: PUBLIC, PRIVATE, or CONFIDENTIAL
            attendees: Comma-separated email addresses
            url: Related URL for the event
            color: Event color (hex or name)

        Returns:
            Dict with event creation result
        """
        client = await get_client(ctx)

        event_data = {
            "title": title,
            "start_datetime": start_datetime,
            "end_datetime": end_datetime,
            "all_day": all_day,
            "description": description,
            "location": location,
            "categories": categories,
            "recurring": recurring,
            "recurrence_rule": recurrence_rule,
            "recurrence_end_date": recurrence_end_date,
            "reminder_minutes": reminder_minutes,
            "reminder_email": reminder_email,
            "status": status,
            "priority": priority,
            "privacy": privacy,
            "attendees": attendees,
            "url": url,
            "color": color,
        }

        return await client.calendar.create_event(calendar_name, event_data)

    @mcp.tool()
    @require_scopes("calendar:read")
    @instrument_tool
    async def nc_calendar_list_events(
        calendar_name: str,
        ctx: Context,
        start_date: str = "",
        end_date: str = "",
        limit: int = 50,
        min_attendees: Optional[int] = None,
        min_duration_minutes: Optional[int] = None,
        categories: Optional[str] = None,
        status: Optional[str] = None,
        title_contains: Optional[str] = None,
        location_contains: Optional[str] = None,
        search_all_calendars: bool = False,
    ):
        """List events in a calendar (or all calendars) within date range with advanced filtering.

        Args:
            calendar_name: Name of the calendar to search. Ignored if search_all_calendars=True.
            ctx: MCP context
            start_date: Start date for search (YYYY-MM-DD format, e.g., "2025-01-01")
            end_date: End date for search (YYYY-MM-DD format, e.g., "2025-01-31")
            limit: Maximum number of events to return
            min_attendees: Filter events with at least this many attendees
            min_duration_minutes: Filter events with at least this duration
            categories: Filter events containing any of these categories (comma-separated, e.g., "work,meeting")
            status: Filter events by status (CONFIRMED, TENTATIVE, or CANCELLED)
            title_contains: Filter events where title contains this text
            location_contains: Filter events where location contains this text
            search_all_calendars: If True, search across all calendars instead of just one

        Returns:
            List of events matching the filters
        """
        client = await get_client(ctx)

        # Convert YYYY-MM-DD format dates to datetime objects
        start_datetime = None
        end_datetime = None

        if start_date:
            try:
                start_datetime = dt.datetime.strptime(start_date, "%Y-%m-%d")
            except ValueError:
                # If parsing fails, try to parse as ISO format
                try:
                    start_datetime = dt.datetime.fromisoformat(start_date)
                except ValueError:
                    logger.warning(f"Invalid start_date format: {start_date}")

        if end_date:
            try:
                # For end date, set to end of day (23:59:59)
                end_datetime = dt.datetime.strptime(end_date, "%Y-%m-%d").replace(
                    hour=23, minute=59, second=59
                )
            except ValueError:
                # If parsing fails, try to parse as ISO format
                try:
                    end_datetime = dt.datetime.fromisoformat(end_date)
                except ValueError:
                    logger.warning(f"Invalid end_date format: {end_date}")

        # Build filters dictionary
        filters = {}
        if min_attendees is not None:
            filters["min_attendees"] = min_attendees
        if min_duration_minutes is not None:
            filters["min_duration_minutes"] = min_duration_minutes
        if categories is not None:
            filters["categories"] = [cat.strip() for cat in categories.split(",")]
        if status is not None:
            filters["status"] = status
        if title_contains is not None:
            filters["title_contains"] = title_contains
        if location_contains is not None:
            filters["location_contains"] = location_contains

        if search_all_calendars:
            # Search across all calendars with filters
            events = await client.calendar.search_events_across_calendars(
                start_datetime=start_datetime,
                end_datetime=end_datetime,
                filters=filters if filters else None,
            )
            return events[:limit]
        else:
            # Search in specific calendar
            events = await client.calendar.get_calendar_events(
                calendar_name=calendar_name,
                start_datetime=start_datetime,
                end_datetime=end_datetime,
                limit=limit,
            )

            # Apply filters if provided
            if filters:
                events = client.calendar._apply_event_filters(events, filters)

            return events

    @mcp.tool()
    @require_scopes("calendar:read")
    @instrument_tool
    async def nc_calendar_get_event(
        calendar_name: str,
        event_uid: str,
        ctx: Context,
    ):
        """Get detailed information about a specific event"""
        client = await get_client(ctx)
        event_data, etag = await client.calendar.get_event(calendar_name, event_uid)
        return event_data

    @mcp.tool()
    @require_scopes("calendar:write")
    @instrument_tool
    async def nc_calendar_update_event(
        calendar_name: str,
        event_uid: str,
        ctx: Context,
        # All the same parameters as create_event but optional
        title: str | None = None,
        start_datetime: str | None = None,
        end_datetime: str | None = None,
        all_day: bool | None = None,
        description: str | None = None,
        location: str | None = None,
        categories: str | None = None,
        # Recurrence updates
        recurring: bool | None = None,
        recurrence_rule: str | None = None,
        # Notification updates
        reminder_minutes: int | None = None,
        reminder_email: bool | None = None,
        # Event property updates
        status: str | None = None,
        priority: int | None = None,
        privacy: str | None = None,
        attendees: str | None = None,
        url: str | None = None,
        color: str | None = None,
        etag: str = "",
    ):
        """Update any aspect of an existing event"""
        client = await get_client(ctx)

        # Build update data with only non-None values
        event_data = {}
        if title is not None:
            event_data["title"] = title
        if start_datetime is not None:
            event_data["start_datetime"] = start_datetime
        if end_datetime is not None:
            event_data["end_datetime"] = end_datetime
        if all_day is not None:
            event_data["all_day"] = all_day
        if description is not None:
            event_data["description"] = description
        if location is not None:
            event_data["location"] = location
        if categories is not None:
            event_data["categories"] = categories
        if recurring is not None:
            event_data["recurring"] = recurring
        if recurrence_rule is not None:
            event_data["recurrence_rule"] = recurrence_rule
        if reminder_minutes is not None:
            event_data["reminder_minutes"] = reminder_minutes
        if reminder_email is not None:
            event_data["reminder_email"] = reminder_email
        if status is not None:
            event_data["status"] = status
        if priority is not None:
            event_data["priority"] = priority
        if privacy is not None:
            event_data["privacy"] = privacy
        if attendees is not None:
            event_data["attendees"] = attendees
        if url is not None:
            event_data["url"] = url
        if color is not None:
            event_data["color"] = color

        return await client.calendar.update_event(
            calendar_name, event_uid, event_data, etag
        )

    @mcp.tool()
    @require_scopes("calendar:write")
    @instrument_tool
    async def nc_calendar_delete_event(
        calendar_name: str,
        event_uid: str,
        ctx: Context,
    ):
        """Delete a calendar event"""
        client = await get_client(ctx)
        return await client.calendar.delete_event(calendar_name, event_uid)

    @mcp.tool()
    @require_scopes("calendar:write")
    @instrument_tool
    async def nc_calendar_create_meeting(
        title: str,
        date: str,
        time: str,
        ctx: Context,
        duration_minutes: int = 60,
        calendar_name: str = "personal",
        attendees: str = "",
        location: str = "",
        description: str = "",
        reminder_minutes: int = 15,
    ):
        """Quick meeting creation with smart defaults

        This is a convenience function for creating events with common meeting defaults.
        It automatically:
        - Calculates end time based on duration
        - Sets status to CONFIRMED
        - Adds a reminder
        - Uses simpler date/time inputs instead of full ISO format

        For full control over all event properties, use nc_calendar_create_event instead.

        Args:
            title: Meeting title
            date: Meeting date (YYYY-MM-DD format, e.g., "2025-01-15")
            time: Meeting start time (HH:MM format, e.g., "14:00")
            ctx: MCP context
            duration_minutes: Meeting duration in minutes (default: 60)
            calendar_name: Calendar to create the meeting in (default: "personal")
            attendees: Comma-separated email addresses of attendees
            location: Meeting location
            description: Meeting description/agenda
            reminder_minutes: Minutes before meeting to send reminder (default: 15)

        Returns:
            Dict with meeting creation result
        """
        client = await get_client(ctx)

        # Combine date and time for start_datetime
        start_datetime = f"{date}T{time}:00"

        # Calculate end_datetime
        start_dt = dt.datetime.fromisoformat(start_datetime)
        end_dt = start_dt + dt.timedelta(minutes=duration_minutes)
        end_datetime = end_dt.isoformat()

        event_data = {
            "title": title,
            "start_datetime": start_datetime,
            "end_datetime": end_datetime,
            "all_day": False,
            "description": description,
            "location": location,
            "attendees": attendees,
            "reminder_minutes": reminder_minutes,
            "status": "CONFIRMED",
            "priority": 5,
            "privacy": "PUBLIC",
        }

        return await client.calendar.create_event(calendar_name, event_data)

    @mcp.tool()
    @require_scopes("calendar:read")
    @instrument_tool
    async def nc_calendar_get_upcoming_events(
        ctx: Context,
        calendar_name: str = "",  # Empty = all calendars
        days_ahead: int = 7,
        limit: int = 10,
    ):
        """Get upcoming events in next N days"""
        client = await get_client(ctx)

        now = dt.datetime.now()
        end_datetime = now + dt.timedelta(days=days_ahead)

        if calendar_name:
            # Get events from specific calendar
            return await client.calendar.get_calendar_events(
                calendar_name=calendar_name,
                start_datetime=now,
                end_datetime=end_datetime,
                limit=limit,
            )
        else:
            # Get events from all calendars
            all_calendars = await client.calendar.list_calendars()
            all_events = []

            for calendar in all_calendars:
                try:
                    events = await client.calendar.get_calendar_events(
                        calendar_name=calendar["name"],
                        start_datetime=now,
                        end_datetime=end_datetime,
                        limit=limit,
                    )
                    # Add calendar info to each event
                    for event in events:
                        event["calendar_name"] = calendar["name"]
                        event["calendar_display_name"] = calendar["display_name"]
                    all_events.extend(events)
                except Exception as e:
                    logger.warning(
                        f"Error getting events from calendar {calendar['name']}: {e}"
                    )
                    continue

            # Sort by start time and limit
            all_events.sort(key=lambda x: x.get("start_datetime", ""))
            return all_events[:limit]

    @mcp.tool()
    @require_scopes("calendar:read")
    @instrument_tool
    async def nc_calendar_find_availability(
        duration_minutes: int,
        ctx: Context,
        attendees: str = "",  # Comma-separated email list
        date_range_start: str = "",  # "2025-07-28"
        date_range_end: str = "",  # "2025-08-04"
        business_hours_only: bool = True,
        exclude_weekends: bool = True,
        preferred_times: str = "",  # Comma-separated time ranges like "09:00-12:00,14:00-17:00"
    ):
        """Find available time slots for scheduling meetings.

        This tool intelligently analyzes existing calendar events to find free time slots
        that work for all specified attendees within the given constraints.

        Args:
            duration_minutes: Required duration for the meeting in minutes
            attendees: Comma-separated list of attendee email addresses to check availability for
            date_range_start: Start date for availability search (YYYY-MM-DD)
            date_range_end: End date for availability search (YYYY-MM-DD)
            business_hours_only: Only suggest slots during business hours (9 AM - 5 PM)
            exclude_weekends: Skip weekends when finding availability
            preferred_times: Preferred time ranges as "HH:MM-HH:MM" (comma-separated)

        Returns:
            List of available time slots with start/end times and duration
        """
        client = await get_client(ctx)

        # Parse attendees
        attendee_list = []
        if attendees:
            attendee_list = [
                email.strip() for email in attendees.split(",") if email.strip()
            ]

        # Parse preferred times
        preferred_time_list = []
        if preferred_times:
            preferred_time_list = [
                time_range.strip()
                for time_range in preferred_times.split(",")
                if time_range.strip()
            ]

        # Convert date strings to datetime objects
        start_datetime = None
        end_datetime = None

        if date_range_start:
            try:
                start_datetime = dt.datetime.strptime(date_range_start, "%Y-%m-%d")
            except ValueError:
                logger.warning(f"Invalid date_range_start format: {date_range_start}")

        if date_range_end:
            try:
                end_datetime = dt.datetime.strptime(date_range_end, "%Y-%m-%d").replace(
                    hour=23, minute=59, second=59
                )
            except ValueError:
                logger.warning(f"Invalid date_range_end format: {date_range_end}")

        # Build constraints
        constraints = {
            "business_hours_only": business_hours_only,
            "exclude_weekends": exclude_weekends,
            "preferred_times": preferred_time_list,
        }

        return await client.calendar.find_availability(
            duration_minutes=duration_minutes,
            attendees=attendee_list,
            start_datetime=start_datetime,
            end_datetime=end_datetime,
            constraints=constraints,
        )

    @mcp.tool()
    @require_scopes("calendar:write")
    @instrument_tool
    async def nc_calendar_bulk_operations(
        operation: str,  # "update", "delete", "move"
        ctx: Context,
        title_contains: Optional[str] = None,
        categories: Optional[str] = None,  # Comma-separated
        calendar_name: Optional[str] = None,
        start_date: str = "",  # "2025-07-01"
        end_date: str = "",  # "2025-07-31"
        status: Optional[str] = None,
        location_contains: Optional[str] = None,
        # Update operation parameters
        new_title: Optional[str] = None,
        new_description: Optional[str] = None,
        new_location: Optional[str] = None,
        new_categories: Optional[str] = None,
        new_priority: Optional[int] = None,
        new_reminder_minutes: Optional[int] = None,
        # Move operation parameters
        target_calendar: Optional[str] = None,
    ):
        """Perform bulk operations (update/delete) on events matching filter criteria.

        This tool allows you to efficiently modify or delete multiple events at once
        by applying filters to find matching events and then performing the specified operation.

        Args:
            operation: Type of operation - "update" or "delete"
            title_contains: Filter events where title contains this text
            categories: Filter events containing any of these categories (comma-separated)
            calendar_name: Filter events from this specific calendar
            start_date: Filter events starting from this date (YYYY-MM-DD)
            end_date: Filter events ending before this date (YYYY-MM-DD)
            status: Filter events by status (CONFIRMED, TENTATIVE, CANCELLED)
            location_contains: Filter events where location contains this text

            # For update operations:
            new_title: New title for matching events
            new_description: New description for matching events
            new_location: New location for matching events
            new_categories: New categories for matching events (comma-separated)
            new_priority: New priority for matching events (1-9, 5=normal)
            new_reminder_minutes: New reminder time in minutes before event

            # For move operations:
            target_calendar: Calendar to move events to (requires operation="move")

        Returns:
            Summary of operation results including counts and details
        """
        client = await get_client(ctx)

        if operation not in ["update", "delete", "move"]:
            raise ValueError("Operation must be 'update', 'delete', or 'move'")

        # Convert date strings to datetime objects
        start_datetime = None
        end_datetime = None

        if start_date:
            try:
                start_datetime = dt.datetime.strptime(start_date, "%Y-%m-%d")
            except ValueError:
                logger.warning(f"Invalid start_date format: {start_date}")

        if end_date:
            try:
                end_datetime = dt.datetime.strptime(end_date, "%Y-%m-%d").replace(
                    hour=23, minute=59, second=59
                )
            except ValueError:
                logger.warning(f"Invalid end_date format: {end_date}")

        # Build filter criteria
        filter_criteria = {}
        if title_contains is not None:
            filter_criteria["title_contains"] = title_contains
        if categories is not None:
            filter_criteria["categories"] = [
                cat.strip() for cat in categories.split(",")
            ]
        if status is not None:
            filter_criteria["status"] = status
        if location_contains is not None:
            filter_criteria["location_contains"] = location_contains
        # Add datetime strings for client compatibility
        if start_date:
            filter_criteria["start_date"] = start_date
        if end_date:
            filter_criteria["end_date"] = end_date

        if operation == "delete":
            # Find matching events and delete them
            if calendar_name:
                events = await client.calendar.get_calendar_events(
                    calendar_name=calendar_name,
                    start_datetime=start_datetime,
                    end_datetime=end_datetime,
                )
                if filter_criteria:
                    events = client.calendar._apply_event_filters(
                        events, filter_criteria
                    )
            else:
                events = await client.calendar.search_events_across_calendars(
                    start_datetime=start_datetime,
                    end_datetime=end_datetime,
                    filters=filter_criteria,
                )

            deleted_count = 0
            failed_count = 0
            results = []

            for event in events:
                try:
                    await client.calendar.delete_event(
                        event.get("calendar_name", calendar_name), event["uid"]
                    )
                    deleted_count += 1
                    results.append(
                        {
                            "uid": event["uid"],
                            "status": "deleted",
                            "title": event.get("title", ""),
                        }
                    )
                except Exception as e:
                    failed_count += 1
                    results.append(
                        {
                            "uid": event["uid"],
                            "status": "failed",
                            "error": str(e),
                            "title": event.get("title", ""),
                        }
                    )

            return {
                "operation": "delete",
                "total_found": len(events),
                "deleted_count": deleted_count,
                "failed_count": failed_count,
                "results": results,
            }

        elif operation == "update":
            # Build update data
            update_data = {}
            if new_title is not None:
                update_data["title"] = new_title
            if new_description is not None:
                update_data["description"] = new_description
            if new_location is not None:
                update_data["location"] = new_location
            if new_categories is not None:
                update_data["categories"] = new_categories
            if new_priority is not None:
                update_data["priority"] = new_priority
            if new_reminder_minutes is not None:
                update_data["reminder_minutes"] = new_reminder_minutes

            if not update_data:
                raise ValueError("No update data provided for update operation")

            return await client.calendar.bulk_update_events(
                filter_criteria, update_data
            )

        elif operation == "move":
            if not target_calendar:
                raise ValueError("target_calendar is required for move operation")

            # Find matching events
            if calendar_name:
                events = await client.calendar.get_calendar_events(
                    calendar_name=calendar_name,
                    start_datetime=start_datetime,
                    end_datetime=end_datetime,
                )
                if filter_criteria:
                    events = client.calendar._apply_event_filters(
                        events, filter_criteria
                    )
            else:
                events = await client.calendar.search_events_across_calendars(
                    start_datetime=start_datetime,
                    end_datetime=end_datetime,
                    filters=filter_criteria,
                )

            moved_count = 0
            failed_count = 0
            results = []

            for event in events:
                try:
                    # Create event in target calendar
                    event_data = {
                        k: v
                        for k, v in event.items()
                        if k
                        not in [
                            "uid",
                            "href",
                            "etag",
                            "calendar_name",
                            "calendar_display_name",
                        ]
                    }

                    await client.calendar.create_event(target_calendar, event_data)

                    # Delete from source calendar
                    await client.calendar.delete_event(
                        event.get("calendar_name", calendar_name), event["uid"]
                    )

                    moved_count += 1
                    results.append(
                        {
                            "uid": event["uid"],
                            "status": "moved",
                            "title": event.get("title", ""),
                            "from_calendar": event.get("calendar_name", calendar_name),
                            "to_calendar": target_calendar,
                        }
                    )
                except Exception as e:
                    failed_count += 1
                    results.append(
                        {
                            "uid": event["uid"],
                            "status": "failed",
                            "error": str(e),
                            "title": event.get("title", ""),
                        }
                    )

            return {
                "operation": "move",
                "total_found": len(events),
                "moved_count": moved_count,
                "failed_count": failed_count,
                "target_calendar": target_calendar,
                "results": results,
            }

    @mcp.tool()
    @require_scopes("calendar:write")
    @instrument_tool
    async def nc_calendar_manage_calendar(
        action: str,  # "create", "delete", "update", "list"
        ctx: Context,
        calendar_name: str = "",
        display_name: str = "",
        description: str = "",
        color: str = "#1976D2",  # Default blue color
    ):
        """Manage calendar creation, deletion, and properties.

        This tool provides comprehensive calendar management functionality including
        creating new calendars, deleting existing ones, and updating calendar properties.

        Args:
            action: Action to perform - "create", "delete", "update", or "list"
            calendar_name: Internal name for the calendar (required for create/delete/update)
            display_name: Human-readable name for the calendar (used for create/update)
            description: Description for the calendar (used for create/update)
            color: Hex color code for the calendar (e.g., "#1976D2" for blue)

        Returns:
            Result of the calendar management operation
        """
        client = await get_client(ctx)

        if action == "list":
            return await client.calendar.list_calendars()

        elif action == "create":
            if not calendar_name:
                raise ValueError("calendar_name is required for create action")

            return await client.calendar.create_calendar(
                calendar_name=calendar_name,
                display_name=display_name or calendar_name,
                description=description,
                color=color,
            )

        elif action == "delete":
            if not calendar_name:
                raise ValueError("calendar_name is required for delete action")

            return await client.calendar.delete_calendar(calendar_name)

        elif action == "update":
            if not calendar_name:
                raise ValueError("calendar_name is required for update action")

            # Note: Calendar property updates require additional CalDAV PROPPATCH implementation
            # For now, return an informative message
            return {
                "status": "not_implemented",
                "message": "Calendar property updates require PROPPATCH implementation",
                "calendar_name": calendar_name,
                "requested_changes": {
                    "display_name": display_name,
                    "description": description,
                    "color": color,
                },
            }

        else:
            raise ValueError("Action must be 'create', 'delete', 'update', or 'list'")

    # ============= Todo/Task Tools =============

    @mcp.tool()
    @require_scopes("todo:read", "calendar:read")
    @instrument_tool
    async def nc_calendar_list_todos(
        calendar_name: str,
        ctx: Context,
        status: Optional[str] = None,
        min_priority: Optional[int] = None,
        categories: Optional[str] = None,
        summary_contains: Optional[str] = None,
    ) -> ListTodosResponse:
        """List todos/tasks in a calendar with optional filtering.

        Args:
            calendar_name: Name of the calendar to list todos from
            ctx: MCP context
            status: Filter by status (NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED)
            min_priority: Filter by minimum priority (1=highest, 9=lowest)
            categories: Filter by categories (comma-separated, e.g., "work,urgent")
            summary_contains: Filter todos where summary contains this text

        Returns:
            List of todos matching the filters
        """
        client = await get_client(ctx)

        # Build filters dictionary
        filters = {}
        if status is not None:
            filters["status"] = status
        if min_priority is not None:
            filters["min_priority"] = min_priority
        if categories is not None:
            filters["categories"] = [cat.strip() for cat in categories.split(",")]
        if summary_contains is not None:
            filters["summary_contains"] = summary_contains

        todos_data = await client.calendar.list_todos(
            calendar_name, filters if filters else None
        )

        todos = [Todo(**todo_data) for todo_data in todos_data]
        return ListTodosResponse(
            todos=todos, calendar_name=calendar_name, total_count=len(todos)
        )

    @mcp.tool()
    @require_scopes("todo:write", "calendar:read")
    @instrument_tool
    async def nc_calendar_create_todo(
        calendar_name: str,
        summary: str,
        ctx: Context,
        description: str = "",
        status: str = "NEEDS-ACTION",
        priority: int = 0,
        due: str = "",
        dtstart: str = "",
        categories: str = "",
    ):
        """Create a new todo/task in a calendar.

        Args:
            calendar_name: Name of the calendar to create the todo in
            summary: Todo title/summary
            ctx: MCP context
            description: Detailed description of the todo
            status: Todo status (NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED)
            priority: Priority (0=undefined, 1=highest, 9=lowest)
            due: Due date/time (ISO format, e.g., "2025-01-15T14:00:00")
            dtstart: Start date/time (ISO format)
            categories: Comma-separated categories (e.g., "work,urgent")

        Returns:
            Dict with todo creation result
        """
        client = await get_client(ctx)

        todo_data = {
            "summary": summary,
            "description": description,
            "status": status,
            "priority": priority,
            "due": due,
            "dtstart": dtstart,
            "categories": categories,
        }

        return await client.calendar.create_todo(calendar_name, todo_data)

    @mcp.tool()
    @require_scopes("todo:write", "calendar:read")
    @instrument_tool
    async def nc_calendar_update_todo(
        calendar_name: str,
        todo_uid: str,
        ctx: Context,
        summary: Optional[str] = None,
        description: Optional[str] = None,
        status: Optional[str] = None,
        priority: Optional[int] = None,
        percent_complete: Optional[int] = None,
        due: Optional[str] = None,
        dtstart: Optional[str] = None,
        completed: Optional[str] = None,
        categories: Optional[str] = None,
    ):
        """Update an existing todo/task.

        Args:
            calendar_name: Name of the calendar containing the todo
            todo_uid: UID of the todo to update
            ctx: MCP context
            summary: New summary/title
            description: New description
            status: New status (NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED)
            priority: New priority (0-9)
            percent_complete: New completion percentage (0-100)
            due: New due date/time (ISO format)
            dtstart: New start date/time (ISO format)
            completed: Completion timestamp (ISO format)
            categories: New categories (comma-separated)

        Returns:
            Dict with todo update result
        """
        client = await get_client(ctx)

        # Build update data with only non-None values
        todo_data = {}
        if summary is not None:
            todo_data["summary"] = summary
        if description is not None:
            todo_data["description"] = description
        if status is not None:
            todo_data["status"] = status
        if priority is not None:
            todo_data["priority"] = priority
        if percent_complete is not None:
            todo_data["percent_complete"] = percent_complete
        if due is not None:
            todo_data["due"] = due
        if dtstart is not None:
            todo_data["dtstart"] = dtstart
        if completed is not None:
            todo_data["completed"] = completed
        if categories is not None:
            todo_data["categories"] = categories

        return await client.calendar.update_todo(calendar_name, todo_uid, todo_data)

    @mcp.tool()
    @require_scopes("todo:write", "calendar:read")
    @instrument_tool
    async def nc_calendar_delete_todo(
        calendar_name: str,
        todo_uid: str,
        ctx: Context,
    ):
        """Delete a todo/task from a calendar.

        Args:
            calendar_name: Name of the calendar containing the todo
            todo_uid: UID of the todo to delete
            ctx: MCP context

        Returns:
            Dict with deletion status
        """
        client = await get_client(ctx)
        return await client.calendar.delete_todo(calendar_name, todo_uid)

    @mcp.tool()
    @require_scopes("todo:read", "calendar:read")
    @instrument_tool
    async def nc_calendar_search_todos(
        ctx: Context,
        status: Optional[str] = None,
        min_priority: Optional[int] = None,
        categories: Optional[str] = None,
        summary_contains: Optional[str] = None,
    ):
        """Search todos across all calendars with optional filtering.

        Args:
            ctx: MCP context
            status: Filter by status (NEEDS-ACTION, IN-PROCESS, COMPLETED, CANCELLED)
            min_priority: Filter by minimum priority (1=highest, 9=lowest)
            categories: Filter by categories (comma-separated, e.g., "work,urgent")
            summary_contains: Filter todos where summary contains this text

        Returns:
            List of todos matching the filters from all calendars
        """
        client = await get_client(ctx)

        # Build filters dictionary
        filters = {}
        if status is not None:
            filters["status"] = status
        if min_priority is not None:
            filters["min_priority"] = min_priority
        if categories is not None:
            filters["categories"] = [cat.strip() for cat in categories.split(",")]
        if summary_contains is not None:
            filters["summary_contains"] = summary_contains

        todos_data = await client.calendar.search_todos_across_calendars(
            filters if filters else None
        )

        todos = [Todo(**todo_data) for todo_data in todos_data]
        return ListTodosResponse(todos=todos, total_count=len(todos))
