"""check_arcce_submit - Submits test jobs to ARC."""

import asyncio
import os
import time
from arcnagios import arcutils, nagutils
from arcnagios.utils import log_process_error
from arcnagios.ce.jobutils import JobNagiosPlugin, JobDescription, JobInfo

class Check_arcce_submit(JobNagiosPlugin):
    def __init__(self):
        JobNagiosPlugin.__init__(self)
        argp = self.argparser.add_argument_group('Options for Job Submission')
        argp.add_argument('-H', dest = 'host', required = True,
                help = 'The host name of the CE to test.  This will be used '
                       'to connect to the CE unless --ce is given.  '
                       'This option is required.')
        argp.add_argument('-S', dest = 'submissioninterface', required = False,
                help = 'The submission interface, passed on to arcsub. ' \
                       'Requires arcsub version 7.0.0 or later.')
        argp.add_argument('-I', dest = 'infointerface', required = False,
                help = 'The information interface, passed on to arcsub. ' \
                       'Requires arcsub version 7.0.0 or later.')
        argp.add_argument('-p', dest = 'port', type = int,
                help = 'An optional port number at which to connect.')
        argp.add_argument('--prev-status', dest = 'prev_status', type = int,
                default = 0, metavar = '{0..3}',
                help = 'The previous Nagios status for this metric.')
        argp.add_argument('--termination-service', dest = 'termination_service',
                default = '',
                help = 'The name (NAGIOS "description") of the passive '
                       'service to which to submit the results.')
        argp.add_argument('--progress-service', metavar = 'SVC',
                help = 'Publish state timeout alerts to SVC.')
        argp.add_argument('--submission-service', dest = 'submission_service',
                default = '',
                help = 'Report submission-related alerts to this service '
                       'instead of raising the alert on the active service.')
        argp.add_argument('--submission-service-threshold',
                default = 2, type = int,
                help = 'Minimum severity before the submission result is '
                       'submitted to the passive service specified by '
                       '--submission-service.  This is the numeric status, '
                       '0 for OK, 1 for WARNING, 2 for ERROR (default), '
                       'and 3 for UNKNOWN.')
        argp.add_argument('--job-submit-timeout', dest = 'job_submit_timeout',
                type = int, default = 600,
                help = 'Timeout for job submission.')
        argp.add_argument('--job-discard-timeout', dest = 'job_discard_timeout',
                type = int, default = 6*3600,
                help = 'Timeout before discarding a job.')
        argp.add_argument('--ce', dest = 'ce',
                help = 'URL for connecting to the CE, using the same format '
                       'as the -C option of arcsub(1).')
        argp.add_argument('--queue', dest = 'queue',
                help = 'Target queue name. If unspecified, let ARC choose it.')
        argp.add_argument('--job-tag', dest = 'job_tag',
                help = 'A short string suitable in directory names to '
                       'distinguish different submission services for the '
                       'same hostname.')
        argp.add_argument('--job-description', dest = 'job_description',
                help = 'Use this job description instead of generating one.  '
                       'In this case --stage-input options are ignored and '
                       'URLs passed to --stage-output will be deleted when '
                       'the job finishes.')
        argp.add_argument('--keep-failed-jobdata', dest = 'keep_failed_jobdata',
                action = 'store_true', default = False,
                help = 'Keep the job descriptions and output directories for '
                       'failed jobs. These will not be removed automatically.')
        argp.add_argument('--test', dest = 'tests', action='append', default=[],
                metavar = 'TESTNAME',
                help = 'Add an additional test described in the configuration '
                       'file under the section "arcce.TESTNAME"')
        argp.add_argument('--runtime-environment', dest = 'runtime_environments',
                action = 'append', default = [], metavar = 'RTE',
                help = 'Request the given runtime environment.')
        argp.add_argument('--wall-time-limit', dest = 'wall_time_limit',
                type = int, default = 600,
                help = 'Soft limit of execution wall-time.')
        argp.add_argument('--memory-limit', dest = 'memory_limit',
                type = int, default = 536870912,
                help = 'The max. about of memory used by the job in bytes. '
                       'Default: 536870912 (512 MiB)')
        argp.add_argument('--enable-gmlog', dest = 'enable_gmlog',
                action = 'store_true', default = False,
                help = 'Request debug information from the CE.  This will be '
                       'stored in a subdirectory log of the output directory.')
        argp.add_argument('--arcsub-loglevel', type = str, default = None,
                help = 'The log level to pass to arcsub (-d).')
        self._staged_inputs = []
        self._staged_outputs = []

    def parse_args(self, args):
        JobNagiosPlugin.parse_args(self, args)

    def _report_submission(self, status: int, msg: str) -> None:
        if status < self.opts.submission_service_threshold \
                or not self.opts.submission_service:
            report = self.nagios_report
        else:
            self.nagios_report.update_status(nagutils.OK,
                                             'Reporting to passive service.')
            report = self.nagios_report_for(self.opts.host,
                                            self.opts.submission_service)
        report.update_status(status, msg)

    def check(self) -> None: # calls asyncio.run
        """Submit a job to a CE."""

        self.require_voms_proxy()

        workdir = self.workdir_for(self.opts.host, self.opts.job_tag)

        jobid_file = os.path.join(workdir, self.JOBID_FILENAME)
        jobinfo = self.load_active_job(self.opts.host, self.opts.job_tag)
        if not jobinfo is None:
            t_sub = jobinfo.submission_time
            job_state = jobinfo.job_state

            if not job_state.is_final():
                s_sub = time.strftime('%FT%T', time.localtime(t_sub))
                self.log.info('Last job was submitted %s.', s_sub)
                t_dis = t_sub + self.opts.job_discard_timeout
                if int(time.time()) >= t_dis:
                    self.log.warning('Discarding last job due to timeout.')
                    asyncio.run(self.discard_job(jobinfo))
                    self._report_submission(nagutils.WARNING,
                            'Re-submitting due to timeout of %s from %s'
                            % (jobinfo.job_id, s_sub))
                else:
                    s_dis = time.strftime('%FT%T', time.localtime(t_dis))
                    self.log.info('Job will be discarded %s.', s_dis)
                    status = self.opts.prev_status or 0
                    self.log.info('Keeping previous status %d.', status)
                    self._report_submission(status, 'Job not finished.')
                    return
            else:
                self.log.debug('Job in terminal state %s.\n', job_state)
                self._report_submission(nagutils.OK,
                             'Waiting for monitoring service to fetch the job.')
                return

        # Prepare the working directory for a new job.
        job_output_dir = os.path.join(workdir, self.JOB_OUTPUT_DIRNAME)
        if not os.path.exists(job_output_dir):
            try:
                os.makedirs(job_output_dir)
            except OSError as exn:
                msg = 'Failed to create working directory: %s' % exn
                self.nagios_report.update_status(nagutils.UNKNOWN, msg)
                return

        self.log.debug('Submitting new job.')
        job_script_file = os.path.join(workdir, self.JOB_SCRIPT_FILENAME)

        # Create job script.
        with open(job_script_file, 'w', encoding='utf-8') as fh:
            fh.write('#! /bin/sh\n\n'
                     'status=0\n'
                     'echo "Job started `date -Is`."\n')
            runtime_environments = set(self.opts.runtime_environments)
            fh.write('\n')
            for test_name in self.opts.tests:
                test = self.load_jobtest(test_name, hostname = self.opts.host)
                test.write_script(fh, workdir)
                self._staged_inputs += test.staged_inputs(workdir)
                self._staged_outputs += test.staged_outputs(workdir)
                runtime_environments.update(test.runtime_environments())
            fh.write('echo "Present files before termination:"\n'
                     'ls -l\n'
                     'echo "Job finished `date -Is`, status = $status."\n'
                     'exit $status\n')

        # Create job description file.
        if self.opts.job_description:
            jobdesc_file = self.opts.job_description
        else:
            jobdesc_file = os.path.join(workdir, self.JOB_DESCRIPTION_FILENAME)
            jobdesc = JobDescription(
                    script_path = job_script_file,
                    application_name = 'ARCCE-probe',
                    logdir = self.opts.enable_gmlog and 'log',
                    job_name = self.opts.termination_service,
                    output = 'stdout.txt',
                    error = 'stderr.txt',
                    staged_inputs = self._staged_inputs,
                    staged_outputs = self._staged_outputs,
                    runtime_environments = list(runtime_environments),
                    wall_time_limit = self.opts.wall_time_limit,
                    memory_limit = self.opts.memory_limit,
                    queue_name = self.opts.queue)
            self.write_job_description(jobdesc_file, jobdesc)

        # Submit the job.
        if self.opts.ce:
            connection_url = self.opts.ce
        elif self.config.has_option('arcce.connection_urls', self.opts.host):
            connection_url = self.config.get('arcce.connection_urls',
                                             self.opts.host)
        elif self.config.has_option('arcce.connection_urls', 'default'):
            connection_url = \
                self.config.get('arcce.connection_urls', 'default') \
                % {'ce_host': self.opts.host}
        else:
            if self.opts.port:
                connection_url = self.opts.host + ':' + str(self.opts.port)
            else:
                connection_url = self.opts.host
        arcsub_result = \
            self.arcclient.arcsub([jobdesc_file],
                    cluster = connection_url,
                    jobids_to_file = jobid_file,
                    timeout = self.opts.job_submit_timeout,
                    loglevel = self.opts.arcsub_loglevel,
                    submissioninterface = self.opts.submissioninterface,
                    infointerface = self.opts.infointerface)
        try:
            with open(jobid_file, encoding='utf-8') as fh:
                job_id = fh.readline().strip()
        except FileNotFoundError:
            job_id = None

        if arcsub_result.is_error():
            self.cleanup_job_files(self.opts.host, self.opts.job_tag,
                    archive = self.opts.keep_failed_jobdata)
            log_process_error(
                    self.log, arcsub_result.get_error(), prefix = 'arcsub',
                    synopsis = 'failed to submit job')
            if not job_id:
                self._report_submission(nagutils.CRITICAL,
                        'Job submission failed.')
                return
            self.log.error('Received a JID despite the error, proceeding.')
            self._report_submission(
                    nagutils.WARNING,
                    'Job seems to be submitted but: %s'
                    % arcsub_result.get_error())
        elif not job_id:
            self.cleanup_job_files(self.opts.host, self.opts.job_tag)
            self.log.error('The job ID was not found in %s.', jobid_file)
            for line in arcsub_result.get().strip().split('\n'):
                self.log.error('arcsub: %s', line)
            self._report_submission(nagutils.CRITICAL, 'Failed to submit job.')
            return
        else:
            self._report_submission(nagutils.OK, 'Job submitted.')

        t_now = time.time()
        jobinfo = JobInfo(
                submission_time = t_now,
                host = self.opts.host,
                job_tag = self.opts.job_tag,
                termination_service = self.opts.termination_service,
                progress_service = self.opts.progress_service,
                job_id = job_id,
                job_state = arcutils.J_NOT_SEEN,
                job_state_time = int(time.time()),
                check_time = t_now,
                stored_urls = [url for _, url, _ in self._staged_outputs if url],
                tests = self.opts.tests,
                reputation_choices = self._reputation_tracker.choices())
        self.save_active_job(jobinfo, self.opts.host, self.opts.job_tag)
