##########################################################################
#
#  Copyright (c) 2026 - Datatailr Inc.
#  All Rights Reserved.
#
#  This file is part of Datatailr and subject to the terms and conditions
#  defined in 'LICENSE.txt'. Unauthorized copying and/or distribution
#  of this file, in parts or full, via any medium is strictly prohibited.
##########################################################################

from typing import Any
from datatailr.wrapper import dt__Job
import re


__CLIENT__ = dt__Job()


class Schedule:
    """
    Build a schedule object for batch/workflow jobs using friendly fields.

    Args:
        cron_expression: Raw cron expression to use (string).
        at_minutes: Specific minutes within the hour, e.g. [0, 30].
        every_minute: Run every N minutes.
        at_hours: Specific hours within the day, e.g. [0, 12].
        every_hour: Run every N hours.
        weekdays: Weekdays by name, e.g. ["Mon", "Wed", "Fri"].
        day_of_month: Day of the month (1-31).
        in_month: Months by name, e.g. ["Jan", "Jul"].
        every_month: Run every N months.
        timezone: Time zone name, e.g. "UTC".
        run_after_job_uuid: Job UUID to run after.
        run_after_job_name: Job name to run after.
        run_after_job_condition: Condition for dependency, e.g. "on failure".

    Examples:
        ```python
        schedule = Schedule(at_hours=[0])
        schedule = Schedule(at_minutes=[0, 30], weekdays=["Mon", "Wed", "Fri"])
        ```
    """

    def __init__(
        self,
        cron_expression: str = "",
        at_minutes: list[int] | None = None,
        every_minute: int | None = None,
        at_hours: list[int] | None = None,
        every_hour: int | None = None,
        weekdays: list[str] | None = None,
        day_of_month: int | None = None,
        in_month: list[str] | None = None,
        every_month: int | None = None,
        timezone: str | None = None,
        run_after_job_uuid: str | None = None,
        run_after_job_name: str | None = None,
        run_after_job_condition: str | None = None,
    ):
        """Initialize a Schedule.

        You can either provide a raw *cron_expression* or use the
        human-readable helper parameters (which are compiled into cron
        syntax automatically).

        Args:
            cron_expression: A raw cron string (e.g. ``"0 */2 * * *"``).
                If provided alongside helper parameters, the helpers take
                precedence.
            at_minutes: Specific minutes past the hour to run
                (e.g. ``[0, 30]`` for ``":00"`` and ``":30"``).
            every_minute: Run every *N* minutes (e.g. ``5`` for every
                5 minutes).
            at_hours: Specific hours of the day to run (0 -- 23).
            every_hour: Run every *N* hours.
            weekdays: Days of the week to run
                (e.g. ``["mon", "wed", "fri"]``).
            day_of_month: Day of the month to run (1 -- 31).
            in_month: Months to run in
                (e.g. ``["jan", "apr", "jul", "oct"]``).
            every_month: Run every *N* months.
            timezone: IANA timezone name (e.g. ``"America/New_York"``).
            run_after_job_uuid: UUID of a job that must complete before
                this schedule triggers.
            run_after_job_name: Name of a job that must complete before
                this schedule triggers.
            run_after_job_condition: Required completion status of the
                predecessor job (e.g. ``"success"``).

        Example:
            ```python
            from datatailr import Schedule
            # Every weekday at 08:00 and 16:00 UTC
            s = Schedule(at_hours=[8, 16], weekdays=["mon","tue","wed","thu","fri"])
            ```
        """
        self.at_minutes = at_minutes
        self.every_minute = every_minute
        self.at_hours = at_hours
        self.every_hour = every_hour
        self.weekdays = weekdays
        self.day_of_month = day_of_month
        self.in_month = in_month
        self.every_month = every_month
        self.timezone = timezone
        self.run_after_job_uuid = run_after_job_uuid
        self.run_after_job_name = run_after_job_name
        self.run_after_job_condition = run_after_job_condition
        self.schedule_expression = None
        self.cron_expression = cron_expression

        self.__is_set__ = False

    def __str__(self) -> str:
        self.__compile__()
        return self.cron_expression

    def __repr__(self) -> str:
        self.__compile__()
        return f"Schedule(cron_expression={self.cron_expression}, timezone={self.timezone}) - {self.schedule_expression}"

    def __setattr__(self, name: str, value: Any) -> None:
        super().__setattr__(name, value)
        if name in [
            "at_minutes",
            "at_hours",
            "every_minute",
            "every_hour",
            "weekdays",
            "day_of_month",
            "in_month",
            "every_month",
        ]:
            self.__is_set__ = False

    def __compile__(self):
        if self.__is_set__:
            return
        argument_name = [
            "at_minutes",
            "at_hours",
            "every_minute",
            "every_hour",
            "weekdays",
            "day_of_month",
            "in_month",
            "every_month",
            "run_after_job_uuid",
            "run_after_job_name",
            "run_after_job_condition",
        ]
        arguments = {}

        for key in argument_name:
            if hasattr(self, key) and getattr(self, key) is not None:
                value = getattr(self, key)
                if isinstance(value, list):
                    value = ",".join(map(str, value))
                arguments[key] = value

        result = __CLIENT__.run("", cron_string=True, **arguments)
        match = re.match(r"^(.*?)\s*\((.*?)\)$", result)
        if match:
            cron_expression, schedule_expression = match.groups()
            part = cron_expression.strip()
            # dt in some cases prints a 6-field cron; prepend "0 " only if we get a 5-field cron
            nfields = len(part.split())
            if nfields == 5:
                self.cron_expression = "0 " + part
            else:
                self.cron_expression = part
            self.schedule_expression = schedule_expression.strip()
        self.__is_set__ = True

    def get_cron_string(self) -> str:
        """
        Return the compiled cron string.

        Returns:
            Cron string (str).

        Examples:
            >>> Schedule(
            ...     at_minutes=[0, 15, 30, 45],
            ...     at_hours=[0, 12],
            ...     weekdays=["Mon", "Wed", "Fri"],
            ...     day_of_month=15,
            ...     in_month=["Jan", "Jul"],
            ... ).get_cron_string()
            '0 0,15,30,45 0,12 15 1,7 1,3,5'
        """
        self.__compile__()
        return self.cron_expression
