"""Async execution utilities.

Provides a shared thread-backed executor abstraction and helpers for
running blocking callables without stalling the FastAPI event loop.
"""

from __future__ import annotations

import asyncio
import functools
from collections.abc import Callable, Coroutine
from concurrent.futures import ThreadPoolExecutor
from typing import Any, ParamSpec, TypeVar

from app.utils.logging_compat import get_logger

P = ParamSpec("P")
T = TypeVar("T")

log = get_logger(__name__)

DEFAULT_BLOCKING_EXECUTOR: ThreadPoolExecutor = ThreadPoolExecutor(
    max_workers=16,
    thread_name_prefix="bangui-blocking",
)


async def run_blocking(
    func: Callable[P, T],
    *args: P.args,
    executor: ThreadPoolExecutor | None = None,
    **kwargs: P.kwargs,
) -> T:
    """Run a blocking callable in the shared thread pool.

    Args:
        func: Blocking callable to execute.
        *args: Positional arguments for the callable.
        executor: Optional custom executor. Defaults to the shared pool.
        **kwargs: Keyword arguments for the callable.

    Returns:
        The callable return value.
    """
    loop = asyncio.get_running_loop()
    executor = DEFAULT_BLOCKING_EXECUTOR if executor is None else executor
    if kwargs:
        func = functools.partial(func, *args, **kwargs)
        return await loop.run_in_executor(executor, func)
    return await loop.run_in_executor(executor, func, *args)


async def logged_task(
    coro: Coroutine[Any, Any, Any],
    name: str,
) -> None:
    """Execute a coroutine with automatic exception logging.

    Wraps fire-and-forget tasks to ensure exceptions are always logged and
    do not become unhandled task exceptions. Use with asyncio.create_task():

        asyncio.create_task(
            logged_task(some_coroutine(), "task_name"),
            name="task_name"
        )

    Args:
        coro: Coroutine to execute.
        name: Task name for logging context.
    """
    try:
        await coro
    except Exception:  # noqa: BLE001
        log.exception("background_task_failed", task_name=name)
