import sys
from itoolkit import (
    iToolKit,
    iCmd5250,
    iPgm,
    iCmd,
    iSh,
    iData,
    iDS,
    iSqlPrepare,
    iSqlParm,
    iSqlExecute,
    iSqlFetch,
    iSqlFree,
)
from itoolkit.transport import DatabaseTransport, DirectTransport
import pyodbc
from pprint import pprint
from datetime import datetime, timedelta

"""
C types          RPG types                     XMLSERVICE types                                   SQL types
===============  ============================  ================================================   =========
int8/byte        D myint8    3i 0              <data type='3i0'/>                                 TINYINT   (unsupported DB2)
int16/short      D myint16   5i 0 (4b 0)       <data type='5i0'/>                                 SMALLINT
int32/int        D myint32  10i 0 (9b 0)       <data type='10i0'/>                                INTEGER
int64/longlong   D myint64  20i 0              <data type='20i0'/>                                BIGINT
uint8/ubyte      D myuint8   3u 0              <data type='3u0'/>
uint16/ushort    D myuint16  5u 0              <data type='5u0'/>
uint32/uint      D myuint32 10u 0              <data type='10u0'/>
uint64/ulonglong D myuint64 20u 0              <data type='20u0'/>
char             D mychar   32a                <data type='32a'/>                                 CHAR(32)
varchar2         D myvchar2 32a   varying      <data type='32a' varying='on'/>                    VARCHAR(32)
varchar4         D myvchar4 32a   varying(4)   <data type='32a' varying='4'/>
packed           D mydec    12p 2              <data type='12p2'/>                                DECIMAL(12,2)
zoned            D myzone   12s 2              <data type='12s2'/>                                NUMERIC(12,2)
float            D myfloat   4f                <data type='4f2'/>                                 FLOAT
real/double      D myreal    8f                <data type='8f4'/>                                 DOUBLE
binary           D mybin    (any)              <data type='9b'>F1F2F3</data>                      BINARY
hole (no out)    D myhole   (any)              <data type='40h'/>
boolean          D mybool    1n                <data type='4a'/>                                  CHAR(4)
time             D mytime     T   timfmt(*iso) <data type='8A'>09.45.29</data>                    TIME
timestamp        D mystamp    Z                <data type='26A'>2011-12-29-12.45.29.000000</data> TIMESTAMP
date             D mydate     D   datfmt(*iso) <data type='10A'>2009-05-11</data>                 DATE
"""


class CommitMode:
    """
    Copied from : https://gist.github.com/mbiette/6cfd5b2dc2624c094575
    """

    NONE = 0  # Commit immediate (*NONE)  --> QSQCLIPKGN
    CS = 1  # Read committed (*CS)        --> QSQCLIPKGS
    CHG = 2  # Read uncommitted (*CHG)    --> QSQCLIPKGC
    ALL = 3  # Repeatable read (*ALL)     --> QSQCLIPKGA
    RR = 4  # Serializable (*RR)          --> QSQCLIPKGL


class ConnectionType:
    """
    Copied from : https://gist.github.com/mbiette/6cfd5b2dc2624c094575
    """

    ReadWrite = 0  # Read/Write (all SQL statements allowed)
    ReadCall = 1  # Read/Call (SELECT and CALL statements allowed)
    Readonly = 2  # Read-only (SELECT statements only)


class AS400:
    count = 0

    def __init__(
        self, address: str = "localhost", username: str = "", password: str = ""
    ):
        self.count += 1

        self.__address = address
        self.__username = username
        self.__password = password

        self.__CPUUsage = None
        self.__OSName = None
        self.__OSVersion = None
        self.__OSRelease = None
        self.__hostName = None
        self.__CPUSCount = None
        self.__configuredCPUS = None
        self.__configuredMemory = None
        self.__totalMemory = None

        if self.__address == "localhost" or self.__address == "127.0.0.1":
            self.itransport = DirectTransport()
        else:
            self.connection = pyodbc.connect(
                f"""DRIVER=iSeries Access ODBC Driver;
                    SYSTEM={self.__address};
                    UID={self.__username};
                    PWD={self.__password};
                    TRANSLATE=1;
                    CommitMode={CommitMode.NONE};
                    ConnectionType={ConnectionType.ReadWrite};"""
            )

            self.itransport = DatabaseTransport(self.connection)
        self.itool = iToolKit()

        self.getSystemInformation()

    def __del__(self):
        self.count -= 1

    def getCurrentJobID(self, debug: bool = False):
        """search for QUSRJOBI on https://www.ibm.com/docs/en/i/7.4?topic=interfaces-api-finder"""
        self.itool.add(
            iPgm(
                "requestQUSRJOBI",
                "QUSRJOBI",
                {"error": "on" if debug else "fast", "hex": "on"},
            )
            .addParm(
                iDS("resultQUSRJOBI", {"len": "sstlen"})
                .addData(iData("Bytes returned", "10i0", ""))
                .addData(iData("Bytes available", "10i0", 8))
                .addData(iData("Job name", "10A", ""))
                .addData(iData("User name", "10A", ""))
                .addData(iData("Job number", "6A", ""))
                .addData(iData("Internal job identifier", "16b", ""))
                .addData(iData("Job status", "10A", ""))
                .addData(iData("Job type", "1A", ""))
                .addData(iData("Job subtype", "1A", ""))
                .addData(iData("Reserved", "2A", ""))
                .addData(iData("Run priority (job)", "10i0", 0))
                .addData(iData("Time slice", "10i0", 8))
                .addData(iData("Default wait", "10i0", 8))
                .addData(iData("Purge", "10A", ""))
            )
            .addParm(
                iData("Length of receiver variable", "10i0", "", {"setlen": "sstlen"})
            )
            .addParm(iData("Format name", "8A", "JOBI0100"))
            .addParm(iData("Qualified job name", "26A", "*"))
            .addParm(iData("Internal job identifier", "16A", ""))
            .addParm(
                iDS("ERRC0100_T", {"len": "errlen"})
                .addData(iData("errRet", "10i0", ""))
                .addData(iData("errAvl", "10i0", ""))
                .addData(iData("errExp", "7a", "", {"setlen": "errlen"}))
                .addData(iData("errRsv", "1a", ""))
            )
        )

        self.itool.call(self.itransport)

        requestResult = self.itool.dict_out("requestQUSRJOBI")
        self.itool.clear()

        if "error" in requestResult:
            raise Exception(requestResult["error"])

        return {
            "Job name": requestResult["resultQUSRJOBI"]["Job name"],
            "User name": requestResult["resultQUSRJOBI"]["User name"],
            "Job number": requestResult["resultQUSRJOBI"]["Job number"],
        }

    def getWRKSYSSTS(self):
        self.itool.add(iCmd5250("wrksyssts", "WRKSYSSTS"))
        self.itool.call(self.itransport)

        pprint(self.itool.dict_out("wrksyssts"))
        self.itool.clear()

    def getQWCRSSTS(
        self, resetInput: bool = False, format: str = "SSTS0220", debug: bool = False
    ):
        """search for QWCRSSTS on https://www.ibm.com/docs/en/i/7.4?topic=interfaces-api-finder"""
        if format == "SSTS0220":
            self.itool.add(
                iPgm("qwcrssts", "QWCRSSTS", {"error": "on" if debug else "fast"})
                .addParm(
                    iDS("qwcrssts", {"len": "sstlen"})
                    # Bytes available
                    .addData(iData("Avl", "10i0", ""))
                    # Bytes returned
                    .addData(iData("Ret", "10i0", ""))
                    # Current date and time
                    .addData(iData("CDT", "8b", ""))
                    # System name
                    .addData(iData("SN", "8A", ""))
                    # Elapsed time
                    .addData(iData("ET", "6A", ""))
                    # Restricted state flag
                    .addData(iData("RF", "1A", ""))
                    # Processor sharing attribute
                    .addData(iData("PSA", "1A", ""))
                    # % processing unit used
                    .addData(iData("PUU", "10i0", ""))
                    # Jobs in system
                    .addData(iData("JIS", "10i0", ""))
                    # Number of partitions
                    .addData(iData("NPA", "10i0", ""))
                    # Current processing capacity
                    .addData(iData("CPC", "10i0", ""))
                    # Number of processors
                    .addData(iData("NPR", "10i0", ""))
                    # Active jobs in system
                    .addData(iData("AJS", "10i0", ""))
                    # Active threads in system
                    .addData(iData("ATS", "10i0", ""))
                    # Maximum jobs in system
                    .addData(iData("MJS", "10i0", ""))
                )
                .addParm(iData("rcvlen", "10i0", "", {"setlen": "sstlen"}))
                .addParm(iData("fmtnam", "8a", format))
                .addParm(iData("rstipt", "10a", "*YES " if resetInput else "*NO "))
                .addParm(
                    iDS("ERRC0100_T", {"len": "errlen"})
                    .addData(iData("errRet", "10i0", ""))
                    .addData(iData("errAvl", "10i0", ""))
                    .addData(iData("errExp", "7a", "", {"setlen": "errlen"}))
                    .addData(iData("errRsv", "1a", ""))
                )
            )
        elif format == "SSTS0200":
            self.itool.add(
                iPgm("qwcrssts", "QWCRSSTS", {"error": "on" if debug else "fast"})
                .addParm(
                    iDS("qwcrssts", {"len": "sstlen"})
                    .addData(iData("Avl", "10i0", ""))  # Bytes available
                    .addData(iData("Ret", "10i0", ""))  # Bytes returned
                    # Current date and time
                    .addData(iData("CDT", "8b", ""))
                    # System name
                    .addData(iData("SN", "8A", ""))
                    # Elapsed time
                    .addData(iData("ET", "6A", ""))
                    # Restricted state flag
                    .addData(iData("RSF", "1A", ""))
                    # Reserved
                    .addData(iData("R1", "1A", ""))
                    # % processing unit used
                    .addData(iData("PUU", "10i0", ""))
                    # Jobs in system
                    .addData(iData("JIS", "10i0", ""))
                    # % permanent addresses
                    .addData(iData("NPA", "10i0", ""))
                    # % temporary addresses
                    .addData(iData("NTA", "10i0", ""))
                    # System ASP
                    .addData(iData("SASP", "10i0", ""))
                    # % system ASP used
                    .addData(iData("NASP", "10i0", ""))
                    # Total auxiliary storage
                    .addData(iData("TAS", "10i0", ""))
                    # Current unprotected storage used
                    .addData(iData("CUSU", "10i0", ""))
                    # Maximum unprotected storage used
                    .addData(iData("MUSU", "10i0", ""))
                    # % DB capability
                    .addData(iData("DBC", "10i0", ""))
                    # Main storage size
                    .addData(iData("MSZ", "10i0", ""))
                    # Number of partitions
                    .addData(iData("NP", "10i0", ""))
                    # Partition identifier
                    .addData(iData("PI", "10i0", ""))
                    # Reserved
                    .addData(iData("R2", "10i0", ""))
                    # Current processing capacity
                    .addData(iData("CPC", "10i0", ""))
                    # Processor sharing attribute
                    .addData(iData("PSA", "1A", ""))
                    # Reserved
                    .addData(iData("PSA", "3A", ""))
                    # Number of processors
                    .addData(iData("NPR", "10i0", ""))
                    # Active jobs in system
                    .addData(iData("AJS", "10i0", ""))
                    # Active threads in system
                    .addData(iData("ATS", "10i0", ""))
                    # Maximum jobs in system
                    .addData(iData("MJS", "10i0", ""))
                    # % temporary 256MB segments used
                    .addData(iData("NTMBSU", "10i0", ""))
                    # % temporary 4GB segments used
                    .addData(iData("NTGBSU", "10i0", ""))
                    # % permanent 256MB segments used
                    .addData(iData("NPMBSU", "10i0", ""))
                    # % permanent 4GB segments used
                    .addData(iData("NPGBSU", "10i0", ""))
                    # % current interactive performance
                    .addData(iData("NCIP", "10i0", ""))
                    # % uncapped CPU capacity used
                    .addData(iData("NUCPUCU", "10i0", ""))
                    # % shared processor pool used
                    .addData(iData("NSPPU", "10i0", ""))
                    # Main storage size(long)
                    .addData(iData("MSS", "20u0", ""))
                )
                .addParm(iData("rcvlen", "10i0", "", {"setlen": "sstlen"}))
                .addParm(iData("fmtnam", "8a", format))
                .addParm(iData("rstipt", "10a", "*YES " if resetInput else "*NO "))
                .addParm(
                    iDS("ERRC0100_T", {"len": "errlen"})
                    .addData(iData("errRet", "10i0", ""))
                    .addData(iData("errAvl", "10i0", ""))
                    .addData(iData("errExp", "7a", "", {"setlen": "errlen"}))
                    .addData(iData("errRsv", "1a", ""))
                )
            )

        self.itool.call(self.itransport)

        qwcrssts = self.itool.dict_out("qwcrssts")
        self.itool.clear()
        if "success" in qwcrssts:
            qwcrssts_result = qwcrssts["qwcrssts"]
            if debug:
                print(qwcrssts["success"])
                print("Length of receiver variable      : " + qwcrssts["rcvlen"])
                print("Format name                      : " + qwcrssts["fmtnam"])
                print("Reset status statistics          : " + qwcrssts["rstipt"])
                print("-" * 10)
                print("\tBytes available                : " + qwcrssts_result["Avl"])
                print("\tBytes returned                 : " + qwcrssts_result["Ret"])
                print(
                    "\tCurrent date and time          : "
                    + str(self.convertDTStoTimestamp(qwcrssts_result["CDT"]))
                )
                print("\tSystem name                    : " + qwcrssts_result["SN"])
                print("\tElapsed time                   : " + qwcrssts_result["ET"])
                print("\tRestricted state flag          : " + qwcrssts_result["RF"])
                print("\tProcessor sharing attribute    : " + qwcrssts_result["PSA"])
                print("\t% processing unit used         : " + qwcrssts_result["PUU"])
                print("\tJobs in system                 : " + qwcrssts_result["JIS"])
                print("\tNumber of partitions           : " + qwcrssts_result["NPA"])
                print("\tCurrent processing capacity    : " + qwcrssts_result["CPC"])
                print("\tNumber of processors           : " + qwcrssts_result["NPR"])
                print("\tActive jobs in system          : " + qwcrssts_result["AJS"])
                print("\tActive threads in system       : " + qwcrssts_result["ATS"])
                print("\tMaximum jobs in system         : " + qwcrssts_result["MJS"])
            else:
                self.__CPUUsage = int(qwcrssts_result["PUU"]) / 10
                if format == "SSTS0200":
                    self.__ASPUsage = float(qwcrssts_result["NASP"]) / 10000
                    self.__activeJobsInSystem = int(qwcrssts_result["JIS"])
                    self.__maximumJobsInSystem = int(qwcrssts_result["MJS"])
                self.__cpuQuantity = qwcrssts_result["NPR"]
                self.__currentProcessingCapacity = qwcrssts_result["CPC"]

                return qwcrssts["qwcrssts"]
        else:
            pprint(qwcrssts)

    def getListOfSpoolFiles(
        self,
        userName: str = "*ALL",
        jobName: str = "*ALL",
        formType: str = "EMail!ERR",
        startDate: datetime = datetime.combine(datetime.today(), datetime.min.time()),
        endDate: datetime = datetime.now(),
        debug: bool = False,
    ):
        self.itool.add(
            iSqlPrepare(
                "listOfSpoolFiles",
                f"""SELECT * FROM TABLE(QSYS2.SPOOLED_FILE_INFO(USER_NAME => '{userName}', JOB_NAME => '{jobName}', FORM_TYPE => '{formType}', STARTING_TIMESTAMP => '{startDate.strftime("%Y-%m-%d-%H.%M.%S")}', ENDING_TIMESTAMP => '{endDate.strftime("%Y-%m-%d-%H.%M.%S")}'));""",
                {"error": "on" if debug else "fast"},
            )
        )

        self.itool.add(iSqlExecute("exec", {"error": "on" if debug else "fast"}))

        self.itool.add(iSqlFetch("fetch", {"error": "on" if debug else "fast"}))
        self.itool.add(iSqlFree("free", {"error": "on" if debug else "fast"}))

        self.itool.call(self.itransport)

        requestResult = self.itool.dict_out("fetch")
        self.itool.clear()

        if "error" in requestResult:
            if "02000:100:" in requestResult["xmlhint"]:
                return []
            print(requestResult["error"], file=sys.stderr, flush=True)
            raise Exception(requestResult["error"])
        else:
            if type(requestResult["row"]) is dict:
                return [requestResult["row"]]
            else:
                return requestResult["row"]

    def getSpoolFileData(
        self, qualifiedJobName: str, spoolFileName: str = "", debug: bool = False
    ):
        self.itool.add(
            iCmd(
                "cpysplf",
                f"CPYSPLF FILE({spoolFileName}) TOFILE(*TOSTMF) JOB({qualifiedJobName})  SPLNBR(*LAST) TOSTMF('/tmp/{qualifiedJobName.replace('/', '_')}_{spoolFileName}.txt')",
            )
        )
        self.itool.add(
            iSh(
                "catSPLf",
                f"iconv -f IBM-297 -t UTF-8 /tmp/{qualifiedJobName.replace('/', '_')}_{spoolFileName}.txt",
            )
        )
        self.itool.add(
            iSh(
                "rmSPLf",
                f"rm /tmp/{qualifiedJobName.replace('/', '_')}_{spoolFileName}.txt",
            )
        )

        self.itool.call(self.itransport)

        requestResult = self.itool.dict_out("catSPLf")
        self.itool.clear()

        if "error" in requestResult:
            if "02000:100:" in requestResult["xmlhint"]:
                return []
            print(requestResult["error"], file=sys.stderr, flush=True)
            raise Exception(requestResult["error"])
        else:
            return requestResult["catSPLf"]

    def getQWCCVTDT(
        self,
        inputVar: str,
        inputFormat: str = "*DTS",
        outputFormat: str = "*YYMD",
        debug: bool = False,
    ):
        """
        The DTS format is a representation on 8 Bytes of the microseconds elapsed since "1928-08-23 12:03:06.315".
        Each increments represents a value from 8 microseconds.
        This format will stop working on "2071-05-10 11:56:53.685".
        """

        iptVarCType = "17A"
        optVarCType = "17A"

        if inputFormat == "*DTS":
            iptVarCType = "8b"
        elif inputFormat == "*YYMD":
            iptVarCType = "17A"
        elif inputFormat == "*CURRENT":
            iptVarCType = "17A"

        if outputFormat == "*DTS":
            optVarCType = "8b"
        elif outputFormat == "*YYMD":
            optVarCType = "17A"

        self.itool.add(
            iPgm("qwccvtdt", "QWCCVTDT", {"error": "on" if debug else "fast"})
            .addParm(iData("iptfrm", "10A", inputFormat))
            .addParm(iData("iptVarC", iptVarCType, inputVar))
            .addParm(iData("optfrm", "10A", outputFormat))
            .addParm(iData("optVarC", optVarCType))
            .addParm(
                iDS("ERRC0100_t", {"len": "errlen"})
                .addData(iData("errRet", "10i0", ""))
                .addData(iData("errAvl", "10i0", ""))
                .addData(iData("errExp", "7A", "", {"setlen": "errlen"}))
                .addData(iData("errRsv", "1A", ""))
            )
        )
        self.itool.call(self.itransport)

        qwccvtdt = self.itool.dict_out("qwccvtdt")
        self.itool.clear()

        if "success" in qwccvtdt:
            if outputFormat == "*YYMD":
                return datetime.strptime(qwccvtdt["optVarC"], "%Y%m%d%H%M%S%f")
            else:
                return self.convertDTStoTimestamp(qwccvtdt["optVarC"])
        else:
            pprint(qwccvtdt)

    def convertDTStoTimestamp(self, DTS: str) -> datetime:
        DTS = int(DTS[0:13], 16)

        return datetime(1928, 8, 23, 12, 3, 6, 315000) + timedelta(microseconds=DTS)

    def robotTimestampToDateTime(self, timestamp: str) -> datetime:
        return datetime.strptime(timestamp[1:], "%y%m%d%H%M%S")

    def robotDateToDateTime(self, date: str) -> datetime:
        return datetime.strptime(date[1:], "%y%m%d")

    def robotTimeToDateTime(self, time: str) -> datetime:
        return datetime.strptime(time, "%H%M%S")

    def dateTimeToRobotTimestamp(self, timestamp: datetime) -> str:
        years = (timestamp.year - 1900) // 100
        return str(years) + timestamp.strftime("%y%m%d%H%M%S")

    def dateTimeToRobotDate(self, timestamp: datetime) -> str:
        years = (timestamp.year - 1900) // 100
        return years + timestamp.strftime("%y%m%d")

    def dateTimeToRobotTime(self, timestamp: datetime) -> str:
        return timestamp.strftime("%H%M%S")

    def getCompletedAndFailedJobs(self, overDays: int = 0, overHours: int = 12):
        endDate = datetime.now()

        self.itool.add(
            iSqlPrepare(
                "robotJobs",
                """
SELECT MSG.JOB_NAME,
    MSG.ROBOT_JOB_NUMBER,
    MSG.JOB_STATUS,
    MSG.OS_JOB_END_DATE,
    MSG.OS_JOB_END_TIME,
    MSG.TIME_STAMP,
    ROB.ROBOT_JOB_DESC,
    ROB.ROBOT_JOB_NOTES
FROM ROBOTLIB.RBTMSG AS MSG
    JOIN ROBOTLIB.RBTROB AS ROB
        ON MSG.ROBOT_JOB_NUMBER = ROB.ROBOT_JOB_NUMBER
WHERE (MSG.OS_JOB_END_DATE * 1000000) + MSG.OS_JOB_END_TIME >= ?
    AND (MSG.OS_JOB_END_DATE * 1000000) + MSG.OS_JOB_END_TIME <= ?
    AND MSG.JOB_STATUS IN ('T', 'W', 'C')
ORDER BY JOB_STATUS DESC, TIME_STAMP DESC;""",
            )
        )

        self.itool.add(
            iSqlExecute("exec")
            .addParm(
                iSqlParm(
                    "beginDate",
                    self.dateTimeToRobotTimestamp(
                        endDate + timedelta(days=overDays * -1, hours=overHours * -1)
                    ),
                )
            )
            .addParm(iSqlParm("endDate", self.dateTimeToRobotTimestamp(endDate)))
        )

        self.itool.add(iSqlFetch("fetch"))
        self.itool.add(iSqlFree("free"))

        self.itool.call(self.itransport)

        requestResult = self.itool.dict_out("fetch")
        self.itool.clear()

        result = []
        failedJobsNumbers = []
        warningJobsNumbers = []

        if "error" in requestResult:
            if "02000:100:" in requestResult["xmlhint"]:
                return []
            print(requestResult["error"], file=sys.stderr, flush=True)
            raise Exception(requestResult["error"])
        else:
            for row in requestResult["row"]:
                if row["JOB_STATUS"] == "T":
                    if row["ROBOT_JOB_NUMBER"] in failedJobsNumbers:
                        for job in result:
                            if job["ROBOT_JOB_NUMBER"] == row["ROBOT_JOB_NUMBER"]:
                                job["failCount"] += 1
                    else:
                        failedJobsNumbers.append(row["ROBOT_JOB_NUMBER"])
                        result.append(
                            {
                                "JOB_NAME": row["JOB_NAME"],
                                "ROBOT_JOB_NUMBER": row["ROBOT_JOB_NUMBER"],
                                "ROBOT_JOB_DESC": row["ROBOT_JOB_DESC"],
                                "ROBOT_JOB_NOTES": row["ROBOT_JOB_NOTES"],
                                "OS_JOB_END_DATE": str(
                                    self.robotDateToDateTime(
                                        row["OS_JOB_END_DATE"]
                                    ).date()
                                )
                                + " "
                                + str(
                                    self.robotTimeToDateTime(
                                        row["OS_JOB_END_TIME"].zfill(6)
                                    ).time()
                                ),
                                "TIME_STAMP": self.robotTimestampToDateTime(
                                    row["TIME_STAMP"]
                                ),
                                "failCount": 1,
                                "status": "terminated",
                            }
                        )

            for row in requestResult["row"]:
                if row["JOB_STATUS"] == "W":
                    if row["ROBOT_JOB_NUMBER"] in warningJobsNumbers:
                        for job in result:
                            if job["ROBOT_JOB_NUMBER"] == row["ROBOT_JOB_NUMBER"]:
                                job["failCount"] += 1
                    else:
                        warningJobsNumbers.append(row["ROBOT_JOB_NUMBER"])
                        result.append(
                            {
                                "JOB_NAME": row["JOB_NAME"],
                                "ROBOT_JOB_NUMBER": row["ROBOT_JOB_NUMBER"],
                                "ROBOT_JOB_DESC": row["ROBOT_JOB_DESC"],
                                "ROBOT_JOB_NOTES": row["ROBOT_JOB_NOTES"],
                                "OS_JOB_END_DATE": str(
                                    self.robotDateToDateTime(
                                        row["OS_JOB_END_DATE"]
                                    ).date()
                                )
                                + " "
                                + str(
                                    self.robotTimeToDateTime(
                                        row["OS_JOB_END_TIME"].zfill(6)
                                    ).time()
                                ),
                                "TIME_STAMP": self.robotTimestampToDateTime(
                                    row["TIME_STAMP"]
                                ),
                                "failCount": 1,
                                "status": "warning",
                            }
                        )

            for job in result:
                for row in requestResult["row"]:
                    if row["JOB_STATUS"] == "C":
                        if job["ROBOT_JOB_NUMBER"] == row["ROBOT_JOB_NUMBER"] and job[
                            "TIME_STAMP"
                        ] <= self.robotTimestampToDateTime(row["TIME_STAMP"]):
                            job["status"] = "Successfuly rerun"

            for job in result:
                job["TIME_STAMP"] = str(job["TIME_STAMP"])

        self.itool.clear()
        return result

    def getUsers(self, limit: int = None, offset: int = 0, debug: bool = False):
        self.itool.add(
            iSqlPrepare(
                "UsersGet",
                f"""
SELECT *
    FROM (
            SELECT
                   CASE GROUP_ID_NUMBER
                       WHEN 0 THEN 'USER'
                       ELSE 'GROUP'
                   END AS PROFILE_TYPE,
                   AUTHORIZATION_NAME,
                   PREVIOUS_SIGNON,
                   SIGN_ON_ATTEMPTS_NOT_VALID,
                   STATUS,
                   NETSERVER_DISABLED,
                   PASSWORD_CHANGE_DATE,
                   NO_PASSWORD_INDICATOR,
                   PASSWORD_LEVEL_0_1,
                   PASSWORD_LEVEL_2_3,
                   PASSWORD_EXPIRATION_INTERVAL,
                   DATE_PASSWORD_EXPIRES,
                   DAYS_UNTIL_PASSWORD_EXPIRES,
                   SET_PASSWORD_TO_EXPIRE,
                   USER_CLASS_NAME,
                   SPECIAL_AUTHORITIES,
                   GROUP_PROFILE_NAME,
                   SUPPLEMENTAL_GROUP_COUNT,
                   SUPPLEMENTAL_GROUP_LIST,
                   OWNER,
                   GROUP_AUTHORITY,
                   ASSISTANCE_LEVEL,
                   CURRENT_LIBRARY_NAME,
                   INITIAL_MENU_NAME,
                   INITIAL_MENU_LIBRARY_NAME,
                   INITIAL_PROGRAM_NAME,
                   INITIAL_PROGRAM_LIBRARY_NAME,
                   LIMIT_CAPABILITIES,
                   CAST(TEXT_DESCRIPTION AS VARCHAR(50) CCSID 297)
                       AS TEXT_DESCRIPTION,
                   DISPLAY_SIGNON_INFORMATION,
                   LIMIT_DEVICE_SESSIONS,
                   KEYBOARD_BUFFERING,
                   MAXIMUM_ALLOWED_STORAGE,
                   STORAGE_USED,
                   HIGHEST_SCHEDULING_PRIORITY,
                   JOB_DESCRIPTION_NAME,
                   JOB_DESCRIPTION_LIBRARY_NAME,
                   ACCOUNTING_CODE,
                   MESSAGE_QUEUE_NAME,
                   MESSAGE_QUEUE_LIBRARY_NAME,
                   MESSAGE_QUEUE_DELIVERY_METHOD,
                   MESSAGE_QUEUE_SEVERITY,
                   OUTPUT_QUEUE_NAME,
                   OUTPUT_QUEUE_LIBRARY_NAME,
                   PRINT_DEVICE,
                   SPECIAL_ENVIRONMENT,
                   ATTENTION_KEY_HANDLING_PROGRAM_NAME,
                   ATTENTION_KEY_HANDLING_PROGRAM_LIBRARY_NAME,
                   LANGUAGE_ID,
                   COUNTRY_OR_REGION_ID,
                   CHARACTER_CODE_SET_ID,
                   USER_OPTIONS,
                   SORT_SEQUENCE_TABLE_NAME,
                   SORT_SEQUENCE_TABLE_LIBRARY_NAME,
                   OBJECT_AUDITING_VALUE,
                   USER_ACTION_AUDIT_LEVEL,
                   GROUP_AUTHORITY_TYPE,
                   USER_ID_NUMBER,
                   GROUP_ID_NUMBER,
                   LOCALE_JOB_ATTRIBUTES,
                   GROUP_MEMBER_INDICATOR,
                   DIGITAL_CERTIFICATE_INDICATOR,
                   CHARACTER_IDENTIFIER_CONTROL,
                   LOCAL_PASSWORD_MANAGEMENT,
                   BLOCK_PASSWORD_CHANGE,
                   USER_ENTITLEMENT_REQUIRED,
                   USER_EXPIRATION_INTERVAL,
                   USER_EXPIRATION_DATE,
                   USER_EXPIRATION_ACTION,
                   HOME_DIRECTORY,
                   LOCALE_PATH_NAME,
                   USER_DEFAULT_PASSWORD,
                   USER_OWNER,
                   USER_CREATOR,
                   SIZE,
                   CREATION_TIMESTAMP,
                   LAST_USED_TIMESTAMP,
                   DAYS_USED_COUNT,
                   LAST_RESET_TIMESTAMP,
                   AUTHORITY_COLLECTION_ACTIVE,
                   AUTHORITY_COLLECTION_REPOSITORY_EXISTS,
                   PASE_SHELL_PATH
                FROM QSYS2.USER_INFO
        )
        {f"LIMIT {limit} OFFSET {offset}" if limit else None}
""",
                {"error": "on" if debug else "fast"},
            )
        )

        self.itool.add(iSqlExecute("exec", {"error": "on" if debug else "fast"}))

        self.itool.add(iSqlFetch("fetch", {"error": "on" if debug else "fast"}))
        self.itool.add(iSqlFree("free", {"error": "on" if debug else "fast"}))

        self.itool.call(self.itransport)

        requestResult = self.itool.dict_out("fetch")
        self.itool.clear()

        if "error" in requestResult:
            print(requestResult["error"], file=sys.stderr, flush=True)
            raise Exception(requestResult["error"])
        else:
            if type(requestResult["row"]) is dict:
                return [requestResult["row"]]
            else:
                return requestResult["row"]

    def getSystemInformation(self, debug: bool = False):
        self.itool.add(
            iSqlPrepare("getSysInfo", """SELECT * FROM SYSIBMADM.ENV_SYS_INFO""")
        )
        self.itool.add(iSqlExecute("exec"))

        self.itool.add(iSqlFetch("fetch"))
        self.itool.add(iSqlFree("free"))

        self.itool.call(self.itransport)

        queryResult = self.itool.dict_out("fetch")
        self.itool.clear()

        if "error" in queryResult:
            print(queryResult["error"])
            raise Exception(queryResult["error"])

        self.itool.clear()

        self.__OSName = queryResult["row"]["OS_NAME"]
        self.__OSVersion = queryResult["row"]["OS_VERSION"]
        self.__OSRelease = queryResult["row"]["OS_RELEASE"]
        self.__hostName = queryResult["row"]["HOST_NAME"]
        self.__CPUSCount = queryResult["row"]["TOTAL_CPUS"]
        self.__configuredCPUS = queryResult["row"]["CONFIGURED_CPUS"]
        self.__configuredMemory = queryResult["row"]["CONFIGURED_MEMORY"]
        self.__totalMemory = queryResult["row"]["TOTAL_MEMORY"]

        return queryResult["row"]

    def getCPUUsage(self):
        return round(self.__CPUUsage, 6)

    def getASPUsage(self):
        return round(self.__ASPUsage, 6)

    def getJOBUsage(self):
        return round((self.__activeJobsInSystem / self.__maximumJobsInSystem) * 100, 6)

    def getOSName(self):
        return self.__OSName

    def getOSVersion(self):
        return self.__OSVersion

    def getOSRelease(self):
        return self.__OSRelease

    def getHostName(self):
        return self.__hostName

    def getShortHostName(self):
        return self.__hostName[: self.__hostName.find(".")]

    def getCPUSCount(self):
        return self.__CPUSCount

    def getConfiguredCPUS(self):
        return self.__configuredCPUS

    def getConfiguredMemory(self):
        return self.__configuredMemory

    def getTotalMemory(self):
        return self.__totalMemory

    def _getColumnsNames(cursor):
        return [column[0] for column in cursor.description]
