"""Calendar and event tools for Reclaim.ai."""

from datetime import datetime, timedelta
from typing import Any, Optional

from fastmcp.exceptions import ToolError
from pydantic import ValidationError

from reclaim_mcp.cache import invalidate_cache, ttl_cache
from reclaim_mcp.client import ReclaimClient
from reclaim_mcp.config import get_settings
from reclaim_mcp.exceptions import NotFoundError, RateLimitError, ReclaimError
from reclaim_mcp.utils import format_validation_errors

from reclaim_mcp.models import (  # isort: skip
    CalendarEventId,
    DateRange,
    EventMove,
    EventRsvp,
    ListLimit,
    OptionalDateRange,
)


def _get_client() -> ReclaimClient:
    """Get a configured Reclaim client."""
    settings = get_settings()
    return ReclaimClient(settings)


def _extract_date(datetime_str: str) -> str:
    """Extract date part (YYYY-MM-DD) from datetime string.

    Handles both date-only ('2026-01-02') and datetime ('2026-01-02T00:00:00Z') formats.
    """
    # Take first 10 characters (YYYY-MM-DD)
    return datetime_str[:10]


@ttl_cache(ttl=60)
async def list_events(
    start: str,
    end: str,
    calendar_ids: Optional[list[int]] = None,
    event_type: Optional[str] = None,
    thin: bool = True,
) -> list[dict]:
    """List calendar events within a time range.

    Args:
        start: Start date (e.g., '2026-01-02' or '2026-01-02T00:00:00Z' - time is ignored)
        end: End date (e.g., '2026-01-02' or '2026-01-02T23:59:59Z' - time is ignored)
        calendar_ids: Optional list of calendar IDs to filter by
        event_type: Optional event type filter (EXTERNAL, RECLAIM_MANAGED, etc.)
        thin: If True, return minimal event data (default True)

    Returns:
        List of event objects with eventId, title, eventStart, eventEnd, etc.
    """
    # Validate input using Pydantic model
    try:
        validated = DateRange(start=_extract_date(start), end=_extract_date(end))
    except ValidationError as e:
        raise ToolError(format_validation_errors(e))

    try:
        client = _get_client()
        params: dict[str, Any] = {
            "start": validated.start,
            "end": validated.end,
            "thin": thin,
        }
        if calendar_ids:
            params["calendarIds"] = ",".join(str(c) for c in calendar_ids)
        if event_type:
            params["type"] = event_type

        events = await client.get("/api/events", params=params)
        return events
    except RateLimitError as e:
        raise ToolError(str(e))
    except ReclaimError as e:
        raise ToolError(f"Error listing events: {e}")


@ttl_cache(ttl=60)
async def list_personal_events(
    start: Optional[str] = None,
    end: Optional[str] = None,
    limit: int = 50,
) -> list[dict]:
    """List Reclaim-managed personal events (tasks, habits, focus time).

    Args:
        start: Optional start datetime in ISO format (defaults to today)
        end: Optional end datetime in ISO format (defaults to 7 days from now)
        limit: Maximum number of events to return (default 50)

    Returns:
        List of personal event objects.
    """
    # Validate limit using Pydantic model
    try:
        validated_limit = ListLimit(limit=limit)
    except ValidationError as e:
        raise ToolError(format_validation_errors(e))

    # Default to current week if dates not provided
    start_date = start
    end_date = end
    if start_date is None:
        start_date = datetime.now().strftime("%Y-%m-%d")
    if end_date is None:
        end_dt = datetime.now() + timedelta(days=7)
        end_date = end_dt.strftime("%Y-%m-%d")

    # Validate dates using Pydantic model
    try:
        validated_dates = OptionalDateRange(
            start=_extract_date(start_date),
            end=_extract_date(end_date),
        )
    except ValidationError as e:
        raise ToolError(format_validation_errors(e))

    try:
        client = _get_client()
        params: dict[str, Any] = {
            "limit": validated_limit.limit,
            "start": validated_dates.start,
            "end": validated_dates.end,
        }

        events = await client.get("/api/events/personal", params=params)
        return events
    except RateLimitError as e:
        raise ToolError(str(e))
    except ReclaimError as e:
        raise ToolError(f"Error listing personal events: {e}")
    except Exception as e:
        # Catch unexpected errors to help diagnose issues like the limit>17 bug
        raise ToolError(f"Unexpected error listing personal events: {type(e).__name__}: {e}")


async def get_event(
    calendar_id: int,
    event_id: str,
    thin: bool = False,
) -> dict:
    """Get a single event by calendar ID and event ID.

    Note: Works best with events from list_events (external calendar events).
    Reclaim-managed events from list_personal_events may return 404.

    Args:
        calendar_id: The calendar ID containing the event
        event_id: The event ID to retrieve
        thin: If True, return minimal event data (default False for full details)

    Returns:
        Event object with full details.
    """
    # Validate input using Pydantic model
    try:
        validated = CalendarEventId(calendar_id=calendar_id, event_id=event_id)
    except ValidationError as e:
        raise ToolError(format_validation_errors(e))

    try:
        client = _get_client()
        params: dict[str, Any] = {"thin": thin}
        event = await client.get(
            f"/api/events/{validated.calendar_id}/{validated.event_id}",
            params=params,
        )
        return event
    except NotFoundError:
        # fmt: off
        raise ToolError(
            f"Event {validated.event_id} not found in calendar "
            f"{validated.calendar_id}"
        )
        # fmt: on
    except RateLimitError as e:
        raise ToolError(str(e))
    except ReclaimError as e:
        raise ToolError(f"Error getting event {validated.event_id}: {e}")


async def set_event_rsvp(
    calendar_id: int,
    event_id: str,
    rsvp_status: str,
    send_updates: bool = True,
) -> dict:
    """Set RSVP status for a calendar event.

    Args:
        calendar_id: The calendar ID containing the event
        event_id: The event ID to update RSVP for
        rsvp_status: RSVP status (ACCEPTED, DECLINED, TENTATIVE, NEEDS_ACTION)
        send_updates: Whether to send update notifications (default True)

    Returns:
        Planner action result with updated event state.
    """
    # Validate input using Pydantic model
    try:
        validated = EventRsvp(
            calendar_id=calendar_id,
            event_id=event_id,
            rsvp_status=rsvp_status,  # type: ignore[arg-type]
        )
    except ValidationError as e:
        raise ToolError(format_validation_errors(e))

    try:
        client = _get_client()
        result = await client.put(
            f"/api/planner/event/rsvp/{validated.calendar_id}/{validated.event_id}",
            {
                "responseStatus": validated.rsvp_status.value,
                "sendUpdates": send_updates,
            },
        )
        invalidate_cache("list_events")
        return result
    except NotFoundError:
        raise ToolError(f"Event {event_id} not found in calendar {calendar_id}")
    except RateLimitError as e:
        raise ToolError(str(e))
    except ReclaimError as e:
        raise ToolError(f"Error setting RSVP for event {event_id}: {e}")


async def move_event(
    event_id: str,
    start_time: str,
    end_time: str,
) -> dict:
    """Move/reschedule an event to a new time.

    Args:
        event_id: The event ID to move
        start_time: New start time in ISO format (e.g., '2026-01-02T14:00:00Z')
        end_time: New end time in ISO format (e.g., '2026-01-02T15:00:00Z')

    Returns:
        Planner action result with updated event state.
    """
    # Validate input using Pydantic model
    try:
        validated = EventMove(
            event_id=event_id,
            start_time=start_time,
            end_time=end_time,
        )
    except ValidationError as e:
        raise ToolError(format_validation_errors(e))

    try:
        client = _get_client()
        # v1 API: uses query params instead of body
        result = await client.post(
            f"/api/planner/event/move/{validated.event_id}",
            {},  # Empty body
            params={"start": validated.start_time, "end": validated.end_time},
        )
        invalidate_cache("list_events")
        invalidate_cache("list_personal_events")
        return result
    except NotFoundError:
        raise ToolError(f"Event {event_id} not found")
    except RateLimitError as e:
        raise ToolError(str(e))
    except ReclaimError as e:
        raise ToolError(f"Error moving event {event_id}: {e}")
