
"""
    Admin Shell commands available in 'normal' install mode
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    IMPORTANT:
        Every import from external packages must be made with LazyImportError!
        Otherwise the bootstrap will fail, because no external package is
        available in bootstrap process!

    :created: 03.2018 by Jens Diemer, www.jensdiemer.de
    :copyleft: 2018 by the bootstrap_env team, see AUTHORS for more details.
    :license: GNU General Public License v3 or later (GPLv3+), see LICENSE for more details.
"""

import os
import sys
from pathlib import Path

# Bootstrap-Env
from bootstrap_env.boot_bootstrap_env import Cmd2, VerboseSubprocess, __version__, get_pip_file_name, in_virtualenv
from bootstrap_env.utils.import_utils import LazyImportError
from bootstrap_env.version import __version__ as bootstrap_env_version

# External libs:
try:
    from cookiecutter.main import cookiecutter
except ImportError as err:
    # Re-Raise ImportError on first usage
    cookiecutter = LazyImportError(err)



class AdminShell(Cmd2):
    """
    Normal user commands.
    Only this commands are available in 'normal' installation mode.
    """
    version = __version__

    def __init__(self, path_helper, *args, **kwargs):
        self.path_helper = path_helper # bootstrap_env.admin_shell.path_helper.PathHelper instance

        super().__init__(*args, **kwargs)

    def get_pip3_path(self):
        if not in_virtualenv():
            self.stdout.write("\nERROR: Only allowed in activated virtualenv!\n\n")
            return

        if sys.platform == 'win32':
            bin_dir_name="Scripts"
        else:
            bin_dir_name = "bin"

        pip3_path = Path(sys.prefix, bin_dir_name, get_pip_file_name()) # e.g.: .../bin/pip3
        if not pip3_path.is_file():
            print("ERROR: pip not found here: '%s'" % pip3_path)
            return

        print("pip found here: '%s'" % pip3_path)
        return pip3_path

    def do_install_test_requirements(self, arg=None):
        """
        Install packages to run tests
        """
        pip3_path = self.get_pip3_path()
        VerboseSubprocess(
            str(pip3_path), "install", "-r", str(self.path_helper.test_req_path)
        ).verbose_call(check=True) # Exit on error

    def do_pytest(self, arg=None):
        """
        Run tests via pytest
        """
        try:
            import pytest
        except ImportError as err:
            print("ERROR: Can't import pytest: %s (pytest not installed, in normal installation!)" % err)
        else:
            root_path = str(self.path_helper.base)
            print("chdir %r" % root_path)
            os.chdir(root_path)

            args = sys.argv[2:]
            print("Call Pytest with args: %s" % repr(args))
            exit_code = pytest.main(args=args)
            sys.exit(exit_code)

    def do_pip_freeze(self, arg=None):
        """
        Just run 'pip freeze'
        """
        return_code = VerboseSubprocess("pip3", "freeze").verbose_call(check=False)

    def do_update_env(self, arg=None):
        """
        Update all packages in virtualenv.

        (Call this command only in a activated virtualenv.)
        """
        pip3_path = str(self.get_pip3_path())

        # Upgrade pip first:
        if sys.platform == 'win32':
            # Note: On windows it will crash with a PermissionError: [WinError 32]
            # because pip can't replace himself while running ;)
            # Work-a-round is "python -m pip install --upgrade pip"
            # see also: https://github.com/pypa/pip/issues/3804
            return_code = VerboseSubprocess(
                sys.executable or "python",
                "-m", "pip", "install", "--upgrade", "pip",
            ).verbose_call(check=False)
        else:
            return_code = VerboseSubprocess(
                pip3_path, "install", "--upgrade", "pip"
            ).verbose_call(check=False)

        root_path = self.path_helper.base.parent

        # Update the requirements files by...
        if self.path_helper.normal_mode:
            # ... update 'bootstrap_env' PyPi package
            return_code = VerboseSubprocess(
                pip3_path, "install", "--upgrade", self.path_helper.egg_name
            ).verbose_call(check=False)
        else:
            # ... git pull bootstrap_env sources
            return_code = VerboseSubprocess(
                "git", "pull", "origin",
                cwd=str(root_path)
            ).verbose_call(check=False)

            return_code = VerboseSubprocess(
                pip3_path, "install", "--editable", ".",
                cwd=str(root_path)
            ).verbose_call(check=False)

        requirement_file_path = str(self.path_helper.req_filepath)

        # Update with requirements files:
        self.stdout.write("Use: '%s'\n" % requirement_file_path)
        return_code = VerboseSubprocess(
            pip3_path, "install",
            "--exists-action", "b", # action when a path already exists: (b)ackup
            "--upgrade",
            "--requirement", requirement_file_path,
            timeout=120  # extended timeout for slow Travis ;)
        ).verbose_call(check=False)

        self.stdout.write("Please restart %s\n" % self.self_filename)
        sys.exit(0)

    def do_pip_sync(self, arg=None):
        """
        run pip-sync (use with care)

        pip-sync will install/upgrade/uninstall everything necessary to match the requirements.txt contents.

        Be careful: pip-sync is meant to be used only with a requirements.txt generated by pip-compile!
        """
        if self.path_helper.egg_name == "bootstrap_env":
            print("ERROR: command not allowed for 'bootstrap_env' !\n")
            print(
                "bootstrap_env doesn't use pip-compile,"
                " because Bootstrap-env should be used as a tool in other projects"
                " and the projects himself should pin requirements ;) "
            )
            return

        # Run pip-sync only in developer mode
        return_code = VerboseSubprocess(
            "pip-sync", str(self.path_helper.req_filepath),
            cwd=str(self.path_helper.base.parent)
        ).verbose_call(check=False)

        self.stdout.write("Please restart %s\n" % self.self_filename)
        sys.exit(0)

    def complete_generate_bootstrap(self, text, line, begidx, endidx):
        # print("text: %r" % text)
        # print("line: %r" % line)
        return self._complete_path(text, line, begidx, endidx)

    def confirm(self, txt, confirm_values=("y", "j")):
        if input("\n%s" % txt).lower() in confirm_values:
            return True
        return False

    def do_generate_bootstrap(self, arg=None):
        """
        Generate new bootstrap file via cookiecutter

        direct call, e.g.:

        bootstrap_env_admin.py generate_bootstrap ~/new_project
        """
        if not arg:
            print("INFO: No output path given.")

        output_dir = Path(arg).expanduser().resolve()

        if output_dir.is_dir():
            print("Create bootstrap file in: %s" % output_dir)
            print("ERROR: output path already exists!")
            return

        txt = "Create bootstrap file in: %s ? [Y/N]" % output_dir
        if not self.confirm(txt):
            print("Abort.")
            return

        repro_path = Path(self.package_path, "boot_source")
        result = cookiecutter(
            template=str(repro_path),
            output_dir=str(output_dir),
            extra_context={
                "_version": bootstrap_env_version,
            }
        )
        print("bootstrap file created here: %s" % result)
