"""Job Information"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from pprint import pformat
import six
from six.moves import zip_longest

from DIRAC import gLogger
from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations
from DIRAC.Core.Utilities.List import fromChar

ASSIGNEDSTATES = ["Assigned", "Processed"]

LOG = gLogger.getSubLogger(__name__)
__RCSID__ = "$Id$"


class TaskInfoException(Exception):
    """Exception when the task info is not recoverable"""

    def __init__(self, message):
        super(TaskInfoException, self).__init__(message)


class JobInfo(object):
    """hold information about jobs"""

    def __init__(self, jobID, status, tID, tType=None):
        self.tID = int(tID)
        self.tType = tType
        self.jobID = int(jobID)
        self.status = status

        # information related to input files
        self.inputFiles = []
        self.inputFileStatus = []  # from the filecatalog
        self.transFileStatus = []  # from the transformation
        self.inputFilesExist = []
        self.errorCounts = []
        self.otherTasks = []

        self.outputFiles = []
        self.outputFileStatus = []

        self.taskID = None
        self.pendingRequest = False

        self.maxResetCounter = Operations().getValue("Transformations/FilesMaxResetCounter", 10)

    def __str__(self):
        info = "%d: %s" % (self.jobID, self.status)
        if self.tID and self.taskID:
            info += " %s Transformation: %d -- %d " % (self.tType, self.tID, self.taskID)
        if self.otherTasks:
            info += " (Last task %s)" % self.otherTasks
        if self.inputFiles:
            ifInfo = [
                "<<< %s (%s, %s, Errors %s)" % _
                for _ in zip_longest(self.inputFiles, self.inputFilesExist, self.transFileStatus, self.errorCounts)
            ]
            info += "\n".join(ifInfo)
        if self.outputFiles:
            info += "\n::: OutputFiles: "
            efInfo = ["%s (%s)" % _ for _ in zip_longest(self.outputFiles, self.outputFileStatus)]
            info += ", ".join(efInfo)
        if self.pendingRequest:
            info += "\n PENDING REQUEST IGNORE THIS JOB!!!"
        else:
            info += "\n No Pending Requests"

        return info

    def allFilesExist(self):
        """Check if all files exists, if there are no output files, return False."""
        return bool(self.outputFileStatus) and all("Exists" in status for status in self.outputFileStatus)

    def allFilesMissing(self):
        """Check if all files are missing, if not output files, return False"""
        return bool(self.outputFileStatus) and all("Missing" in status for status in self.outputFileStatus)

    def someFilesMissing(self):
        """check if some files are missing and therefore some files exist"""
        return bool(self.outputFileStatus) and not (self.allFilesExist() or self.allFilesMissing())

    def allInputFilesExist(self):
        """check if all input files exists"""
        if not self.inputFileStatus:
            return False
        return all("Exists" in status for status in self.inputFileStatus)

    def allInputFilesMissing(self):
        """check if all input files are missing"""
        if not self.inputFileStatus:
            return False
        return all("Missing" in status for status in self.inputFileStatus)

    def someInputFilesMissing(self):
        """check if some input files are missing and therefore some files exist"""
        if not self.inputFileStatus:
            return False
        return not (self.allInputFilesExist() or self.allInputFilesMissing())

    def allFilesProcessed(self):
        """Check if all input files are processed."""
        return bool(self.transFileStatus) and all("Processed" in status for status in self.transFileStatus)

    def allFilesAssigned(self):
        """Check if all input files are assigned."""
        return bool(self.transFileStatus) and all(status in ASSIGNEDSTATES for status in self.transFileStatus)

    def allTransFilesDeleted(self):
        """Check if all input files are deleted in the Transformation System."""
        return bool(self.transFileStatus) and all(status == "Deleted" for status in self.transFileStatus)

    def checkErrorCount(self):
        """Check if any file is above Operations/Transformations/FilesMaxResetCounter error count."""
        return any(errorCount > self.maxResetCounter for errorCount in self.errorCounts)

    def getJobInformation(self, diracAPI, jobMon, jdlOnly=False):
        """Get all the information for the job.

        The InputData, TaskID, OutputData can either be taken from properly filled JDL or

        * inputData from jobMonitor getInputData
        * TaskID from the name of The job via jobMonitor getJobAttribute JobName
        * ProductionOutputData: from jobMonitor getJobParameter ProductionOutputData

        This would be faster if we could do bulk calls for all of these
        """
        if not jdlOnly:
            # this is actually slower than just getting the jdl, because getting the jdl is one service call
            # this is three service calls to three different DBs
            resInputData = jobMon.getInputData(self.jobID)
            if resInputData["OK"]:
                self.inputFiles = resInputData["Value"]
            resName = jobMon.getJobAttribute(self.jobID, "JobName")
            if resName["OK"] and "_" in resName["Value"]:
                self.__getTaskID(resName["Value"])
            resOutput = jobMon.getJobParameter(self.jobID, "ProductionOutputData")
            if resOutput["OK"]:
                self.outputFiles = fromChar(resOutput["Value"].get("ProductionOutputData", ""))
                if not self.outputFiles:
                    LOG.verbose("Did not find outputFiles for", str(self))

        if not (self.inputFiles and self.outputFiles and self.taskID):
            LOG.verbose("Have to check JDL")
            jdlParameters = self.__getJDL(diracAPI)
            # get taskID from JobName, get inputfile(s) from DownloadInputdata
            self.__getOutputFiles(jdlParameters)
            self.__getTaskID(jdlParameters)
            if not self.inputFiles:
                self.__getInputFile(jdlParameters)

    def getTaskInfo(self, tasksDict, lfnTaskDict, withInputTypes):
        """extract the task information from the taskDict"""

        if not self.inputFiles and self.tType in withInputTypes:
            raise TaskInfoException("InputFiles is empty: %s" % str(self))

        # taskID not in tasksDict means another job has been assigned to the files
        if self.taskID in tasksDict:
            for taskDict in tasksDict[self.taskID]:
                self.transFileStatus.append(taskDict["Status"])
                self.errorCounts.append(taskDict["ErrorCount"])
                if taskDict["LFN"] not in self.inputFiles:
                    raise TaskInfoException(
                        "InputFiles do not agree: %s not in InputFiles: \n %s" % (taskDict["LFN"], str(self))
                    )
            return

        # taskID not in tasksDict means another job has been assigned to the files
        for inputLFN in self.inputFiles:
            try:
                for taskDict in tasksDict[lfnTaskDict[inputLFN]]:
                    self.otherTasks.append(lfnTaskDict[inputLFN])
                    self.transFileStatus.append(taskDict["Status"])
                    self.errorCounts.append(taskDict["ErrorCount"])
            except KeyError as ke:
                LOG.error("ERROR for key:", str(ke))
                LOG.error("Failed to get taskDict", "%s, %s: %s" % (self.taskID, self.inputFiles, pformat(lfnTaskDict)))
                raise

    def checkFileExistence(self, success):
        """check if input and outputfile still exist"""
        for lfn in self.inputFiles:
            if lfn in success and success[lfn]:
                self.inputFilesExist.append(True)
                self.inputFileStatus.append("Exists")
            elif lfn in success:
                self.inputFileStatus.append("Missing")
                self.inputFilesExist.append(False)
            else:
                self.inputFileStatus.append("Unknown")
                self.inputFilesExist.append(False)

        for lfn in self.outputFiles:
            if lfn in success and success[lfn]:
                self.outputFileStatus.append("Exists")
            elif lfn in success:
                self.outputFileStatus.append("Missing")
            else:
                self.outputFileStatus.append("Unknown")

    def __getJDL(self, diracAPI):
        """return jdlParameterDictionary for this job"""
        res = diracAPI.getJobJDL(int(self.jobID))
        if not res["OK"]:
            raise RuntimeError("Failed to get jobJDL: %s" % res["Message"])
        jdlParameters = res["Value"]
        return jdlParameters

    def __getOutputFiles(self, jdlParameters):
        """get the Production Outputfiles for the given Job"""
        lfns = jdlParameters.get("ProductionOutputData", [])
        if isinstance(lfns, six.string_types):
            lfns = [lfns]
        self.outputFiles = lfns

    def __getInputFile(self, jdlParameters):
        """get the Inputdata for the given job"""
        lfn = jdlParameters.get("InputData", None)
        if isinstance(lfn, six.string_types):
            self.inputFiles.append(lfn)
            return
        if isinstance(lfn, list):
            self.inputFiles = lfn
            return

    def __getTaskID(self, jdlParameters):
        """Get the taskID."""
        if isinstance(jdlParameters, six.string_types):
            self.taskID = int(jdlParameters.split("_")[1])
        if "TaskID" not in jdlParameters:
            return
        try:
            self.taskID = int(jdlParameters.get("TaskID", None))
        except ValueError:
            LOG.warn("*" * 80)
            LOG.warn("TaskID broken?: %r" % jdlParameters.get("TaskID", None))
            LOG.warn(self)
            LOG.warn("*" * 80)
            raise

    def setJobDone(self, tInfo):
        """mark job as done in wms and transformationsystem"""
        tInfo.setJobDone(self)

    def setJobFailed(self, tInfo):
        """mark job as failed in  wms and transformationsystem"""
        tInfo.setJobFailed(self)

    def setInputProcessed(self, tInfo):
        """mark input file as Processed"""
        tInfo.setInputProcessed(self)

    def setInputMaxReset(self, tInfo):
        """mark input file as MaxReset"""
        tInfo.setInputMaxReset(self)

    def setInputDeleted(self, tInfo):
        """mark input file as Deleted"""
        tInfo.setInputDeleted(self)

    def setInputUnused(self, tInfo):
        """mark input file as Unused"""
        tInfo.setInputUnused(self)

    def cleanOutputs(self, tInfo):
        """remove all job outputs"""
        tInfo.cleanOutputs(self)
