#!/usr/bin/env python
#
# Copyright 2012 SUSE Linux
#
# 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
from __future__ import unicode_literals

import argparse
from contextlib import closing
from datetime import datetime
import io
import os
import re
import shutil
import six
import subprocess
import sys
import tempfile
import requests
from six.moves import filter


RPMSPEC_BIN = "/usr/bin/rpmspec"
OSC_BIN = "/usr/bin/osc"
GITHUB_CHANGES = ("https://api.github.com/repos/%(owner)s/%(repo)s/"
                  "compare/%(v1)s...%(v2)s")


def _get_changelog_github(owner, repo, v1, v2):
    """get a list with changes for the given versions"""
    url = GITHUB_CHANGES % dict(owner=owner, repo=repo,
                                v1=v1, v2=v2)
    try:
        res = requests.get(url)
    except Exception as exc:
        print("Can not get '%s': %s" % (url, str(exc)))
        sys.exit("Can not get '%s': %s" % (url))
    if res.status_code != 200:
        raise Exception("Can not get changes from '%s'" % (url))
    data = res.json()
    commits = data.get('commits', [])
    changes = []
    for c in commits:
        msg = c['commit']['message'].splitlines()[0]
        if not msg.startswith('Merge '):
            changes += [msg]
    # remove duplicates
    return list(set(changes))


def _get_changelog(changelog_provider, v_old, v_new):
    """get changelog for the given versions"""
    changes = []
    if changelog_provider:
        provider, params = changelog_provider.split(',', 1)
        if provider == 'gh':
            owner, repo = params.split(',', 2)
            changes = _get_changelog_github(owner, repo, v_old, v_new)
        elif provider == 'none':
            changes = []
        else:
            raise Exception("Invalid changelog-provider '%s'" % (provider))
    return changes


def _get_changes_datetime():
    return datetime.utcnow().strftime('%a %b %e %T UTC %Y')


def _get_changes_string(changes, email):
    """get a .changes compatible string"""
    if not changes:
        return None
    # header
    changes_str = '-' * 67 + '\n'
    changes_str += '%s - %s\n\n' % (_get_changes_datetime(), email)
    for c in changes:
        if isinstance(c, six.string_types):
            # indent1
            changes_str += '- %s\n' % (c)
        else:
            # expect another list - indent2
            for cc in c:
                changes_str += '  - %s\n' % (cc)
    # footer
    changes_str += '\n'
    return changes_str


def _prepend_string_to_file(string, filename):
    """prepend content from string to filename"""
    with io.open(filename, encoding='utf-8', mode='r') as f:
        current = f.read()
    new = string + current
    with io.open(filename, encoding='utf-8', mode='w') as f:
        f.write(new)


def _get_spec_version(specfile):
    """get a single version from the given .spec file"""
    if not os.path.exists(specfile):
        # maybe this is the initial convertion.
        return None
    version = subprocess.check_output(
        [RPMSPEC_BIN, '-q', '--queryformat', '%{VERSION}\n', specfile])
    return list(set(filter(None, version.decode("utf-8").split('\n'))))[0]


def _obs_add_remove_version_source(old_spec_version, new_spec_version):
    """after the spec was updated and if a new version is available, remove via
    'osc' the old tarball and add the new tarball"""
    if not os.path.exists(OSC_BIN) or not \
       os.path.isdir(os.path.join(os.getcwd(), '.osc')):
        return
    extensions = ['tar.gz,', 'zip', 'tar.bz2', 'xz']
    osc_rm = [OSC_BIN, 'rm', '-f', '*-%s.{%s}' % (old_spec_version,
                                                  ','.join(extensions))]
    with open(os.devnull, 'w') as devnull:
        subprocess.call(' '.join(osc_rm), stdout=devnull,
                        stderr=subprocess.STDOUT, shell=True)

    osc_add = [OSC_BIN, 'add', '*-%s.{%s}' % (new_spec_version,
                                              ','.join(extensions))]
    with open(os.devnull, 'w') as devnull:
        subprocess.call(' '.join(osc_add), stdout=devnull,
                        stderr=subprocess.STDOUT, shell=True)


def download_file(url, dest):
    with closing(requests.get(url, stream=True)) as r:
        with open(dest, 'wb') as f:
            for chunk in r.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)


def parse_args():
    parser = argparse.ArgumentParser(description='renderspec source service')
    parser.add_argument('--outdir',
                        help='osc service parameter that does nothing')
    parser.add_argument('--input-template',
                        help='.spec.j2 template file path')
    parser.add_argument('--output-name',
                        help='name of the rendered .spec.j2 file')
    parser.add_argument('--spec-style', default='suse',
                        help='Spec style to use. Default is "%(default)s"')
    parser.add_argument('--epochs', default=None,
                        help='epochs file path')
    parser.add_argument('--requirements', default=None,
                        help='requirements file path')
    parser.add_argument('--changelog-provider', default=None,
                        help='A provider to generate a changelog. '
                        'If not set, no changelog will be generated.')
    parser.add_argument('--changelog-email', default=None,
                        help='A email address used in the .changes file. ')
    return vars(parser.parse_args())


if __name__ == '__main__':
    args = parse_args()

    if not args['input_template']:
        sys.exit('input-template must be given and must exist')

    tmpdir = tempfile.mkdtemp(prefix='renderspec_tmp_')
    try:
        if args['input_template'].startswith('http'):
            input_filename = os.path.join(tmpdir, 'input_template')
            download_file(args['input_template'], input_filename)
        else:
            input_filename = args['input_template']
        if not os.path.exists(input_filename):
            sys.exit("input file '%s' does not exist" % (input_filename))

        if args['epochs']:
            if args['epochs'].startswith('http'):
                epochs_filename = os.path.join(tmpdir, 'epochs')
                download_file(args['epochs'], epochs_filename)
            else:
                epochs_filename = args['epochs']
            if not os.path.exists(epochs_filename):
                sys.exit("epochs file '%s' does not exist" % (epochs_filename))

        if args['requirements']:
            if args['requirements'].startswith('http'):
                requirements_filename = os.path.join(tmpdir, 'requirements')
                download_file(args['requirements'], requirements_filename)
            else:
                requirements_filename = args['requirements']
            if not os.path.exists(requirements_filename):
                sys.exit("requirements file '%s' does not exist" % (
                    requirements_filename))

        if args['output_name']:
            outfile = args['output_name']
        else:
            outfile = re.sub('\.j2$', '', os.path.basename(input_filename))

        # get the version *before* we run renderspec
        old_spec_version = _get_spec_version(outfile)

        cmd = ['renderspec', '--output', outfile,
               '--spec-style', args['spec_style']]
        if args['epochs']:
            cmd += ['--epochs', epochs_filename]
        if args['requirements']:
            cmd += ['--requirements', requirements_filename]

        cmd += [input_filename]
        # run renderspec
        subprocess.check_call(cmd)

        # get the version *after* we run renderspec
        new_spec_version = _get_spec_version(outfile)

        # check for changelog generation
        changes_file = outfile.replace('.spec', '.changes')
        if old_spec_version != new_spec_version and \
           os.path.exists(changes_file):
            print("Version changed: '%s' -> '%s'"
                  % (old_spec_version, new_spec_version))
            changes = ['update to version %s' % new_spec_version]
            changes_new_version = _get_changelog(
                args['changelog_provider'], old_spec_version, new_spec_version)
            changes.append(changes_new_version)
            changes_str = _get_changes_string(changes, args['changelog_email'])
            print("Update %s" % (changes_file))
            _prepend_string_to_file(changes_str, changes_file)
            # remove/add old/new tarball from OBS
            _obs_add_remove_version_source(old_spec_version, new_spec_version)
    finally:
        shutil.rmtree(tmpdir)
