#!/usr/bin/env python
###################################################################################
# LAVA QA tool
# Copyright (C) 2015, 2016 Collabora Ltd
# Luis Araujo <luis.araujo@collabora.co.uk>

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  US
###################################################################################

from __future__ import unicode_literals

import sys
try:
    import xmlrpc.client as xmlrpclib
except ImportError:
    import xmlrpclib
from argparse import ArgumentParser

from lqa_api.waitqueue import WAIT_DEFAULT_TIMEOUT
from lqa_api.exit_codes import APPLICATION_ERROR
from lqa_tool.version import __version__
from lqa_tool.commands import Command
from lqa_tool.exceptions import ProfileNotFound
from lqa_tool.settings import settings, lqa_logger

class Cli(object):
    """Command line interface using argparse"""

    def __init__(self):
        self.parser = ArgumentParser(description="lqa v{}".format(__version__))
        # Add options common to all sub-commands
        self.parser.add_argument('-c', '--config', metavar='CONFIG.yaml',
                                 help="set configuration file")
        self.parser.add_argument('--log-file', type=str, help="set the log file")

        # Sub-commands
        subparsers = self.parser.add_subparsers()

        # Submit
        submit_parser = subparsers.add_parser('submit', help="Submit job files")
        submit_parser.add_argument('submit_job', nargs='*', type=str,
                                   metavar='JOB_FILE.{yaml,json}',
                                   help="job file to submit")
        submit_parser.add_argument('-g', '--profile-file', metavar='PROFILE.yaml',
                                   help="set profile file")
        submit_parser.add_argument('-n', '--dry-run', action='store_true',
                                   help="Dry-run, do everyting apart from "
                                   "submitting")
        submit_parser.add_argument('-p', '--profile', action='append', type=str,
                                   help="specify the profiles to use "
                                   "(can be given multiple times)")
        submit_parser.add_argument('--all-profiles', action='store_true',
                                   help="process all the available profiles")
        submit_parser.add_argument('-t', '--template-vars', action='append',
                                   type=str, help="set 'field:value' "
                                   "template variables/values "
                                   "(can be given multiple times")
        submit_parser.add_argument('-v', '--verbose', action='store_true',
                                   help="Verbose mode (e.g. print the resulting "
                                   "json)")
        submit_parser.add_argument('--wait',  nargs='?', type=str,
                                   metavar='TIMEOUT', dest='wait_timeout',
                                   const=WAIT_DEFAULT_TIMEOUT,
                                   help="Wait for submitted jobs to complete using"
                                   " a TIMEOUT value (Default timeout of 3 hours)")
        submit_parser.add_argument('--debug-vars', action='store_true',
                                   help="Debug undefined template variables")
        submit_parser.add_argument('--priority',
                                   choices=['high', 'medium', 'low'],
                                   help="Set the job priority"),
        submit_parser.add_argument('--live', action='store_true',
                                   help="show job output live")
        submit_parser.add_argument('--check-image-url', action='store_true',
                                   help="Check that the image url exists before "
                                   "submitting job")
        submit_parser.set_defaults(func=submit)

        # Cancel
        cancel_parser = subparsers.add_parser('cancel', help="Cancel job id")
        cancel_parser.add_argument('job_ids',  nargs='+', type=str,
                                   metavar='JOB_ID', help="job id to cancel")
        cancel_parser.set_defaults(func=cancel)

        # Resubmit
        resubmit_parser = subparsers.add_parser('resubmit', help="Resubmit job id")
        resubmit_parser.add_argument('job_ids',  nargs='+', type=str,
                                     metavar='JOB_ID', help="job id to resubmit")
        resubmit_parser.set_defaults(func=resubmit)

        # Make stream
        mkstream_parser = subparsers.add_parser('mkstream',
                                                help="Create bundle stream")
        mkstream_parser.add_argument('description', type=str, nargs="?",
                                     default="", metavar='DESCRIPTION',
                                     help="description of the stream")
        # Group mutually exclusive options
        # Access group: public or private
        mkstream_access_group = mkstream_parser.add_mutually_exclusive_group()
        mkstream_access_group.add_argument('--public', action='store_const',
                                           const="public", default="public",
                                           help="create public stream (default)")
        mkstream_access_group.add_argument('--private', action='store_const',
                                           const="private",
                                           help="create private stream")
        # User group: anonymous, personal or team
        mkstream_user_group = mkstream_parser.add_mutually_exclusive_group()
        mkstream_user_group.add_argument('--anonymous', type=str, metavar="NAME",
                                         help="create anonymous stream (this "
                                         "option overrides access options since "
                                         "an anonymous stream is always public)")
        mkstream_user_group.add_argument('--personal', type=str,
                                         nargs='?', metavar="LOGGED_USER/SLUG",
                                         # $USER is replaced by the logged in user
                                         # when creating the bundle.
                                         const="$USER",
                                         help="create personal stream and when "
                                         "not argument is passed it defaults to "
                                         "the logged in username")
        mkstream_user_group.add_argument('--team', type=str,
                                         metavar="TEAM_NAME[/SLUG]",
                                         help="create team stream")
        mkstream_parser.set_defaults(func=mkstream)

        # Wait
        wait_parser = subparsers.add_parser('wait', help="Wait for job id")
        wait_parser.add_argument('job_ids',  nargs='+', type=str,
                                 metavar='JOB_ID', help="wait for this job id")
        wait_parser.add_argument('--timeout', type=str,
                                 default=WAIT_DEFAULT_TIMEOUT,
                                 help="set wait timeout")
        wait_parser.set_defaults(func=wait)

        # Status
        status_parser = subparsers.add_parser('status', help="Show job id status")
        status_parser.add_argument('job_ids',  nargs='+', type=str,
                                   metavar='JOB_ID',
                                   help="show the status for job id")
        status_parser.set_defaults(func=status)

        # Job Output
        output_parser = subparsers.add_parser('output',
                                              help="Fetch job id output log")
        output_parser.add_argument('job_id', type=int, metavar='JOB_ID',
                                   help="job id")
        output_group = output_parser.add_mutually_exclusive_group()
        output_group.add_argument('--file', type=str, metavar='FILE',
                                  help="write output to file")
        output_group.add_argument('--live', action='store_true',
                                  help="follow output log live")
        output_parser.set_defaults(func=output)

        # Job information
        job_parser = subparsers.add_parser('job',
                                           help="Get information for job id")
        job_parser.add_argument('job_ids', nargs='+', type=str, metavar='JOB_ID',
                                help="job id from which to fetch information")
        job_parser.add_argument('--info', action='store_true',
                                help="show job metadata information")
        job_parser.add_argument('-t', '--tests', action='store_true',
                                help="show the tests list")
        job_parser.set_defaults(func=job)

        # Test information
        test_parser = subparsers.add_parser('test', help="Show test information")
        test_parser.add_argument('job_id', type=int, metavar='JOB_ID',
                                 help="job id")
        test_parser.add_argument('test_name', nargs='*', type=str,
                                 metavar='TEST_NAME',
                                 help="test name or test uuid to get results, if"
                                 " no test is specified it will list all tests")
        test_parser.add_argument('--info', action='store_true',
                                 help="show test metadata information")
        test_parser.add_argument('--show-packages', action='store_true',
                                 help="show the installed packages in the system"
                                 " for the test (it can be a long output)")
        test_parser.add_argument('-e', '--exclude', action='append', type=str,
                                 help="exclude test from the result "
                                 "(can be given multiple times)")
        test_parser.add_argument('-r', '--results', action='store_true',
                                 help="show test results")
        test_parser.add_argument('--attachments', type=str, metavar="DIR",
                                 help="save test attachments to DIR")
        test_parser.set_defaults(func=test)

        # Tests diff
        diff_parser = subparsers.add_parser('diffresults',
                                            help="Show test results differences")
        diff_parser.add_argument('job1_id', type=int, metavar='JOB1_ID',
                                 help="job 1 id")
        diff_parser.add_argument('job2_id', type=int, metavar='JOB2_ID',
                                 help="job 2 id")
        diff_parser.add_argument('--infra', action='store_true',
                                 help="show infra (lava) tests differences")
        diff_parser.set_defaults(func=diffresults)

        # Report
        report_parser = subparsers.add_parser('report', help="Generate report"
                                              " for job id's")
        report_parser.add_argument('job_ids', nargs='+', type=str,
                                   metavar='JOB_ID',
                                   help="job id to generate report")
        report_parser.add_argument('-a', '--all-results', action='store_true',
                                   help="show all results (not only failed ones)")
        report_parser.add_argument('-l', '--limit', type=int, metavar="LIMIT",
                                   default=500, help="set the number of test "
                                   "results to show (defaults to 500)")
        report_parser.set_defaults(func=report)

        # Results
        results_parser = subparsers.add_parser('results', help="Get (raw) results")
        results_parser.add_argument('job_ids',  nargs='+', type=str,
                                    metavar='JOB_ID', help="job id to get results")
        results_parser.set_defaults(func=results)

        # Jobdef (show job definition)
        jobdef_parser = subparsers.add_parser('jobdef',
                                              help="Show job definition file")
        jobdef_parser.add_argument('job_ids',  nargs='+', type=str,
                                   metavar='JOB_ID',
                                   help="show the job definition file for job id")
        jobdef_parser.set_defaults(func=jobdef)

        # Show running and submitted jobs
        queue_parser = subparsers.add_parser('queue', help="Show the current queue"
                                             " of running and submitted jobs")
        queue_parser.add_argument('-n', '--name', action='append', type=str,
                                  default=[], help="filter jobs with NAME "
                                  "(can be given multiple times)")
        queue_parser.add_argument('-u', '--user', type=str,
                                  help="filter jobs with user NAME")
        queue_parser.add_argument('-d', '--device', action='append', type=str,
                                  help="filter jobs running in DEVICE "
                                  "(can be given multiple times)")
        queue_parser.add_argument('-t', '--hostname', action='append', type=str,
                                  help="filter jobs running in HOSTNAME "
                                  "(can be given multiple times)")
        queue_parser.add_argument('-w', '--worker-host', action='append', type=str,
                                  help="filter jobs running in WORKER_HOST "
                                  "(can be given multiple times)")
        queue_parser.add_argument('-e', '--date', type=str, metavar="YYYYMMDD",
                                  help="show jobs with date equal or older "
                                  "than YYYYMMDD")
        queue_parser.add_argument('-s', '--show-time', action='store_true',
                                  help="show time in the queue for the jobs")
        queue_parser.set_defaults(func=queue)

        # Clean the job queue
        cleanqueue_parser = subparsers.add_parser('cleanqueue',
                                                  help="Clean jobs queue")
        cleanqueue_parser.add_argument('-n', '--name', action='append', type=str,
                                       default=[], help="clean jobs with NAME "
                                       "(can be given multiple times)")
        cleanqueue_parser.add_argument('-u', '--user', type=str,
                                       help="clean jobs with user NAME")
        cleanqueue_parser.add_argument('-d', '--device', action='append', type=str,
                                       help="clean jobs running in DEVICE "
                                       "(can be given multiple times)")
        cleanqueue_parser.add_argument('-t', '--hostname', action='append',
                                       type=str,
                                       help="clean jobs running in HOSTNAME "
                                       "(can be given multiple times)")
        cleanqueue_parser.add_argument('-w', '--worker-host', action='append',
                                       type=str,
                                       help="clean jobs running in WORKER_HOST "
                                       "(can be given multiple times)")
        cleanqueue_parser.add_argument('-e', '--date', type=str,
                                       metavar="YYYYMMDD",
                                       help="clean jobs with date equal or older "
                                       "than YYYYMMDD")
        cleanqueue_parser.set_defaults(func=cleanqueue)

        # Maintenance
        maint_parser = subparsers.add_parser('maint', help="Put the given "
                                             "device in maintenance mode")
        maint_parser.add_argument('HOSTNAME', type=str, help="Name of the device")
        maint_parser.add_argument('REASON', type=str, help="Reason to put the "
                                  "device in maintenance mode.")
        maint_parser.add_argument('--email', type=str, default='',
                                  help="Email address of the user to notify "
                                  "when the job has finished")
        maint_parser.set_defaults(func=maint)

        # Online
        online_parser = subparsers.add_parser('online', help="Put the given "
                                              "device into online mode")
        online_parser.add_argument('HOSTNAME', type=str, help="Name of the device")
        online_parser.add_argument('REASON', type=str, help="Reason to put the "
                                   "device into online mode.")
        online_parser.add_argument('--skip-health-check', action='store_true',
                                   default=False, help="Skip health check")
        online_parser.set_defaults(func=online)

        # Show devices
        devices_parser = subparsers.add_parser('devices', help="Show status of all"
                                               " available devices")
        devices_parser.set_defaults(func=devices)

        # List streams
        streams_parser = subparsers.add_parser('liststreams', help="Show streams "
                                               "the user has access to")
        streams_parser.set_defaults(func=liststreams)

        # Who am I?
        whoami_parser = subparsers.add_parser('whoami', help="Show authenticated "
                                              "user name")
        whoami_parser.set_defaults(func=whoami)

        # Server version
        sversion_parser = subparsers.add_parser('sversion',
                                                help="Show LAVA server version")
        sversion_parser.set_defaults(func=sversion)

    def run(self):
        args = self._parse_args()
        # Catch any high level exception at this point
        try:
            settings.load_config(config_file=args.config, log_file=args.log_file)
            args.func(args)
        except ProfileNotFound:
            self.parser.error('Please specify a profile file using the -g option')
        except xmlrpclib.ProtocolError as e:
            # Catch any XMLRPC protocol error at this point.
            lqa_logger.error("xmlrpc error: {}".format(e))
            exit(APPLICATION_ERROR)
        except KeyboardInterrupt:
            pass

    def _parse_args(self, args=sys.argv[1:]):
        return self.parser.parse_args(args)


def main():
    lqa = Cli()
    lqa.run()

def submit(args):
    from lqa_tool.commands.submit import SubmitCmd
    SubmitCmd(args).run()

def cancel(args):
    from lqa_tool.commands.cancel import CancelCmd
    CancelCmd(args).run()

def resubmit(args):
    from lqa_tool.commands.resubmit import ReSubmitCmd
    ReSubmitCmd(args).run()

def mkstream(args):
    from lqa_tool.commands.mkstream import MkStreamCmd
    MkStreamCmd(args).run()

def wait(args):
    from lqa_tool.commands.wait import WaitCmd
    WaitCmd(args).run()

def status(args):
    from lqa_tool.commands.status import StatusCmd
    StatusCmd(args).run()

def output(args):
    from lqa_tool.commands.output import OutputCmd
    OutputCmd(args).run()

def job(args):
    from lqa_tool.commands.job import JobCmd
    JobCmd(args).run()

def test(args):
    from lqa_tool.commands.test import TestCmd
    TestCmd(args).run()

def diffresults(args):
    from lqa_tool.commands.diffresults import DiffResultsCmd
    DiffResultsCmd(args).run()

def report(args):
    from lqa_tool.commands.report import ReportCmd
    ReportCmd(args).run()

def results(args):
    from lqa_tool.commands.results import ResultsCmd
    ResultsCmd(args).run()

def jobdef(args):
    from lqa_tool.commands.jobdef import JobDefCmd
    JobDefCmd(args).run()

def queue(args):
    from lqa_tool.commands.queue import QueueCmd
    QueueCmd(args).run()

def cleanqueue(args):
    from lqa_tool.commands.cleanqueue import CleanQueueCmd
    CleanQueueCmd(args).run()

def maint(args):
    from lqa_tool.commands.maint import MaintCmd
    MaintCmd(args).run()

def online(args):
    from lqa_tool.commands.online import OnlineCmd
    OnlineCmd(args).run()

def devices(args):
    from lqa_tool.commands.devices import DevicesCmd
    DevicesCmd(args).run()

def liststreams(args):
    from lqa_tool.commands.liststreams import ListStreamsCmd
    ListStreamsCmd(args).run()

def whoami(args):
    from lqa_tool.commands.whoami import WhoAmICmd
    WhoAmICmd(args).run()

def sversion(args):
    from lqa_tool.commands.sversion import SVersionCmd
    SVersionCmd(args).run()
