# (C) Datadog, Inc. 2019-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
from __future__ import absolute_import

import os

import tox
import tox.config

STYLE_CHECK_ENV_NAME = 'style'
STYLE_FORMATTER_ENV_NAME = 'format_style'
STYLE_FLAG = 'dd_check_style'
TYPES_FLAG = 'dd_check_types'
MYPY_ARGS_OPTION = 'dd_mypy_args'
E2E_READY_CONDITION = 'e2e ready if'
FIX_DEFAULT_ENVDIR_FLAG = 'ensure_default_envdir'

# Style deps:
# We pin deps in order to make CI more stable/reliable.
ISORT_DEP = 'isort[pyproject]==5.5.1'
BLACK_DEP = 'black==20.8b1'
FLAKE8_DEP = 'flake8==3.8.3'
FLAKE8_BUGBEAR_DEP = 'flake8-bugbear==20.1.4'
FLAKE8_LOGGING_FORMAT_DEP = 'flake8-logging-format==0.6.0'
MYPY_DEP = 'mypy==0.770'


@tox.hookimpl
def tox_configure(config):
    """
    For more info, see: https://tox.readthedocs.io/en/latest/plugins.html
    For an example, see: https://github.com/tox-dev/tox-travis
    """
    sections = config._cfg.sections
    base_testenv = sections.get('testenv', {})

    # Default to false so:
    # 1. we don't affect other projects using tox
    # 2. check migrations can happen gradually
    if str(base_testenv.get(STYLE_FLAG, 'false')).lower() == 'true':
        # Disable flake8 since we already include that
        config.envlist[:] = [env for env in config.envlist if not env.endswith('flake8')]

        make_envconfig = get_make_envconfig()
        reader = get_reader(config)

        add_style_checker(config, sections, make_envconfig, reader)
        add_style_formatter(config, sections, make_envconfig, reader)

    # Workaround for https://github.com/tox-dev/tox/issues/1593
    #
    # Do this only after all dynamic environments have been created
    if str(base_testenv.get(FIX_DEFAULT_ENVDIR_FLAG, 'false')).lower() == 'true':
        for env_name, env_config in config.envconfigs.items():
            if env_config.envdir == config.toxinidir:
                env_config.envdir = config.toxworkdir / env_name
                env_config.envlogdir = env_config.envdir / 'log'
                env_config.envtmpdir = env_config.envdir / 'tmp'

    # Conditionally set 'e2e ready' depending on env variables
    description = base_testenv.get('description')
    if description and E2E_READY_CONDITION in description:
        data, var = description.split(' if ')
        if var in os.environ:
            description = data
        else:
            description = '{} is missing'.format(var)
        for cfg in config.envconfigs.values():
            if E2E_READY_CONDITION in cfg.description:
                cfg.description = description


def add_style_checker(config, sections, make_envconfig, reader):
    # testenv:style
    section = '{}{}'.format(tox.config.testenvprefix, STYLE_CHECK_ENV_NAME)

    dependencies = [
        FLAKE8_DEP,
        FLAKE8_BUGBEAR_DEP,
        FLAKE8_LOGGING_FORMAT_DEP,
        BLACK_DEP,
        ISORT_DEP,
    ]

    commands = [
        'flake8 --config=../.flake8 .',
        'black --check --diff .',
        'isort --check-only --diff .',
    ]

    if sections['testenv'].get(TYPES_FLAG, 'false').lower() == 'true':
        # For command line options accepted by mypy, see: https://mypy.readthedocs.io/en/stable/command_line.html
        # Each integration should explicitly specify its options and which files it'd like to type check, which is
        # why we're defaulting to 'no arguments' by default.
        mypy_args = sections['testenv'].get(MYPY_ARGS_OPTION, '')

        # Allow using multiple lines for enhanced readability in case of large amount of options/files to check.
        mypy_args = mypy_args.replace('\n', ' ')

        dependencies.append(MYPY_DEP)
        commands.append('mypy --config-file=../mypy.ini {}'.format(mypy_args))

    sections[section] = {
        'platform': 'linux|darwin|win32',
        # Tools used here require Python 3.6+
        # more info: https://github.com/ambv/black/issues/439#issuecomment-411429907
        'basepython': 'python3',
        'skip_install': 'true',
        'deps': '\n'.join(dependencies),
        'commands': '\n'.join(commands),
    }

    # Always add the environment configurations
    config.envconfigs[STYLE_CHECK_ENV_NAME] = make_envconfig(
        config, STYLE_CHECK_ENV_NAME, section, reader._subs, config
    )

    # Intentionally add to envlist when seeing what is available
    if config.option.env is None or config.option.env == STYLE_CHECK_ENV_NAME:
        config.envlist_default.append(STYLE_CHECK_ENV_NAME)


def add_style_formatter(config, sections, make_envconfig, reader):
    # testenv:format_style
    section = '{}{}'.format(tox.config.testenvprefix, STYLE_FORMATTER_ENV_NAME)
    dependencies = [
        FLAKE8_DEP,
        BLACK_DEP,
        ISORT_DEP,
    ]
    sections[section] = {
        'platform': 'linux|darwin|win32',
        # These tools require Python 3.6+
        # more info: https://github.com/ambv/black/issues/439#issuecomment-411429907
        'basepython': 'python3',
        'skip_install': 'true',
        'deps': '\n'.join(dependencies),
        # Run formatter AFTER sorting imports
        'commands': '\n'.join(
            [
                'isort .',
                'black .',
                'python -c "print(\'\\n[NOTE] flake8 may still report style errors for things black cannot fix, '
                'these will need to be fixed manually.\')"',
                'flake8 --config=../.flake8 .',
            ]
        ),
    }

    # Always add the environment configurations
    config.envconfigs[STYLE_FORMATTER_ENV_NAME] = make_envconfig(
        config, STYLE_FORMATTER_ENV_NAME, section, reader._subs, config
    )

    # Intentionally add to envlist when seeing what is available
    if config.option.env is None or config.option.env == STYLE_FORMATTER_ENV_NAME:
        config.envlist_default.append(STYLE_FORMATTER_ENV_NAME)


def get_make_envconfig():
    make_envconfig = tox.config.ParseIni.make_envconfig

    # Make this a non-bound method for Python 2 compatibility
    make_envconfig = getattr(make_envconfig, '__func__', make_envconfig)

    return make_envconfig


def get_reader(config):
    # This is just boilerplate necessary to create a valid reader
    reader = tox.config.SectionReader('tox', config._cfg)
    reader.addsubstitutions(toxinidir=config.toxinidir, homedir=config.homedir)
    reader.addsubstitutions(toxworkdir=config.toxworkdir)
    config.distdir = reader.getpath('distdir', '{toxworkdir}/dist')
    reader.addsubstitutions(distdir=config.distdir)
    config.distshare = reader.getpath('distshare', '{homedir}/.tox/distshare')
    reader.addsubstitutions(distshare=config.distshare)

    return reader
