"""
Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com

Async execution handler: accept request, return immediately, run command in
background and notify via job_push notifier so WS subscribers receive the result.
"""

from __future__ import annotations

import asyncio
import uuid
from typing import Any, Dict, Optional

from fastapi import Request

from mcp_proxy_adapter.commands.command_registry import registry
from mcp_proxy_adapter.core.errors import MethodNotFoundError
from mcp_proxy_adapter.core.job_push.notifier import notify_job_state_changed
from mcp_proxy_adapter.core.logging import get_global_logger, RequestLogger

# Default timeout for async command execution when config is not available (seconds).
ASYNC_COMMAND_TIMEOUT = 300.0


async def _run_async_command_and_notify(
    command_class: Any,
    command_name: str,
    params: Dict[str, Any],
    context: Dict[str, Any],
    deliver_id: str,
    logger: Any,
    timeout: float = ASYNC_COMMAND_TIMEOUT,
) -> None:
    """
    Run command in background and notify job state on completion or failure.

    Does not re-raise; catches all exceptions and calls notify_job_state_changed
    with status "failed". Used by execute_command_async.
    """
    try:
        result_obj = await asyncio.wait_for(
            command_class.run(**params, context=context), timeout=timeout
        )
        await notify_job_state_changed(
            deliver_id, "completed", result_obj.to_dict(), None, 100, ""
        )
    except asyncio.TimeoutError:
        logger.warning(
            "Async command '%s' (deliver_id=%s) timed out after %.1fs",
            command_name,
            deliver_id,
            timeout,
        )
        await notify_job_state_changed(
            deliver_id,
            "failed",
            None,
            f"Command timed out after {timeout}s",
            0,
            "",
        )
    except Exception as exc:
        logger.exception(
            "Async command '%s' (deliver_id=%s) failed: %s",
            command_name,
            deliver_id,
            exc,
        )
        await notify_job_state_changed(deliver_id, "failed", None, str(exc), 0, "")


async def execute_command_async(
    command_name: str,
    params: Optional[Dict[str, Any]],
    deliver_id: Optional[str],
    request_id: Optional[str] = None,
    request: Optional[Request] = None,
) -> Dict[str, Any]:
    """Accept async command request, return immediately, run command in background.

    Returns dict with accepted=True and deliver_id. On completion or failure
    notifies via notify_job_state_changed so WS subscribers receive the result.
    """
    logger = RequestLogger(__name__, request_id) if request_id else get_global_logger()

    try:
        command_class = registry.get_command(command_name)
    except Exception:
        raise MethodNotFoundError(f"Method not found: {command_name}")

    context: Dict[str, Any] = {}
    if request is not None and hasattr(request, "state"):
        user_id = getattr(request.state, "user_id", None)
        user_role = getattr(request.state, "user_role", None)
        user_roles = getattr(request.state, "user_roles", None)
        if user_id or user_role or user_roles:
            context["user"] = {
                "id": user_id,
                "role": user_role,
                "roles": user_roles or [],
            }

    if not (deliver_id and str(deliver_id).strip()):
        deliver_id = str(uuid.uuid4())
    else:
        deliver_id = str(deliver_id).strip()

    command_params = (params or {}).copy()
    command_params.pop("deliver_id", None)

    asyncio.create_task(
        _run_async_command_and_notify(
            command_class,
            command_name,
            command_params,
            context,
            deliver_id,
            logger,
        )
    )

    return {
        "accepted": True,
        "deliver_id": deliver_id,
        "message": "Command accepted; result will be delivered via WebSocket",
    }
