from collections.abc import Callable
from typing import Any

from ..models import Message
from ..utils.call import call_with_relevant_kwargs
from ..utils.http import DEFAULT_TIMEOUT, post, post_async
from .decorators import normalise_message
from .registry import register_provider
from .types import AsyncSendFn, SendFn


def make_provider(
    name: str,
    *,
    resolve_url: Callable[..., str],
    build_payload: Callable[..., dict[str, Any]],
    string_key: str | None = None,
) -> tuple[SendFn, AsyncSendFn]:
    """Register a provider and return its send functions.

    Builds and registers synchronous and asynchronous send functions from
    two pure functions. kwargs are forwarded selectively — each function
    receives only the parameters it declares.

    Args:
        name: Provider name, used as the registry key.
        resolve_url: Callable that resolves the destination URL from kwargs.
        build_payload: Callable that builds the request payload from a Message and kwargs.
        string_key: The payload key that holds the message body,
                    used when constructing a Message from a plain string response.

    Returns:
        Tuple of (send, async_send) registered under the given name.

    Raises:
        ValueError: If a provider with this name is already registered.
    """

    @normalise_message
    def send(message: Message, *, timeout=DEFAULT_TIMEOUT, **kwargs) -> None:
        url = call_with_relevant_kwargs(resolve_url, **kwargs)
        payload = call_with_relevant_kwargs(build_payload, message=message, **kwargs)
        post(url, json=payload, timeout=timeout)

    @normalise_message
    async def async_send(message, *, timeout=DEFAULT_TIMEOUT, **kwargs) -> None:
        url = call_with_relevant_kwargs(resolve_url, **kwargs)
        payload = call_with_relevant_kwargs(build_payload, message=message, **kwargs)
        await post_async(url, json=payload, timeout=timeout)

    # Register send methods
    register_provider(name, send=send, async_send=async_send, string_key=string_key)

    return send, async_send
