#!/usr/bin/python3
SVER = '2.0.13'
##############################################################################
# gstat - Show the current GitHub status of the repository
# Copyright (C) 2023 SUSE LLC
#
# Description:  Checks the status of the CWD repository or all repos if no
#               git repo in CWD.
# Modified:     2023 Oct 31
#
##############################################################################
#
#  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; version 2 of the License.
#
#  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/>.
#
#  Authors/Contributors:
#     Jason Record <jason.record@suse.com>
#
##############################################################################

import sys
import os
import re
import getopt
import signal
import subprocess
import configparser
import patdevel as pd

##############################################################################
# Global Options
##############################################################################

title_string = "SCA GitHub Repository Status"

##############################################################################
# Functions
##############################################################################

def usage():
    "Displays usage information"
    display = "  {:33s} {}"
    print("Usage: gstat [options] [github_directory_path]")
    print()
    print("Options:")
    print(display.format("-h, --help", "Display this help"))
    print(display.format("-a, --all", "Check all extra options, same as -bsgd"))
    print(display.format("-r, --repos", "Force checking configured reposistories"))
    print(display.format("-b, --branches", "Show all local repository branches"))
    print(display.format("-s, --show-branch", "Show repository branches and their commits"))
    print(display.format("-g, --log", "Show the commit log"))
    print(display.format("-d, --diff", "Show the difference between local changes and the branch"))
    print(display.format('-l <level>, --log_level <level>', "Set log level, default: Minimal"))
    print(display.format('', "0 Quiet, 1 Minimal, 2 Normal, 3 Verbose, 4 Debug"))
    print()

def signal_handler(sig, frame):
    print("\n\nAborting...\n")
    show_summary()
    sys.exit(1)

def show_git_branches(_msg, data):
    _msg.min("+ Output", 'git --no-pager branch -a')
    _msg.min()
    for line in data['branches']:
        print(line)
    _msg.min()

def show_git_show_branch(_msg, data):
    _msg.min("+ Output", 'git --no-pager show_branch # before ---')
    _msg.min()
    for line in data['show_branch']:
        print(line)
    _msg.min()

def show_git_log(_msg, data):
    commits_required = 4 # if set to 0, get all commits
    commits_current = 0
    _msg.min("+ Output", 'git --no-pager log')
    _msg.min()
    if( _msg.get_level() > _msg.LOG_MIN ):
        commits_required = 0
    if commits_required > 0:
        for line in data['log']:
            if line.startswith('commit '):
                if commits_current < commits_required:
                    commits_current += 1
                else:
                    break
            print(line)
    else:
        for line in data['log']:
            print(line)

    _msg.min()

def show_git_diff(_msg, data):
    _msg.min("+ Output", 'git --no-pager diff')
    if len(data['diff']) > 0:
        _msg.min()
        for line in data['diff']:
            print(line)
    else:
        _msg.min('  No differences found')
    _msg.min()

def show_git_status(_msg, data):
    _msg.min("+ Output", 'git status')
    _msg.min()
    for line in data['content']:
        _msg.min(line)
    _msg.min()

def show_repo_status(_msg, _repo_data, details = False):
    DISPLAY_OFFSET = 42
    size_base = pd.SEPARATOR_LEN - DISPLAY_OFFSET
    size_brackets = 2
    if details:
        pd.separator_line('-')
    size_state = len(_repo_data['state'])
    size = size_base - size_brackets - size_state
    state_display = "Branch: {:" + str(size) + "} [{}]"
    _msg.min("Status " + _repo_data['name'], state_display.format(_repo_data['branch'], _repo_data['state']))

    if details:
        show_git_status(_msg, _repo_data)

    if( _msg.get_level() >= _msg.LOG_DEBUG ):
        for key, value in _repo_data.items():
            _msg.debug("  <show_repo_status> " + key, str(value))

def _show_repo_status_list(_msg, _repo_path_list):
    this_log_level = _msg.get_level()
    prev_log_level = this_log_level
    show_details = False
    if this_log_level == _msg.LOG_NORMAL:
        show_details = True
        this_log_level = _msg.LOG_MIN
        _msg.set_level(this_log_level)
    for path in _repo_path_list:
        git_repo = pd.GitHubRepository(_msg, path)
        repo_data = git_repo.get_info()
        if repo_data['valid']:
            if this_log_level == _msg.LOG_MIN:
                if repo_data['outdated']:
                    show_repo_status(_msg, repo_data, show_details)
                else:
                    show_repo_status(_msg, repo_data, details=False)
            elif this_log_level > _msg.LOG_NORMAL:
                show_repo_status(_msg, repo_data, details=True)
        else:
            _msg.min("+ Remove directory " + path)
    _msg.set_level(prev_log_level)

def show_all_repo_status(_msg, _config):
    repo_dir = pd.config_entry(_config.get("Common", "sca_repo_dir"), '/')
    repo_list = pd.config_entry(_config.get("GitHub", "patdev_repos")).split(',')
    repo_paths = []
    _msg.min("Configuration repositories", repo_dir)
    pd.check_git_repos(_config, _msg)
    for this_repo in repo_list:
        repo_paths.append(repo_dir + this_repo)
    _show_repo_status_list(_msg, repo_paths)

def show_discovered_repo_status(_msg, _path):
    _msg.min("Discover GitHub repos in", _path)
    git_repos_found = []
    for root, dirs, files in os.walk(_path, topdown = True):
        for name in dirs:
            git_dir = os.path.join(root, name)
            git_config_file = git_dir + "/.git/config"
            if( os.path.isfile(git_config_file) ):
                git_repos_found.append(git_dir)
        break
    git_repos_found.sort()
    count = len(git_repos_found)
    _msg.min("+ GitHub repositories", str(count))
    if count > 0:
        _show_repo_status_list(_msg, git_repos_found)

##############################################################################
# Main
##############################################################################

def main(argv):
    "main entry point"
    global SVER
    force_all = False
    opt_branches = False
    opt_show_branches = False
    opt_log = False
    opt_diff = False
    opt_extra = False
    user_logging = -1

    if( os.path.exists(pd.config_file) ):
        config.read(pd.config_file)
        config_log_level = pd.config_entry(config.get("Common", "log_level"))
        config_logging = msg.validate_level(config_log_level)
        if( config_logging >= msg.LOG_QUIET ):
            msg.set_level(config_logging)
        else:
            print("Warning: Invalid log level in config file, using instance default")
    else:
        pd.title(title_string, SVER)
        print("Error: Config file not found - {}".format(pd.config_file))
        print()
        sys.exit(1)

    try:
        (optlist, args) = getopt.gnu_getopt(argv[1:], "habsgdrl:", ["help", "all", "branches", "show-branch", "log", "diff", "repos", "log_level="])
    except getopt.GetoptError as exc:
        pd.title(title_string, SVER)
        print("Error:", exc, file=sys.stderr)
        sys.exit(2)
    for opt, arg in optlist:
        if opt in {"-h", "--help"}:
            pd.title(title_string, SVER)
            usage()
            sys.exit(0)
        elif opt in {"-a", "--all"}:
            opt_branches = True
            opt_show_branches = True
            opt_log = True
            opt_diff = True
            opt_extra = True
        elif opt in {"-r", "--repos"}:
            force_all = True
        elif opt in {"-b", "--branches"}:
            opt_branches = True
            opt_extra = True
        elif opt in {"-s", "--show-branch"}:
            opt_show_branches = True
            opt_extra = True
        elif opt in {"-g", "--log"}:
            opt_log = True
            opt_extra = True
        elif opt in {"-d", "--diff"}:
            opt_diff = True
            opt_extra = True
        elif opt in {"-l", "--log_level"}:
            user_logging = msg.validate_level(arg)
            if( user_logging >= msg.LOG_QUIET ):
                msg.set_level(user_logging)
            else:
                print("Warning: Invalid log level, using instance default")

    if( msg.get_level() > msg.LOG_QUIET ):
        pd.title(title_string, SVER)

    if user_logging < 0:
        msg.set_level(msg.LOG_MIN)

    if force_all:
        msg.normal("Log Level", msg.get_level_str())
        show_all_repo_status(msg, config)
    else:
        given_dir = ''
        if len(args) > 0: # A path to a proposed GitHub repository was given on the command line
            given_dir = args[0]
            msg.normal("Log Level", msg.get_level_str())
            if os.path.isdir(given_dir):
                path = os.path.abspath(given_dir)
                git_repo = pd.GitHubRepository(msg, path)
                repo_data = git_repo.get_info()
                if repo_data['valid']:
                    if repo_data['outdated']:
                        show_repo_status(msg, repo_data, details=True)
                    else:
                        if msg.get_level() > msg.LOG_MIN:
                            show_repo_status(msg, repo_data, details=True)
                        else:
                            show_repo_status(msg, repo_data, details=False)
                    if opt_branches:
                        show_git_branches(msg, repo_data)
                    if opt_show_branches:
                        show_git_show_branch(msg, repo_data)
                    if opt_log:
                        show_git_log(msg, repo_data)
                    if opt_diff:
                        show_git_diff(msg, repo_data)
                else:
                    show_discovered_repo_status(msg, path)
            else:
                print("Error: Invalid directory - " + given_dir)
                sys.exit(5)
        else: # No repository path given on the command line
            path = os.getcwd()
            msg.normal("Log Level", msg.get_level_str())
            git_repo = pd.GitHubRepository(msg, path)
            repo_data = git_repo.get_info()
            if repo_data['valid']:
                if repo_data['outdated']:
                    show_repo_status(msg, repo_data, details=True)
                else:
                    if msg.get_level() > msg.LOG_MIN:
                        show_repo_status(msg, repo_data, details=True)
                    else:
                        show_repo_status(msg, repo_data, details=False)
                if opt_branches:
                    show_git_branches(msg, repo_data)
                if opt_show_branches:
                    show_git_show_branch(msg, repo_data)
                if opt_log:
                    show_git_log(msg, repo_data)
                if opt_diff:
                    show_git_diff(msg, repo_data)
            else:
                show_discovered_repo_status(msg, path)
    msg.min()

# Entry point
if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal_handler)
    config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
    msg = pd.DisplayMessages()
    main(sys.argv)

