"""Execute unit tests using pytest."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import os
import sys

from ..util import (
    ANSIBLE_TEST_DATA_ROOT,
    display,
    get_available_python_versions,
    is_subdir,
    SubprocessError,
    REMOTE_ONLY_PYTHON_VERSIONS,
)

from ..util_common import (
    intercept_command,
    ResultType,
    handle_layout_messages,
)

from ..ansible_util import (
    ansible_environment,
    check_pyyaml,
)

from ..target import (
    walk_internal_targets,
    walk_units_targets,
)

from ..config import (
    UnitsConfig,
)

from ..coverage_util import (
    coverage_context,
)

from ..data import (
    data_context,
)

from ..executor import (
    AllTargetsSkipped,
    Delegate,
    get_changes_filter,
    install_command_requirements,
    SUPPORTED_PYTHON_VERSIONS,
)


def command_units(args):
    """
    :type args: UnitsConfig
    """
    handle_layout_messages(data_context().content.unit_messages)

    changes = get_changes_filter(args)
    require = args.require + changes
    include = walk_internal_targets(walk_units_targets(), args.include, args.exclude, require)

    paths = [target.path for target in include]
    remote_paths = [path for path in paths
                    if is_subdir(path, data_context().content.unit_module_path)
                    or is_subdir(path, data_context().content.unit_module_utils_path)]

    if not paths:
        raise AllTargetsSkipped()

    if args.python and args.python in REMOTE_ONLY_PYTHON_VERSIONS and not remote_paths:
        raise AllTargetsSkipped()

    if args.delegate:
        raise Delegate(require=changes, exclude=args.exclude)

    version_commands = []

    available_versions = sorted(get_available_python_versions(list(SUPPORTED_PYTHON_VERSIONS)).keys())

    for version in SUPPORTED_PYTHON_VERSIONS:
        # run all versions unless version given, in which case run only that version
        if args.python and version != args.python_version:
            continue

        if not args.python and version not in available_versions:
            display.warning("Skipping unit tests on Python %s due to missing interpreter." % version)
            continue

        if args.requirements_mode != 'skip':
            install_command_requirements(args, version)

        env = ansible_environment(args)

        cmd = [
            'pytest',
            '--boxed',
            '-r', 'a',
            '-n', str(args.num_workers) if args.num_workers else 'auto',
            '--color',
            'yes' if args.color else 'no',
            '-p', 'no:cacheprovider',
            '-c', os.path.join(ANSIBLE_TEST_DATA_ROOT, 'pytest.ini'),
            '--junit-xml', os.path.join(ResultType.JUNIT.path, 'python%s-units.xml' % version),
        ]

        if not data_context().content.collection:
            cmd.append('--durations=25')

        if version != '2.6':
            # added in pytest 4.5.0, which requires python 2.7+
            cmd.append('--strict-markers')

        plugins = []

        if args.coverage:
            plugins.append('ansible_pytest_coverage')

        if data_context().content.collection:
            plugins.append('ansible_pytest_collections')

        if plugins:
            env['PYTHONPATH'] += ':%s' % os.path.join(ANSIBLE_TEST_DATA_ROOT, 'pytest/plugins')
            env['PYTEST_PLUGINS'] = ','.join(plugins)

        if args.collect_only:
            cmd.append('--collect-only')

        if args.verbosity:
            cmd.append('-' + ('v' * args.verbosity))

        if version in REMOTE_ONLY_PYTHON_VERSIONS:
            test_paths = remote_paths
        else:
            test_paths = paths

        if not test_paths:
            continue

        cmd.extend(test_paths)

        version_commands.append((version, cmd, env))

    if args.requirements_mode == 'only':
        sys.exit()

    for version, command, env in version_commands:
        check_pyyaml(args, version)

        display.info('Unit test with Python %s' % version)

        try:
            with coverage_context(args):
                intercept_command(args, command, target_name='units', env=env, python_version=version)
        except SubprocessError as ex:
            # pytest exits with status code 5 when all tests are skipped, which isn't an error for our use case
            if ex.status != 5:
                raise
