"""LALInference Pipeline specification."""

import configparser
import glob
import os
import re
import subprocess


from asimov import config, logger
from asimov.utils import set_directory

from ..pipeline import Pipeline, PipelineException, PipelineLogger


class LALInference(Pipeline):
    """
    The LALInference Pipeline.

    Parameters
    ----------
    production : :class:`asimov.Production`
       The production object.
    category : str, optional
        The category of the job.
        Defaults to "C01_offline".
    """

    name = "lalinference"
    STATUS = {"wait", "stuck", "stopped", "running", "finished"}

    def __init__(self, production, category=None):
        super(LALInference, self).__init__(production, category)
        self.logger = logger
        if not production.pipeline.lower() == "lalinference":
            raise PipelineException

    def detect_completion(self):
        """
        Check for the production of the posterior file to signal that the job has completed.
        """
        results_dir = glob.glob(f"{self.production.rundir}/posterior_samples")
        if len(results_dir) > 0:
            if len(glob.glob(os.path.join(results_dir[0], "posterior_*.hdf5"))) > 0:
                return True
            else:
                return False
        else:
            return False

    def build_dag(self, psds=None, user=None, clobber_psd=False, dryrun=False):
        """
        Construct a DAG file in order to submit a production to the
        condor scheduler using LALInferencePipe.

        Parameters
        ----------
        production : str
           The production name.
        psds : dict, optional
           The PSDs which should be used for this DAG. If no PSDs are
           provided the PSD files specified in the ini file will be used
           instead.
        user : str
           The user accounting tag which should be used to run the job.
        dryrun: bool
           If set to true the commands will not be run, but will be printed to standard output. Defaults to False.

        Raises
        ------
        PipelineException
           Raised if the construction of the DAG fails.
        """

        # Change to the location of the ini file.
        with set_directory(
            os.path.join(self.production.event.repository.directory, self.category)
        ):
            gps_file = self.production.get_timefile()

            if self.production.rundir:
                rundir = self.production.rundir
            else:
                rundir = os.path.join(
                    os.path.expanduser("~"),
                    self.production.event.name,
                    self.production.name,
                )
                self.production.rundir = rundir

            # os.mkdir(self.production.rundir, exist_ok=True)
            ini = f"{self.production.name}.ini"
            command = [
                os.path.join(
                    config.get("pipelines", "environment"), "bin", "lalinference_pipe"
                ),
                "-g",
                f"{gps_file}",
                "-r",
                self.production.rundir,
                ini,
            ]

            if dryrun:
                print(" ".join(command))
            else:
                self.logger.info(" ".join(command))
                pipe = subprocess.Popen(
                    command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
                )
                out, err = pipe.communicate()
                if err or "Successfully created DAG file." not in str(out):
                    self.production.status = "stuck"
                    if hasattr(self.production.event, "issue_object"):
                        self.logger.error(
                            f"DAG file could not be created.\n{command}\n{out}\n\n{err}"
                        )
                        raise PipelineException(
                            f"DAG file could not be created.\n{command}\n{out}\n\n{err}",
                            issue=self.production.event.issue_object,
                            production=self.production.name,
                        )
                    else:
                        self.logger.error(
                            f"DAG file could not be created.\n{command}\n{out}\n\n{err}"
                        )
                        raise PipelineException(
                            f"DAG file could not be created.\n{command}\n{out}\n\n{err}",
                            production=self.production.name,
                        )
                else:
                    self.logger.info("DAG created")
                    if hasattr(self.production.event, "issue_object"):
                        return PipelineLogger(
                            message=out,
                            issue=self.production.event.issue_object,
                            production=self.production.name,
                        )
                    else:
                        return PipelineLogger(
                            message=out, production=self.production.name
                        )

    def samples(self):
        """
        Collect the combined samples file for PESummary.
        """
        return glob.glob(
            os.path.join(self.production.rundir, "posterior_samples", "posterior*.hdf5")
        )

    def collect_logs(self):
        """
        Collect all of the log files which have been produced by this production and
        return their contents as a dictionary.
        """
        logs = glob.glob(f"{self.production.rundir}/log/*.err") + glob.glob(
            f"{self.production.rundir}/*.err"
        )
        messages = {}
        for log in logs:
            with open(log, "r") as log_f:
                message = log_f.read()
                messages[log.split("/")[-1]] = message
        return messages

    def submit_dag(self, dryrun=False):
        """
        Submit a DAG file to the condor cluster.

        Parameters
        ----------
        category : str, optional
           The category of the job.
           Defaults to "C01_offline".
        production : str
           The production name.
        dryrun: bool
           If set to true the commands will not be run, but will be printed to standard output. Defaults to False.


        Returns
        -------
        int
           The cluster ID assigned to the running DAG file.
        PipelineLogger
           The pipeline logger message.

        Raises
        ------
        PipelineException
           This will be raised if the pipeline fails to submit the job.
        """

        with set_directory(self.production.rundir):

            self.before_submit(dryrun=dryrun)

            try:
                command = [
                    "condor_submit_dag",
                    "-batch-name",
                    f"lalinf/{self.production.event.name}/{self.production.name}",
                    os.path.join(self.production.rundir, "multidag.dag"),
                ]

                if dryrun:
                    print(" ".join(command))
                else:
                    dagman = subprocess.Popen(
                        command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
                    )

                    stdout, stderr = dagman.communicate()

                    if "submitted to cluster" in str(stdout):
                        cluster = re.search(
                            r"submitted to cluster ([\d]+)", str(stdout)
                        ).groups()[0]
                        self.production.status = "running"
                        self.production.job_id = cluster
                        return cluster, PipelineLogger(stdout)
                    else:
                        raise PipelineException(
                            f"The DAG file could not be submitted.\n\n{stdout}\n\n{stderr}",
                            issue=self.production.event.issue_object,
                            production=self.production.name,
                        )

            except FileNotFoundError as error:
                raise PipelineException(
                    "It looks like condor isn't installed on this system.\n"
                    f"""I wanted to run {" ".join(command)}."""
                ) from error

    def after_completion(self):
        cluster = self.run_pesummary()
        self.production.meta["job id"] = int(cluster)
        self.production.status = "processing"

    def resurrect(self):
        """
        Attempt to ressurrect a failed job.
        """
        try:
            count = self.production.meta["resurrections"]
        except KeyError:
            count = 0
        if (count < 5) and (
            len(glob.glob(os.path.join(self.production.rundir, "submit", "*.rescue*")))
            > 0
        ):
            count += 1
            self.submit_dag()

    @classmethod
    def read_ini(cls, filepath):
        """
        Read and parse a bilby configuration file.

        Note that bilby configurations are property files and not compliant ini configs.

        Parameters
        ----------
        filepath: str
           The path to the ini file.
        """

        with open(filepath, "r") as f:
            file_content = f.read()

        config_parser = configparser.RawConfigParser()
        config_parser.read_string(file_content)

        return config_parser
