#!python
import sys
import os
import shutil
import logging
import argparse
from fleximod import utils
from fleximod.gitinterface import GitInterface
from fleximod.gitmodules import GitModules
from fleximod.version import __version__
from configparser import NoOptionError
# logger variable is global
logger = None

def commandline_arguments(args=None):
    description = """
    %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models
    """
    parser = argparse.ArgumentParser(
        description=description, formatter_class=argparse.RawDescriptionHelpFormatter
    )

    #
    # user options
    #
    choices = ["update", "install", "status"]
    parser.add_argument(
        "action",
        choices=choices,
        default="install",
        help=f"Subcommand of fleximod, choices are {choices}",
    )

    parser.add_argument(
        "components",
        nargs="*",
        help="Specific component(s) to checkout. By default, "
        "all required submodules are checked out.",
    )

    parser.add_argument(
        "-C",
        "--path",
        default=os.getcwd(),
        help="Toplevel repository directory.  Defaults to current directory.",
    )

    parser.add_argument(
        "-g",
        "--gitmodules",
        nargs="?",
        default=".gitmodules",
        help="The submodule description filename. " "Default: %(default)s.",
    )

    parser.add_argument(
        "-x",
        "--exclude",
        nargs="*",
        help="Component(s) listed in the gitmodules file which should be ignored.",
    )

    parser.add_argument(
        "-o",
        "--optional",
        action="store_true",
        default=False,
        help="By default only the required submodules "
        "are checked out. This flag will also checkout the "
        "optional submodules relative to the toplevel directory.",
    )

    parser.add_argument(
        "-v",
        "--verbose",
        action="count",
        default=0,
        help="Output additional information to "
        "the screen and log file. This flag can be "
        "used up to two times, increasing the "
        "verbosity level each time.",
    )

    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version=f"%(prog)s {__version__}",
        help="Print version and exit.",
    )

    #
    # developer options
    #
    parser.add_argument(
        "--backtrace",
        action="store_true",
        help="DEVELOPER: show exception backtraces as extra " "debugging output",
    )

    parser.add_argument(
        "-d",
        "--debug",
        action="store_true",
        default=False,
        help="DEVELOPER: output additional debugging "
        "information to the screen and log file.",
    )

    logging_group = parser.add_mutually_exclusive_group()

    logging_group.add_argument(
        "--logging",
        dest="do_logging",
        action="store_true",
        help="DEVELOPER: enable logging.",
    )
    logging_group.add_argument(
        "--no-logging",
        dest="do_logging",
        action="store_false",
        default=False,
        help="DEVELOPER: disable logging " "(this is the default)",
    )
    if args:
        options = parser.parse_args(args)
    else:
        options = parser.parse_args()

    if options.optional:
        fxrequired = ["T:T", "T:F", "I:T"]
    else:
        fxrequired = ["T:T", "I:T"]

    action = options.action
    if not action:
        action = "install"

    if options.debug:
        level = logging.DEBUG
    elif options.verbose:
        level = logging.INFO
    else:
        level = logging.WARNING
    # Configure the root logger
    logging.basicConfig(
        level=level,
        format="%(name)s - %(levelname)s - %(message)s",
        handlers=[logging.FileHandler("fleximod.log"), logging.StreamHandler()],
    )
    if hasattr(options, 'version'):
        exit()
    
    return (
        options.path,
        options.gitmodules,
        fxrequired,
        options.components,
        options.exclude,
        options.verbose,
        action,
    )


def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master"):
    # first create the module directory
    if not os.path.isdir(path):
        os.makedirs(path)
    # Check first if the module is already defined
    # and the sparse-checkout file exists
    git = GitInterface(root_dir, logger)

    # initialize a new git repo and set the sparse checkout flag
    sprep_repo = os.path.join(root_dir, path)
    sprepo_git = GitInterface(sprep_repo, logger)
    if os.path.exists(os.path.join(sprep_repo,".git")):
        try:
            logger.info("Submodule {} found".format(name))
            chk = sprepo_git.config_get_value("core", "sparseCheckout")
            if chk == "true":
                logger.info("Sparse submodule {} already checked out".format(name))
                return
        except NoOptionError:
            logger.debug("Sparse submodule {} not present".format(name))
        except Exception as e:
            utils.fatal_error("Unexpected error {} occured.".format(e))


    sprepo_git.config_set_value("core", "sparseCheckout", "true")

    # set the repository remote
    sprepo_git.git_operation("remote", "add", "origin", url)

    superroot = git.git_operation("rev-parse", "--show-superproject-working-tree")
    if os.path.isfile(os.path.join(root_dir, ".git")):
        with open(os.path.join(root_dir, ".git")) as f:
            gitpath = os.path.abspath(os.path.join(root_dir,f.read().split()[1]))
        topgit = os.path.abspath(os.path.join(gitpath, "modules"))
    else:                      
        topgit = os.path.abspath(os.path.join(root_dir, ".git", "modules"))

    if not os.path.isdir(topgit):
        os.makedirs(topgit)
    topgit = os.path.join(topgit, name)
    logger.debug(f"root_dir is {root_dir} topgit is {topgit} superroot is {superroot}")

    if os.path.isdir(os.path.join(root_dir,path,".git")):
        shutil.move(os.path.join(root_dir,path, ".git"), topgit)
        with open(os.path.join(root_dir,path, ".git"), "w") as f:
            f.write("gitdir: " + os.path.relpath(topgit, os.path.join(root_dir,path)))
            
    gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout"))
    if os.path.isfile(gitsparse):
        logger.warning("submodule {} is already initialized".format(name))
        return


    shutil.copy(os.path.join(root_dir,path, sparsefile), gitsparse)

    # Finally checkout the repo
    sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags")
    sprepo_git.git_operation("checkout", tag)
    print(f"Successfully checked out {name}")


def submodule_checkout(root, name, path, url=None, tag=None):
    git = GitInterface(root, logger)
    repodir = os.path.join(root, path)
    if os.path.exists(os.path.join(repodir, ".git")):
        logger.info("Submodule {} already checked out".format(name))
        return
    # if url is provided update to the new url
    tmpurl = None

    # Look for a .gitmodules file in the newly checkedout repo
    if url:
        # ssh urls cause problems for those who dont have git accounts with ssh keys defined
        # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was
        # opened with a GitModules object we don't need to worry about restoring the file here
        # it will be done by the GitModules class
        if url.startswith("git@"):
            tmpurl = url
            url = url.replace("git@github.com:", "https://github.com")            
            git.git_operation("clone", "-b", tag, url, path)
            # Now need to move the .git dir to the submodule location
            
            
    if not tmpurl:
        logger.debug(git.git_operation("submodule", "update", "--init", "--", path))

    if os.path.exists(os.path.join(repodir, ".gitmodules")):
        # recursively handle this checkout
        print(f"Recursively checking out submodules of {name} {repodir} {url}")
        gitmodules = GitModules(logger,confpath=repodir)
        submodules_install(gitmodules, repodir, ["I:T"])
    if os.path.exists(os.path.join(repodir, ".git")):
        print(f"Successfully checked out {name}")
    else:
        utils.fatal_error(f"Failed to checkout {name}")

    if tmpurl:
        print(git.git_operation("restore", ".gitmodules"))

    return


def submodules_status(gitmodules, root_dir):
    for name in gitmodules.sections():
        path = gitmodules.get(name, "path")
        tag = gitmodules.get(name, "fxtag")
        if not path:
            utils.fatal_error("No path found in .gitmodules for {}".format(name))
        newpath = os.path.join(root_dir, path)
        if not os.path.exists(os.path.join(newpath, ".git")):
            print(f"Submodule {name} not checked out")
        else:
            with utils.pushd(newpath):
                git = GitInterface(newpath, logger)
                atag = git.git_operation("describe", "--tags", "--always").rstrip()
                if tag and atag != tag:
                    print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}")
                elif tag:
                    print(f"Submodule {name} at tag {tag}")
                else:
                    print(
                        f"Submodule {name} has no tag defined in .gitmodules, module at {atag}"
                    )
                status = git.git_operation("status","--ignore-submodules","untracked")
                if "nothing to commit" not in status:
                    print(status)


def submodules_update(gitmodules, root_dir):
    for name in gitmodules.sections():
        fxtag = gitmodules.get(name, "fxtag")
        path = gitmodules.get(name, "path")
        url = gitmodules.get(name, "url")
        logger.info(f"name={name} path={path} url={url} fxtag={fxtag}")
        if os.path.exists(os.path.join(path, ".git")):
            submoddir = os.path.join(root_dir, path)
            with utils.pushd(submoddir):
                git = GitInterface(submoddir, logger)
                # first make sure the url is correct
                upstream = git.git_operation("ls-remote", "--get-url").rstrip()
                newremote = "origin"
                if upstream != url:
                    # TODO - this needs to be a unique name
                    remotes = git.git_operation("remote", "-v")
                    if url in remotes:
                        for line in remotes:
                            if url in line and "fetch" in line:
                                newremote = line.split()[0]
                                break
                    else:
                        i = 0
                        while newremote in remotes:
                            i = i + 1
                            newremote = f"newremote.{i:02d}"
                        git.git_operation("remote", "add", newremote, url)

                tags = git.git_operation("tag", "-l")
                if fxtag and fxtag not in tags:
                    git.git_operation("fetch", newremote, "--tags")
                atag = git.git_operation("describe", "--tags", "--always").rstrip()
                if fxtag and fxtag != atag:
                    print(f"Updating {name} to {fxtag}")
                    git.git_operation("checkout", fxtag)
                elif not fxtag:
                    print(f"No fxtag found for submodule {name}")
                else:
                    print(f"submodule {name} up to date.")


def submodules_install(gitmodules, root_dir, requiredlist):
    for name in gitmodules.sections():
        fxrequired = gitmodules.get(name, "fxrequired")
        fxsparse = gitmodules.get(name, "fxsparse")
        fxtag = gitmodules.get(name, "fxtag")
        path = gitmodules.get(name, "path")
        url = gitmodules.get(name, "url")

        if fxrequired and fxrequired not in requiredlist:
            if "T:F" == fxrequired:
                print("Skipping optional component {}".format(name))
            continue
            
        if fxsparse:
            logger.debug(
                f"Callng submodule_sparse_checkout({root_dir}, {name}, {url}, {path}, {fxsparse}, {fxtag}"
            )
            submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag)
        else:
            logger.debug(
                "Calling submodule_checkout({},{},{})".format(root_dir, name, path)
            )
            
            submodule_checkout(root_dir, name, path, url=url, tag=fxtag)


def _main_func():
    (
        root_dir,
        file_name,
        fxrequired,
        includelist,
        excludelist,
        verbose,
        action,
    ) = commandline_arguments()
    # Get a logger for the package
    global logger
    logger = logging.getLogger(__name__)
        
    logger.info(f"action is {action}")

    if not os.path.isfile(os.path.join(root_dir, file_name)):
        file_path = utils.find_upwards(root_dir, file_name)

        if file_path is None:
            utils.fatal_error(
                "No {} found in {} or any of it's parents".format(file_name, root_dir)
            )
        root_dir = os.path.dirname(file_path)
    logger.info(f"root_dir is {root_dir}")
    gitmodules = GitModules(
        logger,
        confpath=root_dir,
        conffile=file_name,
        includelist=includelist,
        excludelist=excludelist,
    )

    if action == "update":
        submodules_update(gitmodules, root_dir)
    elif action == "install":
        submodules_install(gitmodules, root_dir, fxrequired)
    elif action == "status":
        submodules_status(gitmodules, root_dir)
    else:
        utils.fatal_error(f"unrecognized action request {action}")


if __name__ == "__main__":
    _main_func()
