"""Cron expression parsing and next-run-at calculation.

Uses the croniter library for robust 5-field cron parsing.
Falls back to basic validation if croniter is not available.
"""

from __future__ import annotations

from datetime import datetime, timezone

try:
    from croniter import CroniterBadCronError, croniter

    _CRONITER_AVAILABLE = True
except ImportError:
    _CRONITER_AVAILABLE = False


def validate_cron_expression(expression: str) -> bool:
    """Return True if the cron expression is valid."""
    if _CRONITER_AVAILABLE:
        return croniter.is_valid(expression)
    # Basic fallback: check that we have exactly 5 fields
    parts = expression.strip().split()
    return len(parts) == 5


def get_next_run_at(expression: str, after: datetime | None = None) -> datetime | None:
    """Return the next scheduled datetime for the given cron expression.

    Args:
        expression: Standard 5-field cron expression (minute hour day month weekday).
        after: Calculate next run after this datetime. Defaults to now (UTC).

    Returns:
        Next run datetime in UTC, or None if the expression is invalid or
        croniter is unavailable.
    """
    if not _CRONITER_AVAILABLE:
        return None

    if after is None:
        after = datetime.now(timezone.utc)

    # croniter works with naive datetimes; strip timezone for calculation
    after_naive = after.replace(tzinfo=None)

    try:
        cron = croniter(expression, after_naive)
        next_dt = cron.get_next(datetime)
        # Return as UTC-aware datetime
        return next_dt.replace(tzinfo=timezone.utc)
    except (CroniterBadCronError, ValueError):
        return None


def get_upcoming_runs(
    expression: str,
    count: int,
    after: datetime | None = None,
) -> list[datetime]:
    """Return the next `count` scheduled datetimes for a cron expression.

    Args:
        expression: Standard 5-field cron expression.
        count: Number of upcoming datetimes to return.
        after: Start calculating from this datetime. Defaults to now (UTC).

    Returns:
        List of upcoming run datetimes in UTC (may be empty if invalid).
    """
    if not _CRONITER_AVAILABLE:
        return []

    if after is None:
        after = datetime.now(timezone.utc)

    after_naive = after.replace(tzinfo=None)

    try:
        cron = croniter(expression, after_naive)
        result = []
        for _ in range(count):
            next_dt = cron.get_next(datetime)
            result.append(next_dt.replace(tzinfo=timezone.utc))
        return result
    except (CroniterBadCronError, ValueError):
        return []
