#!/usr/bin/env python3
# (ignore bad script name) pylint: disable=C0103
"""
A script for downloading VO data.
"""
import glob
import os
from shlex import quote
import shutil
import subprocess
import sys
import tempfile

from optparse import OptionParser


verbose = False

class Error(Exception):
    "Class for expected exceptions"
    pass


def get_options(argv):
    "Parse, validate, and transform command-line options."
    parser = OptionParser("%prog [--repo <REPO>] [--location root|<DIR>]\n")

    parser.add_option("--repo", metavar="REPO", default=None,
                      help="The specific yum repo to download the VO data from. Downloads from the OSG repo by default.")
    parser.add_option("--location", metavar="DIR", default='root',
                      help="The location to download the VO data to. If not specified, or specified as 'root', will "
                      "download to $OSG_LOCATION/etc in a tarball install, or /etc in a non-tarball install")

    options, _ = parser.parse_args(argv[1:]) # raises SystemExit(2) on error

    if not options.location or options.location == 'root':
        if is_rpm_install(argv):
            options.location = '/etc'
        else:
            osg_location = os.environ.get('OSG_LOCATION', '')
            if os.path.exists(osg_location):
                options.location = os.path.join(osg_location, 'etc')
            else:
                raise Error("$OSG_LOCATION is not set or doesn't exist. Did you source setup.sh?")

    return options


def is_rpm_install(argv):
    ret = os.system("rpm -qf " + quote(argv[0]) + " >/dev/null 2>&1")
    return ret == 0


def _get_osg_repo_args():
    """Get the arguments to yumdownloader for using the osg repo whose repo
    definition file is possibly in a tarball client environment.
    """
    args = []

    osg_location = os.environ.get('OSG_LOCATION', '/')
    # point yum at the repo file
    repos_dir = os.path.join(osg_location, 'etc/yum.repos.d')
    osg_repo_file = os.path.join(repos_dir, 'osg.repo')
    if not os.path.exists(osg_repo_file):
        raise Error("Repo definition file for OSG repos not found. Is 'osg-release' installed?")

    args.append('--config=' + osg_repo_file)

    # The GPG key location in the repo file assumes the keys are in a
    # rootly location; we need to override them if we're in a tarball install.
    # We don't know the exact name of the key file but yum allows multiple keys
    # so we can take advantage of that.
    gpg_key_files_list = glob.glob(os.path.join(osg_location,
                                                'etc/pki/rpm-gpg/RPM-GPG-KEY-OSG*'))
    if not gpg_key_files_list:
        raise Error("OSG repo GPG key(s) not found. Is 'osg-release' installed?")

    args.append("--setopt=osg.gpgkey=%s" % " ".join("file://" + gkf for gkf in gpg_key_files_list))

    return args


def _download_vodata(repo=None):
    """Download and extract the vo-client RPM. Current directory should be an
    empty temporary directory.
    """
    cleancachecmd = ['yum', 'clean', 'all']
    downloadercmd = ['yumdownloader', 'vo-client']
    if repo:
        args = ['--disablerepo=*', '--enablerepo=' + repo]
    else:
        # Download from the osg repo. In a tarball install, the repo
        # definition file may be in $OSG_LOCATION/etc/yum.repos.d/...
        # so use that if available.
        args = _get_osg_repo_args()
    assert args, "one of these two shoulda worked"

    for command, notfoundcomplaint, errorcomplaint in \
            [ (cleancachecmd, "yum not found -- install yum",                 "Couldn't clean yum cache: ")
            , (downloadercmd, "yumdownloader not found -- install yum-utils", "Couldn't download vo-client: ")
            ]:
        try:
            subprocess.check_call(command + args)
        except FileNotFoundError as err:
            raise Error(notfoundcomplaint)
        except subprocess.CalledProcessError as err:
            raise Error(errorcomplaint + str(err))

    try:
        vodata_rpm = glob.glob('vo-client-*.rpm')[0]
    except IndexError as err:
        raise Error("Downloaded RPM not found")

    subprocess.check_call("rpm2cpio %s | cpio -id --quiet" % quote(vodata_rpm), shell=True)


def rmtree(path, ignore_errors=False):
    """Remove `path`, recursively deleting if it is a directory.
    Wrapper around shutil.rmtree() but also handles non-directories,
    symlinks to directories, and does not a raise an error if `path` does not
    exist.
    """
    if not os.path.exists(path):
        return
    if not os.path.isdir(path) or os.path.islink(path):
        os.unlink(path)
    else:
        shutil.rmtree(path, ignore_errors)


def _move_vodata(destdir):
    """Move VO data to destdir. Current directory must be the directory that
    vomses and vomsdir are in, and destdir must be an absolute path.
    """
    # The vo-client RPM has this structure:
    #
    # etc/vomses
    # etc/grid-security/vomsdir/*/*.lsc
    #
    # Move it so that "etc" is replaced by destdir.
    # Try not to leave vomsdir in an inconsistent state if an error happens.
    # Leave existing files/directories behind as vomses.old and vomsdir.old.

    if not os.path.exists(destdir):
        os.makedirs(destdir)
    elif not os.path.isdir(destdir):
        raise Error(destdir + " exists but is not a directory")

    vomsesfile = os.path.join(destdir, 'vomses')
    oldvomsesfile = os.path.join(destdir, 'vomses.old')
    if os.path.exists(vomsesfile):
        os.rename(vomsesfile, oldvomsesfile)
    shutil.move('etc/vomses', vomsesfile)

    gridsecdir = os.path.join(destdir, 'grid-security')
    if not os.path.exists(gridsecdir):
        os.makedirs(gridsecdir)
    elif not os.path.isdir(gridsecdir):
        raise Error(gridsecdir + " exists but is not a directory")

    vomsdir = os.path.join(gridsecdir, 'vomsdir')
    newdir = os.path.join(gridsecdir, 'vomsdir.new')
    olddir = os.path.join(gridsecdir, 'vomsdir.old')

    rmtree(newdir)
    shutil.move('etc/grid-security/vomsdir', newdir)

    if os.path.exists(vomsdir):
        rmtree(olddir)
        os.rename(vomsdir, olddir)
    os.rename(newdir, vomsdir)


def update_vodata(destdir, repo=None):
    "Fetch the VO data, and move it to its proper place"
    destdir = os.path.abspath(destdir)
    oldcwd = os.getcwd()
    tempdir = tempfile.mkdtemp(prefix='vodata')
    try:
        os.chdir(tempdir)
        _download_vodata(repo)
        _move_vodata(destdir)
        print("VO data downloaded to " + destdir)
    finally:
        os.chdir(oldcwd)
        rmtree(tempdir, ignore_errors=True)


def main(argv):
    try:
        options = get_options(argv)
        update_vodata(options.location, options.repo)
    except Error as err:
        print("Error: " + str(err), file=sys.stderr)
        return 1

    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))

