import inspect
from functools import wraps
from typing import Any, Callable

from celery import current_app
from celery.schedules import crontab


def scheduler(
    name: str,
    minute: str = "*",
    hour: str = "*",
    day_of_week: str = "*",
    day_of_month: str = "*",
    month_of_year: str = "*",
) -> Callable[[Callable], Callable]:
    """Schedule a Celery task using crontab-like timing.

    Examples
    --------
    - `minute='*/15'`: Run every 15 minutes.
    - `hour='*/3'`: Run every 3 hours.
    - `day_of_week='mon-fri'`: Run on weekdays only.
    - `day_of_month='1-7,15-21'`: Run on the first and third weeks of the month.
    - `month_of_year='*/3'`: Run on the first month of each quarter.

    All time units can be specified with lists of numbers or crontab pattern strings for advanced scheduling.
    All specified time parts (minute, hour, day, etc.) must align for a task to run.
    """

    def decorator(task_func: Callable) -> Callable:
        @wraps(task_func)
        def scheduled_task(*args: Any, **kwargs: Any) -> Any:
            return task_func(*args, **kwargs)

        module = inspect.getmodule(task_func)
        if module is None:
            raise ValueError(f"Module for the task function {task_func.__name__} could not be found.")

        task_path = f"{module.__name__}.{task_func.__name__}"
        current_app.conf.beat_schedule[task_func.__name__] = {
            "name": name,
            "task": task_path,
            "schedule": crontab(
                minute=minute,
                hour=hour,
                day_of_month=day_of_month,
                month_of_year=month_of_year,
                day_of_week=day_of_week,
            ),
        }

        return scheduled_task

    return decorator