from __future__ import annotations

from typing import Any, Mapping, Sequence

from celery import current_app
from celery.app.base import Celery
from celery.result import AsyncResult
import time
from lts_celery_utils import logger


def reliable_send_task(
    task_name: str,
    args: Sequence[Any] | None = None,
    kwargs: Mapping[str, Any] | None = None,
    queue: str | None = None,
    max_retries: int = 3,
    retry_backoff_base: float = 2.0,
    app: Celery | None = None,
    delivery_mode: int = 2,
) -> AsyncResult:
    """
    Send a Celery task with retry logic.

    Args:
        task_name: Fully-qualified task name.
        args: Positional arguments for the task.
        kwargs: Keyword arguments for the task.
        queue: Optional queue name to route the task to.
        max_retries: Maximum number of attempts before raising.
        retry_backoff_base: Base for exponential backoff between retries.
        app: Optional Celery app instance; defaults to current_app.
        delivery_mode: AMQP delivery mode. Use 2 for persistent messages.

    Returns:
        AsyncResult for the enqueued task.

    Raises:
        ValueError: If max_retries is less than 1.
        Exception: Re-raises the last exception after all retries fail.
    """
    if max_retries < 1:
        raise ValueError("max_retries must be >= 1")

    app_to_use = app or current_app
    args_to_use = tuple(args) if args is not None else ()
    kwargs_to_use = dict(kwargs) if kwargs is not None else {}

    last_exc: Exception | None = None
    for attempt in range(1, max_retries + 1):
        try:
            return app_to_use.send_task(
                task_name,
                args=args_to_use,
                kwargs=kwargs_to_use,
                queue=queue,
                delivery_mode=delivery_mode,
            )
        except Exception as exc:  # pragma: no cover - passthru of bkend errs
            logger.warning(f"Attempt {attempt} failed " +
                           f"for task {task_name}: {exc}")
            last_exc = exc
            if attempt < max_retries:
                if retry_backoff_base > 0:
                    time.sleep(retry_backoff_base ** (attempt - 1))
                continue
            raise

    if last_exc is not None:
        raise last_exc
    raise RuntimeError("reliable_send_task failed " +
                       "without raising an exception")
