#!/usr/bin/env python
# Copyright (c) 2016 SUSE Linux GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function

import argparse
import contextlib
import getpass
import itertools
import os
import re
import subprocess
import sys
import threading
from socket import timeout

from jenkinsapi.jenkins import Jenkins

import paramiko

from six.moves import configparser
from six.moves import input


# path to the user configuration file
USER_CONFIG_PATH = os.path.expanduser('~/.soc-ci.ini')

# the default config section
CONFIG_SECTION = "defaults"

# possible mkcX hosts
CI_WORKERS = list('abcdefghijklmnop')


def _config_credentials_get():
    """get username, password and url"""
    user = input("username: ")
    password = getpass.getpass()
    url = input("url: ")
    nick = input("nick (used for CI reservations, eg 'tbechtold'): ")
    return user, password, url, nick


def _config_get():
    """get the configuration"""
    conf = configparser.RawConfigParser()
    conf.read([USER_CONFIG_PATH])
    if not conf.has_section(CONFIG_SECTION):
        user, password, url, nick = _config_credentials_get()
        conf.add_section(CONFIG_SECTION)
        conf.set(CONFIG_SECTION, "user", user)
        conf.set(CONFIG_SECTION, "password", password)
        conf.set(CONFIG_SECTION, "url", url)
        conf.set(CONFIG_SECTION, "nick", nick)
        with os.fdopen(os.open(
                USER_CONFIG_PATH, os.O_WRONLY | os.O_CREAT, 0o600), 'w') as f:
            conf.write(f)

    # some optional configuration options
    if not conf.has_option(CONFIG_SECTION, "artifacts_dir"):
        conf.set(CONFIG_SECTION, "artifacts_dir",
                 os.path.expanduser('~/.soc-ci/artifacts/'))
    if not conf.has_option(CONFIG_SECTION, "ssl_verify"):
        conf.set(CONFIG_SECTION, "ssl_verify", "True")

    # create things where needed
    if not os.path.exists(conf.get(CONFIG_SECTION, "artifacts_dir")):
        os.makedirs(conf.get(CONFIG_SECTION, "artifacts_dir"))

    return conf


def _get_jenkins_job(conf=None):
    if conf is None:
        conf = _config_get()
    server = Jenkins(conf.get(CONFIG_SECTION, 'url'),
                     username=conf.get(CONFIG_SECTION, 'user'),
                     password=conf.get(CONFIG_SECTION, 'password'),
                     ssl_verify=conf.getboolean(CONFIG_SECTION, 'ssl_verify'))
    return server['openstack-mkcloud']


###############################################################################
@contextlib.contextmanager
def changedir(newdir):
    olddir = os.getcwd()
    os.chdir(os.path.expanduser(newdir))
    try:
        yield
    finally:
        os.chdir(olddir)


def _ssh_get_client():
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    return client


def _os_mkcloud_where(job_id):
    job = _get_jenkins_job()

    build = job.get_build(job_id)
    console = build.get_console()
    m = re.search(r'.*access from outside via http://(.*)/ and .*', console)
    if m:
        return m.group(1)
    return None


def os_mkcloud_where(args):
    where = _os_mkcloud_where(args.job_id)
    if where:
        print(where)
        sys.exit(0)
    sys.exit('no idea where openstack-mkcloud job %s runs' % args.job_id)


def os_mkcloud_console_log(args):
    job = _get_jenkins_job()

    build = job.get_build(args.job_id)
    print(build.get_console())
    sys.exit(0)


def os_mkcloud_available(args):
    crowbar_node = _os_mkcloud_where(args.job_id)
    if crowbar_node:
        client = _ssh_get_client()
        client.connect(crowbar_node, username='root', password='linux')
        stdin, stdout, stderr = client.exec_command('cat /etc/motd')
        m = re.search(r'\s*created by the job:\s*https://ci.suse.de/job/'
                      'openstack-mkcloud/(.*)/.*', stdout.read())
        if m:
            found_job = int(m.group(1))
            if args.job_id == found_job:
                print('job env for %s still available on "%s"' % (
                    args.job_id, crowbar_node))
                sys.exit(0)
            else:
                sys.exit('job env no longer available')
    else:
        sys.exit('no idea where openstack-mkcloud job %s runs' % args.job_id)


def _unpack_supportconfigs(cwd):
    sucessful = True
    with changedir(cwd):
        for (dirpath, dirnames, filenames) in os.walk('.'):
            for f in itertools.ifilter(lambda x: x.endswith('tbz'), filenames):
                with open('/dev/null', 'w') as devnull:
                    try:
                        subprocess.check_call(
                            'unpack-supportconfig %s' % f,
                            stdout=devnull, stderr=devnull, shell=True)
                    except subprocess.CalledProcessError as e:
                        print('Something failed while trying to unpack the '
                              'supportconfigs: %s' % (str(e)))
                        sucessful = False
    return sucessful


def os_mkcloud_artifacts(args):
    """download artifacts for the given job"""
    conf = _config_get()
    job = _get_jenkins_job(conf=conf)

    build = job.get_build(args.job_id)
    dest_dir = os.path.join(conf.get(CONFIG_SECTION, 'artifacts_dir'),
                            'os-mkcloud', '%s' % args.job_id)
    if os.path.exists(dest_dir):
        if not args.force:
            print('Artifacts already available under %s' % dest_dir)
            sys.exit(0)
    else:
        os.makedirs(dest_dir)

    for filename, artifact in build.get_artifact_dict().items():
        artifact.save_to_dir(dest_dir)
    print('Artifacts downloaded to %s' % dest_dir)
    if _unpack_supportconfigs(dest_dir):
        print('supportconfigs available in %s' % dest_dir)
    sys.exit(0)


def _get_worker_pool_list(worker, data_dict, data_lock):
    """write a worker pool list output to the data_dict"""
    client = _ssh_get_client()
    try:
        client.connect('mkch%s.cloud.suse.de' % worker,
                       username='root', password='linux', timeout=3)
    except timeout:
        pass
    else:
        stdin, stdout, stderr = client.exec_command('ls /root/pool/')
        if data_lock:
            with data_lock:
                data_dict[worker] = stdout.read().strip().split()
        else:
            data_dict[worker] = stdout.read().strip().split()


def ci_worker_pool_list(args):
    data = dict()
    _get_worker_pool_list(args.worker, data, None)
    print('\n'.join(data[args.worker]))
    sys.exit(0)


def ci_worker_pool_reserve(args):
    conf = _config_get()
    client = _ssh_get_client()
    client.connect('mkch%s.cloud.suse.de' % args.worker,
                   username='root', password='linux')
    stdin, stdout, stderr = client.exec_command(
        'mv /root/pool/%s /root/pool/%s.%s.`date "+%%Y-%%m-%%d"`' % (
            args.slot,
            args.slot, conf.get(CONFIG_SECTION, 'nick')))
    print(stdout.read())
    sys.exit(stdout.channel.recv_exit_status())


def ci_worker_pool_release(args):
    conf = _config_get()
    client = _ssh_get_client()
    client.connect('mkch%s.cloud.suse.de' % args.worker,
                   username='root', password='linux')
    stdin, stdout, stderr = client.exec_command(
        'mv /root/pool/%s.%s.* /root/pool/%s' % (
            args.slot, conf.get(CONFIG_SECTION, 'nick'), args.slot))
    print(stdout.read())
    sys.exit(stdout.channel.recv_exit_status())


def ci_workers_pool_list(args):
    conf = _config_get()
    data = dict()
    data_lock = threading.Lock()
    threads = []
    for worker in CI_WORKERS:
        t = threading.Thread(target=_get_worker_pool_list,
                             args=(worker, data, data_lock))
        t.start()
        threads.append(t)
    for t in threads:
        t.join()

    for worker in sorted(data.keys()):
        print('### mkch%s.cloud.suse.de ###' % worker)
        for pool in data[worker]:
            if args.all or pool.find('.%s' % conf.get(
                    CONFIG_SECTION, 'nick')) != -1:
                print(pool)
    sys.exit(0)


def parse_args():
    parser = argparse.ArgumentParser(description='Useful commands for the CI')
    subparsers = parser.add_subparsers(help='sub-command help')

    # where runs a openstack-mkcloud job
    parser_os_mkcloud_where = subparsers.add_parser(
        'os-mkcloud-where', help='openstack-mkcloud: where run the job?')
    parser_os_mkcloud_where.add_argument('job_id', metavar='ID', type=int)
    parser_os_mkcloud_where.set_defaults(func=os_mkcloud_where)

    # a openstack-mkcloud job console log
    parser_os_mkcloud_console_log = subparsers.add_parser(
        'os-mkcloud-console-log', help='openstack-mkcloud: print console log')
    parser_os_mkcloud_console_log.add_argument('job_id', metavar='ID',
                                               type=int)
    parser_os_mkcloud_console_log.set_defaults(func=os_mkcloud_console_log)

    # is a openstack-mkcloud job still available in the CI?
    parser_os_mkcloud_available = subparsers.add_parser(
        'os-mkcloud-available', help='openstack-mkcloud: job env still '
        'available?')
    parser_os_mkcloud_available.add_argument('job_id', metavar='ID', type=int)
    parser_os_mkcloud_available.set_defaults(func=os_mkcloud_available)

    # download openstack-mkcloud job artifacts
    parser_os_mkcloud_artifacts = subparsers.add_parser(
        'os-mkcloud-artifacts', help='openstack-mkcloud: download artifacts')
    parser_os_mkcloud_artifacts.add_argument('job_id', metavar='ID', type=int)
    parser_os_mkcloud_artifacts.add_argument('--force', action='store_true',
                                             help='Force download even if'
                                             'artifacts already exist')
    parser_os_mkcloud_artifacts.set_defaults(func=os_mkcloud_artifacts)

    # worker pool list
    parser_worker_pool_list = subparsers.add_parser(
        'worker-pool-list', help='List CI worker pool for a single worker')
    parser_worker_pool_list.add_argument('worker', type=str,
                                         choices=CI_WORKERS)
    parser_worker_pool_list.set_defaults(func=ci_worker_pool_list)

    # worker pool reserve
    parser_worker_pool_reserve = subparsers.add_parser(
        'worker-pool-reserve', help='Reserve a pool from a CI worker')
    parser_worker_pool_reserve.add_argument('worker', type=str,
                                            choices=CI_WORKERS)
    parser_worker_pool_reserve.add_argument('slot', type=int)
    parser_worker_pool_reserve.set_defaults(func=ci_worker_pool_reserve)

    # worker pool release
    parser_worker_pool_release = subparsers.add_parser(
        'worker-pool-release', help='Release a pool from a CI worker')
    parser_worker_pool_release.add_argument('worker', type=str,
                                            choices=CI_WORKERS)
    parser_worker_pool_release.add_argument('slot', type=int)
    parser_worker_pool_release.set_defaults(func=ci_worker_pool_release)

    # workers pool list - list *all* workers pool
    parser_workers_pool_list = subparsers.add_parser(
        'workers-pool-list', help='List CI workers pool for all workers')
    parser_workers_pool_list.add_argument('--all',
                                          action='store_true',
                                          help='Show all reservations.')
    parser_workers_pool_list.set_defaults(func=ci_workers_pool_list)

    return parser.parse_args()


def main():
    args = parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
