#!/usr/bin/env python3

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

r"""This tool analyzes the matches between jobs and factory entries

Arguments:
   $1 = work dir
   $2 = group_name
   $3 = job search pattern (e.g. '234\.3')
"""

import os
import os.path
import re

# import signal
import sys
import traceback

from glideinwms.frontend import glideinFrontendConfig, glideinFrontendInterface, glideinFrontendLib
from glideinwms.lib import exprParser

# from glideinwms.frontend.glideinFrontendLib import getGlideinCpusNum


def print_match(job, glidein, match_ast, counts, level, countid):
    prefix = ""
    for lev in range(level):
        prefix += "  "

    match_obj = exprParser.exp_compile(match_ast)
    res = eval(match_obj)
    c = f"{countid}.{res}"
    if c not in counts:
        counts[c] = [0, exprParser.exp_unparse(match_ast)]
    counts[c][0] += 1
    print("%s%-10s %s" % (prefix, res, exprParser.exp_unparse(match_ast)))

    if isinstance(match_ast, exprParser.Or):
        # print
        print("  %s%-5s due to OR of" % (prefix, res))
        i = 0
        for n in match_ast.nodes:
            r = print_match(job, glidein, n, counts, level + 2, c + ".OR%i" % i)
            i += 1
            if r == True:  # noqa: E712  # Coming form eval
                break  # partial eval, exit at first True
    elif isinstance(match_ast, exprParser.And):
        # print
        print("  %s%-5s due to AND of" % (prefix, res))
        i = 0
        for n in match_ast.nodes:
            r = print_match(job, glidein, n, counts, level + 2, c + ".AND%i" % i)
            i += 1
            if r == False:  # noqa: E712  # Coming form eval
                break  # partial eval, exit at first False
    elif isinstance(match_ast, exprParser.Not):
        # print
        print("  %s%-5s due to NOT of" % (prefix, res))
        print_match(job, glidein, match_ast.expr, counts, level + 2, c + ".NOT")

    return res


############################################################
def do_work(elementDescript, paramsDescript, signatureDescript, job_filter):
    job_filter_obj = re.compile(job_filter)
    # query condor

    glidein_dict = {}
    factory_constraint = elementDescript.merged_data["FactoryQueryExpr"]
    factory_pools = elementDescript.merged_data["FactoryCollectors"]
    for factory_pool in factory_pools:
        factory_pool_node = factory_pool[0]
        factory_identity = factory_pool[1]
        my_identity_at_factory_pool = factory_pool[2]
        try:
            full_constraint = '(%s) && ((PubKeyType=?="RSA") && (GlideinAllowx509_Proxy=!=False))' % factory_constraint
            factory_glidein_dict = glideinFrontendInterface.findGlideins(
                factory_pool_node, None, signatureDescript.signature_type, full_constraint
            )
        except RuntimeError as e:
            if factory_pool_node is not None:
                print(f"Failed to talk to factory_pool {factory_pool_node}: {e}")
            else:
                print("Failed to talk to factory_pool: %s" % e)
            # failed to talk, like empty... maybe the next factory will have something
            factory_glidein_dict = {}

        for glidename in factory_glidein_dict.keys():
            if ("AuthenticatedIdentity" not in factory_glidein_dict[glidename]["attrs"]) or (
                factory_glidein_dict[glidename]["attrs"]["AuthenticatedIdentity"] != factory_identity
            ):
                print(f"Found an untrusted factory {glidename} at {factory_pool_node}; ignoring.")
                if "AuthenticatedIdentity" in factory_glidein_dict[glidename]["attrs"]:
                    print(
                        "Found an untrusted factory %s at %s; identity mismatch '%s'!='%s'"
                        % (
                            glidename,
                            factory_pool_node,
                            factory_glidein_dict[glidename]["attrs"]["AuthenticatedIdentity"],
                            factory_identity,
                        )
                    )
            else:
                glidein_dict[(factory_pool_node, glidename, my_identity_at_factory_pool)] = factory_glidein_dict[
                    glidename
                ]

    ## schedd
    condorq_format_list = elementDescript.merged_data["JobMatchAttrs"]
    condorq_dict = glideinFrontendLib.getCondorQ(
        elementDescript.merged_data["JobSchedds"], elementDescript.merged_data["JobQueryExpr"], condorq_format_list
    )

    # Match
    match_ast = exprParser.exp_parse(elementDescript.merged_data["MatchExpr"])
    sk = condorq_dict.keys()
    sk.sort()
    for schedd in sk:
        condorq = condorq_dict[schedd]
        condorq_data = condorq.fetchStored()
        jk = condorq_data.keys()
        jk.sort()
        for jid in jk:
            job = condorq_data[jid]
            t = "%s#%i.%i" % (schedd, jid[0], jid[1])

            if job_filter_obj.search(t) is None:
                continue  # no matching

            print()
            print("-=-=-=-=-=-=-=-=-=-")
            print("Job %s" % t)

            match_counts = {}
            for glidename in glidein_dict:
                glidein = glidein_dict[glidename]

                print("-------------")
                print(f"{t} vs {glidename[1]}")
                print("job:     %s" % job)
                glideattr = {}
                for a in elementDescript.merged_data.get("FactoryMatchAttrs", []):
                    if a[0] in glidein["attrs"]:
                        glideattr[a[0]] = glidein["attrs"][a[0]]
                print("glidein: %s" % glideattr)
                print("=")

                print_match(job, glidein, match_ast, match_counts, 1, t)

                pass

            print()
            print("======================")
            print("Summary for %s" % t)
            print("job:     %s" % job)
            ks = match_counts.keys()
            ks = sorted(ks, key=lambda x: (len(x), x))  # sort first by length, then by content
            for k in ks:
                print("%-80s %10i %s" % (k, match_counts[k][0], match_counts[k][1]))
            pass
        pass

    return


############################################################
def main(work_dir, group_name, job_filter):
    elementDescript = glideinFrontendConfig.ElementMergedDescript(work_dir, group_name)

    paramsDescript = glideinFrontendConfig.ParamsDescript(work_dir, group_name)
    signatureDescript = glideinFrontendConfig.GroupSignatureDescript(work_dir, group_name)

    # set the condor configuration and GSI setup globally, so I don't need to worry about it later on
    os.environ["CONDOR_CONFIG"] = elementDescript.frontend_data["CondorConfig"]
    os.environ["_CONDOR_CERTIFICATE_MAPFILE"] = elementDescript.element_data["MapFile"]
    os.environ["X509_USER_PROXY"] = elementDescript.frontend_data["ClassAdProxy"]

    try:
        do_work(elementDescript, paramsDescript, signatureDescript, job_filter)
    except KeyboardInterrupt:
        print("Received signal...exit")
    except Exception:
        tb = traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])
        print("Exception occurred: %s" % tb)


############################################################
#
# S T A R T U P
#
############################################################

if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2], sys.argv[3])
