#!/usr/bin/env python3
#
# Copyright (C) 2019 Collabora Limited
# Author: Guillaume Tucker <guillaume.tucker@collabora.com>
#
# This module 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 USA

import argparse
import glob
import json
import os
import sys

from kernelci.cli import Args, Command, parse_args_with_parser
import kernelci.config.lab
import kernelci.config.test
import kernelci.build
import kernelci.lab
import kernelci.test


# -----------------------------------------------------------------------------
# Commands
#

class cmd_validate(Command):
    help = "Validate the YAML configuration"
    opt_args = [Args.verbose]

    def __call__(self, test_configs, lab_configs, args):
        # ToDo: Use jsonschema
        return True


class cmd_list_jobs(Command):
    help = "List all the jobs that need to be run for a given build and lab"
    args = [Args.bmeta_json, Args.dtbs_json, Args.lab_config]
    opt_args = [Args.user, Args.lab_token, Args.lab_json]

    def __call__(self, test_configs, lab_configs, args):
        bmeta, dtbs = kernelci.build.load_json(args.bmeta_json, args.dtbs_json)

        if bmeta['status'] != "PASS":
            return True

        lab = lab_configs['labs'][args.lab_config]
        api = kernelci.lab.get_api(
            lab, args.user, args.lab_token, args.lab_json)

        configs = kernelci.test.match_configs(
            test_configs['test_configs'], bmeta, dtbs, lab)
        for device_type, plan in configs:
            if not api.device_type_online(device_type):
                continue
            print(' '.join([device_type.name, plan.name]))

        return True


class cmd_list_plans(Command):
    help = "List all the existing test plan names"

    def __call__(self, test_configs, lab_configs, args):
        plans = set(plan.name
                    for plan in list(test_configs['test_plans'].values()))
        for plan in sorted(plans):
            print(plan)
        return True


class cmd_list_labs(Command):
    help = "List all the existing lab names"

    def __call__(self, test_configs, lab_configs, args):
        for lab in sorted(lab_configs['labs'].keys()):
            print(lab)
        return True


class cmd_get_lab_info(Command):
    help = "Get the information about a lab into a JSON file"
    args = [Args.lab_config, Args.lab_json]
    opt_args = [Args.user, Args.lab_token]

    def __call__(self, test_configs, lab_configs, args):
        lab = lab_configs['labs'][args.lab_config]
        lab_api = kernelci.lab.get_api(lab, args.user, args.lab_token)
        data = {
            'lab': lab.name,
            'lab_type': lab.lab_type,
            'url': lab.url,
            'devices': lab_api.devices,
        }
        dir = os.path.dirname(args.lab_json)
        if dir and not os.path.exists(dir):
            os.makedirs(dir)
        with open(args.lab_json, 'w') as json_file:
            json.dump(data, json_file, indent=4, sort_keys=True)
        return True


class cmd_generate(Command):
    help = "Generate the job definition for a given build"
    args = [Args.bmeta_json, Args.dtbs_json, Args.storage, Args.lab_config]
    opt_args = [Args.plan, Args.target, Args.output,
                Args.lab_json, Args.user, Args.lab_token,
                Args.callback_id, Args.callback_dataset,
                Args.callback_type, Args.callback_url, Args.mach]

    def __call__(self, test_configs, lab_configs, args):
        if args.callback_id and not args.callback_url:
            print("--callback-url is required with --callback-id")
            return False

        bmeta, dtbs = kernelci.build.load_json(args.bmeta_json, args.dtbs_json)

        if bmeta['status'] != "PASS":
            return True

        lab = lab_configs['labs'][args.lab_config]
        api = kernelci.lab.get_api(
            lab, args.user, args.lab_token, args.lab_json)

        if args.target and args.plan:
            target = test_configs['device_types'][args.target]
            plan = test_configs['test_plans'][args.plan]
            jobs_list = [(target, plan)]
        else:
            jobs_list = []
            configs = kernelci.test.match_configs(
                test_configs['test_configs'], bmeta, dtbs, lab)
            for device_type, plan in configs:
                if not api.device_type_online(device_type):
                    continue
                if args.target and device_type.name != args.target:
                    continue
                if args.plan and plan.name != args.plan:
                    continue
                if args.mach and device_type.mach != args.mach:
                    continue
                jobs_list.append((device_type, plan))

        # ToDo: deal with a JSON file containing a list of builds to iterate
        # over, such as the one produced by "kci_build publish" or saved as a
        # result of a new command "kci_build get_meta" to download meta-data
        # from the backend API.
        callback_opts = {
            'id': args.callback_id,
            'dataset': args.callback_dataset,
            'type': args.callback_type,
            'url': args.callback_url,
        }
        if args.output and not os.path.exists(args.output):
            os.makedirs(args.output)
        for target, plan in jobs_list:
            params = kernelci.test.get_params(
                bmeta, target, plan, args.storage)
            job = api.generate(params, target, plan, callback_opts)
            if job is None:
                print("Failed to generate the job definition")
                return False
            if args.output:
                file_name = api.job_file_name(params)
                output_file = os.path.join(args.output, file_name)
                print(output_file)
                with open(output_file, 'w') as output:
                    output.write(job)
            else:
                print("# Job: {}".format(params['name']))
                print(job)
        return True


class cmd_submit(Command):
    help = "Submit job definitions to a lab"
    args = [Args.lab_config, Args.user, Args.lab_token, Args.jobs]

    def __call__(self, test_configs, lab_configs, args):
        lab = lab_configs['labs'][args.lab_config]
        lab_api = kernelci.lab.get_api(lab, args.user, args.lab_token)
        job_paths = glob.glob(args.jobs)
        res = True
        for path in job_paths:
            if not os.path.isfile(path):
                continue
            with open(path, 'r') as job_file:
                job = job_file.read()
                try:
                    job_id = lab_api.submit(job)
                    print("{} {}".format(job_id, path))
                except Exception as e:
                    print("ERROR {}: {}".format(path, e))
                    res = False
        return res


# -----------------------------------------------------------------------------
# Main
#

if __name__ == '__main__':
    parser = argparse.ArgumentParser("kci_test")
    parser.add_argument("--yaml-test-configs", default="test-configs.yaml",
                        help="Path to the YAML test configs file")
    parser.add_argument("--yaml-lab-configs", default="lab-configs.yaml",
                        help="Path to the YAML lab configs file")
    args = parse_args_with_parser(parser, globals())
    test_configs = kernelci.config.test.from_yaml(args.yaml_test_configs)
    lab_configs = kernelci.config.lab.from_yaml(args.yaml_lab_configs)
    status = args.func(test_configs, lab_configs, args)
    sys.exit(0 if status is True else 1)
