# -*- coding: utf-8 -*-
# Copyright (C) 2013-2018 by The Linux Foundation and contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys

import grokmirror
import logging
import fnmatch
import subprocess

logger = logging.getLogger(__name__)


def git_rev_parse_all(gitdir):
    args = ['rev-parse', '--all']
    retcode, output, error = grokmirror.run_git_command(gitdir, args)

    if error:
        # Put things we recognize into debug
        debug = list()
        warn = list()
        for line in error.split('\n'):
            warn.append(line)
        if debug:
            logger.debug('Stderr: %s', '\n'.join(debug))
        if warn:
            logger.warning('Stderr: %s', '\n'.join(warn))

    return output


def git_remote_update(args, fullpath):
    retcode, output, error = grokmirror.run_git_command(fullpath, args)

    if error:
        # Put things we recognize into debug
        debug = list()
        warn = list()
        for line in error.split('\n'):
            if line.find('From ') == 0:
                debug.append(line)
            elif line.find('-> ') > 0:
                debug.append(line)
            else:
                warn.append(line)
        if debug:
            logger.debug('Stderr: %s', '\n'.join(debug))
        if warn:
            logger.warning('Stderr: %s', '\n'.join(warn))


def dumb_pull_repo(gitdir, remotes, svn=False):
    # verify it's a git repo and fetch all remotes
    logger.debug('Will pull %s with following remotes: %s', gitdir, remotes)
    old_revs = git_rev_parse_all(gitdir)

    if not old_revs:
        logger.critical('Error opening %s.', gitdir)
        logger.critical('Make sure it is a bare git repository.')
        sys.exit(1)

    try:
        grokmirror.lock_repo(gitdir, nonblocking=True)
    except IOError:
        logger.info('Could not obtain exclusive lock on %s', gitdir)
        logger.info('\tAssuming another process is running.')
        return False

    if svn:
        logger.debug('Using git-svn for %s', gitdir)

        for remote in remotes:
            # arghie-argh-argh
            if remote == '*':
                remote = '--all'

            logger.info('Running git-svn fetch %s in %s', remote, gitdir)
            args = ['svn', 'fetch', remote]
            git_remote_update(args, gitdir)

    else:
        # Not an svn remote
        myremotes = grokmirror.list_repo_remotes(gitdir)
        if not len(myremotes):
            logger.info('Repository %s has no defined remotes!', gitdir)
            return False

        logger.debug('existing remotes: %s', myremotes)
        for remote in remotes:
            remotefound = False
            for myremote in myremotes:
                if fnmatch.fnmatch(myremote, remote):
                    remotefound = True
                    logger.debug('existing remote %s matches %s', myremote, remote)
                    args = ['remote', 'update', myremote, '--prune']
                    logger.info('Updating remote %s in %s', myremote, gitdir)

                    git_remote_update(args, gitdir)

            if not remotefound:
                logger.info('Could not find any remotes matching %s in %s', remote, gitdir)

    grokmirror.unlock_repo(gitdir)
    new_revs = git_rev_parse_all(gitdir)

    if old_revs == new_revs:
        logger.debug('No new revs, no updates')
        return False

    logger.debug('New revs found -- new content pulled')
    return True


def run_post_update_hook(hookscript, gitdir):
    if hookscript == '':
        return
    if not os.access(hookscript, os.X_OK):
        logger.warning('post_update_hook %s is not executable', hookscript)
        return

    args = [hookscript, gitdir]
    logger.debug('Running: %s', ' '.join(args))
    (output, error) = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

    error = error.decode().strip()
    output = output.decode().strip()
    if error:
        # Put hook stderror into warning
        logger.warning('Hook Stderr: %s', error)
    if output:
        # Put hook stdout into info
        logger.info('Hook Stdout: %s', output)


def parse_args():
    import argparse
    # noinspection PyTypeChecker
    op = argparse.ArgumentParser(prog='grok-dumb-pull',
                                 description='Fetch remotes in repositories not managed by grokmirror',
                                 formatter_class=argparse.ArgumentDefaultsHelpFormatter)

    op.add_argument('-v', '--verbose', dest='verbose', action='store_true',
                    default=False,
                    help='Be verbose and tell us what you are doing')
    op.add_argument('-s', '--svn', dest='svn', action='store_true',
                    default=False,
                    help='The remotes for these repositories are Subversion')
    op.add_argument('-r', '--remote-names', dest='remotes', action='append',
                    default=None,
                    help='Only fetch remotes matching this name (accepts shell globbing)')
    op.add_argument('-u', '--post-update-hook', dest='posthook',
                    default='',
                    help='Run this hook after each repository is updated.')
    op.add_argument('-l', '--logfile', dest='logfile',
                    default=None,
                    help='Put debug logs into this file')
    op.add_argument('--version', action='version', version=grokmirror.VERSION)
    op.add_argument('paths', nargs='+', help='Full path(s) of the repos to pull')

    opts = op.parse_args()

    if not len(opts.paths):
        op.error('You must provide at least a path to the repos to pull')

    return opts


def dumb_pull(paths, verbose=False, svn=False, remotes=None, posthook='', logfile=None):
    global logger

    loglevel = logging.INFO
    logger = grokmirror.init_logger('dumb-pull', logfile, loglevel, verbose)

    if remotes is None:
        remotes = ['*']

    # Find all repositories we are to pull
    for entry in paths:
        if entry[-4:] == '.git':
            if not os.path.exists(entry):
                logger.critical('%s does not exist', entry)
                continue

            logger.debug('Found %s', entry)
            didwork = dumb_pull_repo(entry, remotes, svn=svn)
            if didwork:
                run_post_update_hook(posthook, entry)

        else:
            logger.debug('Finding all git repos in %s', entry)
            for founddir in grokmirror.find_all_gitdirs(entry):
                didwork = dumb_pull_repo(founddir, remotes, svn=svn)
                if didwork:
                    run_post_update_hook(posthook, founddir)


def command():
    opts = parse_args()

    return dumb_pull(
        opts.paths, verbose=opts.verbose, svn=opts.svn, remotes=opts.remotes,
        posthook=opts.posthook, logfile=opts.logfile)


if __name__ == '__main__':
    command()
