"""Task management methods for FastMCP Client."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

import mcp.types
from mcp import McpError

if TYPE_CHECKING:
    from fastmcp.client.client import Client
from mcp.types import (
    CancelTaskRequest,
    CancelTaskRequestParams,
    GetTaskPayloadRequest,
    GetTaskPayloadRequestParams,
    GetTaskPayloadResult,
    GetTaskRequest,
    GetTaskRequestParams,
    GetTaskResult,
    ListTasksRequest,
    PaginatedRequestParams,
)

from fastmcp.utilities.logging import get_logger

logger = get_logger(__name__)


class ClientTaskManagementMixin:
    """Mixin providing task management methods for Client."""

    async def get_task_status(self: Client, task_id: str) -> GetTaskResult:
        """Query the status of a background task.

        Sends a 'tasks/get' MCP protocol request over the existing transport.

        Args:
            task_id: The task ID returned from call_tool_as_task

        Returns:
            GetTaskResult: Status information including taskId, status, pollInterval, etc.

        Raises:
            RuntimeError: If client not connected
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        request = GetTaskRequest(params=GetTaskRequestParams(taskId=task_id))
        return await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=GetTaskResult,
            )
        )

    async def get_task_result(self: Client, task_id: str) -> Any:
        """Retrieve the raw result of a completed background task.

        Sends a 'tasks/result' MCP protocol request over the existing transport.
        Returns the raw result - callers should parse it appropriately.

        Args:
            task_id: The task ID returned from call_tool_as_task

        Returns:
            Any: The raw result (could be tool, prompt, or resource result)

        Raises:
            RuntimeError: If client not connected, task not found, or task failed
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        request = GetTaskPayloadRequest(
            params=GetTaskPayloadRequestParams(taskId=task_id)
        )
        # Return raw result - Task classes handle type-specific parsing
        result = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[arg-type]  # ty:ignore[invalid-argument-type]
                result_type=GetTaskPayloadResult,
            )
        )
        # Return as dict for compatibility with Task class parsing
        return result.model_dump(exclude_none=True, by_alias=True)

    async def list_tasks(
        self: Client,
        cursor: str | None = None,
        limit: int = 50,
    ) -> dict[str, Any]:
        """List background tasks.

        Sends a 'tasks/list' MCP protocol request to the server. If the server
        returns an empty list (indicating client-side tracking), falls back to
        querying status for locally tracked task IDs.

        Args:
            cursor: Optional pagination cursor
            limit: Maximum number of tasks to return (default 50)

        Returns:
            dict: Response with structure:
                - tasks: List of task status dicts with taskId, status, etc.
                - nextCursor: Optional cursor for next page

        Raises:
            RuntimeError: If client not connected
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        # Send protocol request
        params = PaginatedRequestParams(cursor=cursor, limit=limit)  # type: ignore[call-arg]  # Optional field in MCP SDK  # ty:ignore[unknown-argument]
        request = ListTasksRequest(params=params)
        server_response = await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[invalid-argument-type]  # ty:ignore[invalid-argument-type]
                result_type=mcp.types.ListTasksResult,
            )
        )

        # If server returned tasks, use those
        if server_response.tasks:
            return server_response.model_dump(by_alias=True)

        # Server returned empty - fall back to client-side tracking
        tasks = []
        for task_id in list(self._submitted_task_ids)[:limit]:
            try:
                status = await self.get_task_status(task_id)
                tasks.append(status.model_dump(by_alias=True))
            except McpError:
                # Task may have expired or been deleted, skip it
                continue

        return {"tasks": tasks, "nextCursor": None}

    async def cancel_task(self: Client, task_id: str) -> mcp.types.CancelTaskResult:
        """Cancel a task, transitioning it to cancelled state.

        Sends a 'tasks/cancel' MCP protocol request. Task will halt execution
        and transition to cancelled state.

        Args:
            task_id: The task ID to cancel

        Returns:
            CancelTaskResult: The task status showing cancelled state

        Raises:
            RuntimeError: If task doesn't exist
            McpError: If the request results in a TimeoutError | JSONRPCError
        """
        request = CancelTaskRequest(params=CancelTaskRequestParams(taskId=task_id))
        return await self._await_with_session_monitoring(
            self.session.send_request(
                request=request,  # type: ignore[invalid-argument-type]  # ty:ignore[invalid-argument-type]
                result_type=mcp.types.CancelTaskResult,
            )
        )
