#!/usr/bin/python3

# SPDX-FileCopyrightText: 2009 Fermi Research Alliance, LLC
# SPDX-License-Identifier: Apache-2.0

"""Command entry_ls, for listing and printing job log information for a specific entry.

This script is used to list and display error/output files for a given entry,
user and date. It parses an XML file that wraps the log file lines and prints
out details based on various command-line options.

The script requires the environment variable GLIDEIN_FACTORY_DIR to be set to
the gwms factory instance location (or it must be run in that directory).

Usage:
    entry_ls [OPTIONS] ENTRY USER DATE

Options:
  -o           Append .out extension instead of .err extension.
  -t           Show only jobs where condor_started == 'True'.
  -f           Show only jobs where condor_started == 'False'.
  -e HOUR      Show jobs that ended before HOUR of the day (0-23).
  -E HOUR      Show jobs that ended on or after HOUR of the day (0-23).
  -d DURATION  Show jobs that lasted up to DURATION seconds.
  -D DURATION  Show jobs that lasted at least DURATION seconds.
  -h           Display this help message.
"""

import getopt
import os
import stat
import sys
import tempfile
import time
import xml.parsers.expat


# assumes the following globals are defined:
# match_exp
# gfactory_dir
# user
# entry
# suffix
# long
def start_element(name, attrs):
    """Handle a start element event from the XML parser.

    This function is registered as the StartElementHandler for the expat
    XML parser. It evaluates a global expression (compiled in 'match_exp') against
    the element attributes. If the expression evaluates to True, it constructs a log file
    path using globals and prints information about the log file. If the file does not exist,
    an error is written to stderr.

    Args:
        name (str): The name of the XML element.
        attrs (dict): A dictionary of attributes for the element.
    """
    # if name == 'job' and attrs['username'] == user:
    if eval(match_exp):
        cluster, proc = attrs["id"].split(".")
        logname = os.path.join(
            gfactory_dir, "client_log/user_%s/entry_%s/job.%i.%i.%s" % (user, entry, int(cluster), int(proc), suffix)
        )
        try:
            if long:
                s = os.stat(logname)
                print(time.ctime(s[stat.ST_MTIME]), "%10i" % s[stat.ST_SIZE], "%6o" % s[stat.ST_MODE], logname)
            else:
                print(logname)
        except OSError:
            sys.stderr.write("ERROR: The log %s does not exist.\n" % logname)


def print_help():
    """Print the usage message for the script.

    This function outputs a detailed help message explaining how to use the
    script and what options are available.
    """
    print(
        """\
Usage: entry_ls [OPTIONS] ENTRY USER DATE
where DATE is of the form YYYYMMDD

lists error / output files for the entry and user-specified constrained by
the following OPTIONS.

-o           append .out instead of .err extension
-t show      jobs where condor_started == 'True'
-f show      jobs where condor_started == 'False'
-e HOUR      show jobs that ended before HOUR of the day (0-23)
-E HOUR      show jobs that ended on or after HOUR of the day (0-23)
-d DURATION  show jobs that lasted up to DURATION seconds
-D DURATION  show jobs that lasted at least DURATION seconds

Requires the env variable GLIDEIN_FACTORY_DIR to be set to the
gwms factory instance location (or to be run in that directory)
"""
    )


if __name__ == "__main__":
    try:
        opts, args = getopt.getopt(sys.argv[1:], "holtfe:E:d:D:")
    except Exception:
        print_help()
        sys.exit(1)

    if len(args) != 3:
        print_help()
        sys.exit(1)
    entry, user, date = args

    suffix = "err"
    match_exp = "name == 'job' and attrs['username'] == user"
    start_exp = None
    end_exp = None
    dur_exp = None
    long = False

    for opt in opts:
        if opt[0] == "-h":
            print_help()
            sys.exit(0)
        elif opt[0] == "-o":
            suffix = "out"
        elif opt[0] == "-l":
            long = True
        elif opt[0] == "-t":
            start_exp = "attrs['condor_started'] == 'True'"
        elif opt[0] == "-f":
            start_exp = "attrs['condor_started'] == 'False'"
        elif opt[0] == "-e":
            end_exp = "int(attrs['terminated'].split('T')[1].split(':')[0]) < %i" % int(opt[1])
        elif opt[0] == "-E":
            end_exp = "int(attrs['terminated'].split('T')[1].split(':')[0]) >= %i" % int(opt[1])
        elif opt[0] == "-d":
            dur_exp = "int(attrs['duration']) <= %i" % int(opt[1])
        elif opt[0] == "-D":
            dur_exp = "int(attrs['duration']) >= %i" % int(opt[1])

    if start_exp is not None:
        match_exp = f"{match_exp} and {start_exp}"
    if end_exp is not None:
        match_exp = f"{match_exp} and {end_exp}"
    if dur_exp is not None:
        match_exp = f"{match_exp} and {dur_exp}"

    # print match_exp
    match_exp = compile(match_exp, "<string>", "eval")

    # Try GLIDEIN_FACTORY_DIR env var first
    if "GLIDEIN_FACTORY_DIR" in os.environ:
        gfactory_dir = os.environ["GLIDEIN_FACTORY_DIR"]
    else:
        if os.path.isdir("/var/lib/gwms-factory/work-dir"):
            # Assume RPM is installed
            gfactory_dir = "/var/lib/gwms-factory/work-dir"
        else:
            gfactory_dir = "."

    gfactory_log_dir = os.path.join(gfactory_dir, "log")
    if not os.path.isdir(gfactory_log_dir):
        sys.stderr.write("The directory %s does not exist.\n" % gfactory_log_dir)
        sys.stderr.write("Please set GLIDEIN_FACTORY_DIR to the factory instance directory and re-try.\n")
        sys.exit(1)

    # RPM has another layer of log directories
    if os.path.realpath(gfactory_dir) == os.path.realpath("/var/lib/gwms-factory/work-dir"):
        gfactory_log_dir = os.path.join(gfactory_log_dir, "server")

    if entry[:6] == "entry_":
        sys.stderr.write('WARNING: entry should not start with "entry_"\n')
        entry = entry[6:]

    gfactory_entry_dir = os.path.join(gfactory_log_dir, "entry_" + entry)
    if not os.path.isdir(gfactory_entry_dir):
        sys.stderr.write("The directory %s does not exist.\n" % gfactory_entry_dir)
        sys.stderr.write("Please set GLIDEIN_FACTORY_DIR to the factory instance directory and re-try.\n")
        sys.exit(1)

    completed_fname = os.path.join(gfactory_entry_dir, "completed_jobs_%s.log" % (date))
    try:
        fin = open(completed_fname)
    except OSError:
        sys.stderr.write(
            f"WARNING: {completed_fname} did not exist.  No jobs completed yet (or this is the wrong directory).\n"
        )
        sys.exit(1)

    # write into a temporary xml file and add root tags to make expat happy
    tmp = tempfile.TemporaryFile()
    tmp.write("<r>")

    for line in fin:
        tmp.write(line)
    fin.close()

    tmp.write("</r>")

    tmp.flush()

    tmp.seek(0)

    xmlparser = xml.parsers.expat.ParserCreate()
    xmlparser.StartElementHandler = start_element

    xmlparser.ParseFile(tmp)

    tmp.close()
