#!/usr/bin/env python3
# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""sdc - scheduler daemon controller.

usage: %prog [options] [xmlfile] [id]

This utility sends control messages to the scheduler daemon.

Arguments:
  xmlfile     XML file containing tasks to add (-a/--add only)
  id          id of task to delete (-d/--delete only)

Options specify actions; actions are mutually exclusive.  The default
action is -l/--listing.

"""

# Python imports
import os
import sys
import datetime
import optparse
import textwrap

# Sigma imports
from sigma.util import avalog, config
from sigma.util.errors import usage, error
from sigma.util.groupings import groupby
from sigma.sched import tasks, remote, timezones

# Global objects
options = config.Options()
parser = optparse.OptionParser(usage=__doc__.strip())
parser.add_option("-a", "--add", dest="action", action="store_const",
                  const="add", help="add a lists of tasks from an XML file")
parser.add_option("-d", "--delete", dest="action", action="store_const",
                  const="delete", help="delete a task by id")
parser.add_option("-l", "--listing", dest="action", action="store_const",
                  const="listing", help="list all tasks", default="listing")
parser.add_option("-p", "--ping", dest="action", action="store_const",
                  const="ping", help="ping the scheduler and print status")
parser.add_option("-s", "--stop", dest="action", action="store_const",
                  const="shutdown", help="stop the scheduler process")
parser.add_option("-r", "--restart", dest="action", action="store_const",
                  const="restart", help="restart the scheduler process")
parser.add_option("-S", "--server", dest="server", action="store",
                  help="the scheduler server address in the form host:port "
                       "(default taken from config file)",
                  default=options.scheduler)

# status message format
fmt = " utime: %10.3f\t stime: %10.3f\t\ncutime: %10.3f\tcstime: %10.3f"

# Days of the week, 0=Monday
weekdays = "Mon Tue Wed Thu Fri Sat Sun".split()

def main(args=None):
    # Parses the command line arguments
    opts, args = parser.parse_args(args)

    # Check argument count
    if args:
        if len(args) > 1:
            return usage("At most one positional argument allowed")
        if opts.action not in ("add", "delete"):
            return usage("Only -a/-add and -d/--delete "
                         "take a positional argument")
    else:
        if opts.action in ("add", "delete"):
            return usage("Options -a/-add and -d/--delete "
                         "require one positional argument")

    # Create the scheduler proxy
    sched = remote.Scheduler(opts.server)
    try:
        status = sched.ping()
    except Exception as err:
        return error("Can't reach scheduler at %s (%s)", opts.server, err)

    # Handle action
    avalog.setupLogging()
    if opts.action == "ping":
        # Use the status we collected earlier
        print("Status for scheduler at", opts.server)
        print(fmt % tuple(status[:4]))

    elif opts.action == "shutdown":
        count = sched.shutdown()
        if count >= 0:
            print("Scheduler at %s shut down (%s tasks canceled)." %
                  (opts.server, count))
        else:
            return error("Failed to stop scheduler at %s.", opts.server)

    elif opts.action == "restart":
        count = sched.restart()
        if count >= 0:
            print("Scheduler at %s restarted (%s tasks canceled)." %
                  (opts.server, count))
        else:
            return error("Failed to stop scheduler at %s.", opts.server)

    elif opts.action == "add":
        filename = args[0]
        try:
            f = open(filename)
        except IOError as err:
            return error("Can't open %s: %s", filename, err)
        xmltext = f.read()
        f.close()
        try:
            tasklist = tasks.parse_tasks(xmltext)
        except Exception as err:
            return error("Error parsing tasks: %s", err)
        count = sched.add(xmltext)
        if count >= 0: 
            print("Scheduler at %s added %d tasks." % (opts.server, count))
        else:
            return error("Failed to add tasks to scheduler at %s.",
                         opts.server)

    elif opts.action == "listing":
            taskdict = sched.listtasks()
            ids = sorted(taskdict.keys())
            print("Scheduler at", opts.server, "has", len(ids), "task(s)")
            print("%-16s %5s %10s %10s %10s %s" % ("ID", "FREQ",
                                                   "START", "END",
                                                   "LAST", "COMMAND"))
            for id in ids:
                task = taskdict[id]
                last = "-"
                if task.detail is not None:
                    dt = datetime.datetime.now(timezones.utc) - task.detail
                    if dt.days < 0:
                        last = "+%ds" % -(dt.seconds + dt.days*24*3600)
                    else:
                        minutes, seconds = divmod(dt.seconds, 60)
                        hours, minutes = divmod(minutes, 60)
                        days = dt.days
                        if days:
                            last = "-%dd,%dh" % (days, hours)
                        elif hours:
                            last = "-%dh,%dm" % (hours, minutes)
                        elif minutes:
                            last = "-%dm,%ds" % (minutes, seconds)
                        else:
                            last = "-%ds" % seconds
                start = task.start and task.start.date or "-"
                end = task.end and task.end.date or "-"
                freq = task.freq
                if freq is None:
                    freq = "-"
                elif freq % (24*3600) == 0:
                    freq = "%dd" % (freq // (24*3600))
                elif freq % 3600 == 0:
                    freq = "%dh" % (freq // 3600)
                elif freq % 60 == 0:
                    freq = "%dm" % (freq // 60)
                else:
                    freq = "%ds" % freq
                print("%-16s %5s %10s %10s %10s %s" % (id, freq,
                                                       start, end,
                                                       last, task.command))
                if task.schedule:
                    # Format the non-blackout periods.
                    # XXX Should collapse redundant items
                    # XXX Should join adjacent identical days
                    sched = []
                    for s in task.schedule:
                        if 0 <= s.day < 7:
                            start = s.start or 0
                            end = s.end or 24
                            sched.append((s.day, start, end))
                    sched.sort()
                    words = []
                    for day, group in groupby(sched, lambda x: x[0]):
                        hours = []
                        for day, start, end in group:
                            hours.append("%02d-%02d" % (start, end))
                        phrase = "%s %s" % (weekdays[day], ", ".join(hours))
                        words.append(phrase)
                    text = "; ".join(words)
                    indent = " "*17
                    print(textwrap.fill(text,
                                        initial_indent=indent,
                                        subsequent_indent=indent))
    elif opts.action == "delete":
        id = args[0]
        count = sched.delete(id)
        if count > 0:
            print("Scheduler at %s deleted %d task(s)." % (opts.server, count))
        else:
            return error("Failed to delete task at scheduler at %s.",
                         opts.server)

if __name__ == "__main__":
    sys.exit(main())
