"""
    CLI for development
"""

import logging
import shlex
import sys
from pathlib import Path

import rich_click as click
from cli_base.cli_tools import code_style
from cli_base.cli_tools.dev_tools import run_coverage, run_tox
from cli_base.cli_tools.subprocess_utils import verbose_check_call
from cli_base.cli_tools.test_utils.snapshot import UpdateTestSnapshotFiles
from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE
from cli_base.cli_tools.version_info import print_version
from cli_base.run_pip_audit import run_pip_audit
from django.core.management.commands.test import Command as DjangoTestCommand
from django_yunohost_integration.local_test import create_local_test
from manageprojects.utilities.publish import publish_package
from rich import print
from rich.console import Console
from rich.traceback import install as rich_traceback_install
from rich_click import RichGroup

import for_runners_ynh
from for_runners_ynh import constants
from for_runners_ynh.constants import PACKAGE_ROOT
from for_runners_ynh.tests import setup_ynh_tests


logger = logging.getLogger(__name__)


OPTION_ARGS_DEFAULT_TRUE = dict(is_flag=True, show_default=True, default=True)
OPTION_ARGS_DEFAULT_FALSE = dict(is_flag=True, show_default=True, default=False)
ARGUMENT_EXISTING_DIR = dict(
    type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True, path_type=Path)
)
ARGUMENT_NOT_EXISTING_DIR = dict(
    type=click.Path(
        exists=False,
        file_okay=False,
        dir_okay=True,
        readable=False,
        writable=True,
        path_type=Path,
    )
)
ARGUMENT_EXISTING_FILE = dict(
    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, path_type=Path)
)


class ClickGroup(RichGroup):  # FIXME: How to set the "info_name" easier?
    def make_context(self, info_name, *args, **kwargs):
        info_name = './dev-cli.py'
        return super().make_context(info_name, *args, **kwargs)


@click.group(
    cls=ClickGroup,
    epilog=constants.CLI_EPILOG,
)
def cli():
    pass


@cli.command()
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def mypy(verbosity: int):
    """Run Mypy (configured in pyproject.toml)"""
    verbose_check_call('mypy', '.', cwd=PACKAGE_ROOT, verbose=verbosity > 0, exit_on_error=True)


@cli.command()
def install():
    """
    Run pip-sync and install 'for_runners_ynh' via pip as editable.
    """
    verbose_check_call('pip-sync', PACKAGE_ROOT / 'requirements.dev.txt')
    verbose_check_call('pip', 'install', '--no-deps', '-e', '.')


@cli.command()
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def pip_audit(verbosity: int):
    """
    Run pip-audit check against current requirements files
    """
    run_pip_audit(base_path=PACKAGE_ROOT, verbosity=verbosity)


@cli.command()
def update():
    """
    Update "requirements*.txt" dependencies files
    """
    bin_path = Path(sys.executable).parent

    verbose_check_call(bin_path / 'pip', 'install', '-U', 'pip')
    verbose_check_call(bin_path / 'pip', 'install', '-U', 'pip-tools')

    extra_env = dict(
        CUSTOM_COMPILE_COMMAND='./dev-cli.py update',
    )

    pip_compile_base = [
        bin_path / 'pip-compile',
        '--verbose',
        '--allow-unsafe',  # https://pip-tools.readthedocs.io/en/latest/#deprecations
        '--resolver=backtracking',  # https://pip-tools.readthedocs.io/en/latest/#deprecations
        '--upgrade',
        '--generate-hashes',
    ]

    # Only "prod" dependencies:
    verbose_check_call(
        *pip_compile_base,
        'pyproject.toml',
        '--output-file',
        'conf/requirements.txt',
        extra_env=extra_env,
    )

    # dependencies + "dev"-optional-dependencies:
    verbose_check_call(
        *pip_compile_base,
        'pyproject.toml',
        '--extra=dev',
        '--output-file',
        'requirements.dev.txt',
        extra_env=extra_env,
    )

    run_pip_audit(base_path=PACKAGE_ROOT)

    # Install new dependencies in current .venv:
    verbose_check_call(bin_path / 'pip-sync', 'requirements.dev.txt')


@cli.command()
def publish():
    """
    Build and upload this project to PyPi
    """
    try:
        _run_django_test_cli(argv=sys.argv, exit_after_run=True)  # Don't publish a broken state
    except SystemExit as err:
        assert err.code == 0, f'Exit code is not 0: {err.code}'

    publish_package(
        module=for_runners_ynh,
        package_path=PACKAGE_ROOT,
        distribution_name='for_runners_ynh',
    )


@cli.command()
@click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE)
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def fix_code_style(color: bool, verbosity: int):
    """
    Fix code style of all your_cool_package source code files via darker
    """
    code_style.fix(package_root=PACKAGE_ROOT, darker_color=color, darker_verbose=verbosity > 0)


@cli.command()
@click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE)
@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE)
def check_code_style(color: bool, verbosity: int):
    """
    Check code style by calling darker + flake8
    """
    code_style.check(package_root=PACKAGE_ROOT, darker_color=color, darker_verbose=verbosity > 0)


@cli.command()
def update_test_snapshot_files():
    """
    Update all test snapshot files (by remove and recreate all snapshot files)
    """
    with UpdateTestSnapshotFiles(root_path=PACKAGE_ROOT, verbose=True):
        # Just recreate them by running tests:
        _run_django_test_cli(argv=sys.argv, exit_after_run=False)


def _run_django_test_cli(argv, exit_after_run=True):
    """
    Call the origin Django test manage command CLI and pass all args to it.
    """
    setup_ynh_tests()

    print('\nStart Django unittests with:')
    for default_arg in ('shuffle', 'buffer'):
        if default_arg not in argv and f'--no-{default_arg}' not in argv:
            argv.append(f'--{default_arg}')
    print(shlex.join(argv))
    print()

    test_command = DjangoTestCommand()
    test_command.run_from_argv(argv)
    if exit_after_run:
        sys.exit(0)


@cli.command()  # Dummy command
def test():
    """
    Compile YunoHost files and run Django unittests
    """
    _run_django_test_cli(argv=sys.argv, exit_after_run=True)


@cli.command()  # Dummy command
def coverage():
    """
    Run tests and show coverage report.
    """
    run_coverage()


@cli.command()  # Dummy "tox" command
def tox():
    """
    Run tox
    """
    run_tox()


@cli.command()
def version():
    """Print version and exit"""
    # Pseudo command, because the version always printed on every CLI call ;)
    sys.exit(0)


@cli.command()
def local_test():
    """
    Build a "local_test" YunoHost installation and start the Django dev. server against it.
    """
    create_local_test(
        django_settings_path=PACKAGE_ROOT / 'conf' / 'settings.py',
        destination=PACKAGE_ROOT / 'local_test',
        runserver=True,
        extra_replacements={
            '__DEBUG_ENABLED__': '1',
        },
    )


@cli.command()
def diffsettings():
    """
    Run "diffsettings" manage command against a "local_test" YunoHost installation.
    """
    destination = PACKAGE_ROOT / 'local_test'
    create_local_test(
        django_settings_path=PACKAGE_ROOT / 'conf' / 'settings.py',
        destination=destination,
        runserver=False,
        extra_replacements={
            '__DEBUG_ENABLED__': '1',
        },
    )
    app_path = destination / 'opt_yunohost'
    verbose_check_call(
        sys.executable,
        app_path / 'manage.py',
        'diffsettings',
        cwd=app_path,
    )


def main():
    print_version(for_runners_ynh)

    if len(sys.argv) >= 2:
        # Check if we can just pass a command call to origin CLI:
        command = sys.argv[1]
        command_map = {
            'test': _run_django_test_cli,
            'tox': run_tox,
            'coverage': run_coverage,
        }
        if real_func := command_map.get(command):
            real_func(argv=sys.argv, exit_after_run=True)

    console = Console()
    rich_traceback_install(
        width=console.size.width,  # full terminal width
        show_locals=True,
        suppress=[click],
        max_frames=2,
    )

    print('Execute Click CLI')
    cli()
