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

from typing_extensions import ParamSpec

P = ParamSpec("P")
R = TypeVar('R')

# Pool partagé pour éviter de créer un nouveau pool à chaque appel
_thread_pool = ThreadPoolExecutor()


def force_async(fn: Callable[P, R]) -> Callable[P, Coroutine[Any, Any, R]]:
    """
    Transforms a sync function to async function using threads.

    Args:
        fn: A synchronous function

    Returns:
        An async function with the same signature but returning a coroutine
    """

    @functools.wraps(fn)
    async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        loop = asyncio.get_event_loop()
        future = _thread_pool.submit(fn, *args, **kwargs)
        return await asyncio.wrap_future(future)

    return wrapper


def force_sync(fn: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, R]:
    """
    Transforms an async function to sync function.

    Args:
        fn: An async function

    Returns:
        A sync function with the same signature
    """

    @functools.wraps(fn)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        coro = fn(*args, **kwargs)
        return asyncio.run(coro)

    return wrapper
