import os
import sys
import uuid
import importlib
import subprocess
from textwrap import dedent

import pytest

from .._setup_helpers import get_compiler, get_extensions
from . import cleanup_import, run_setup

extension_helpers_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))  # noqa


def teardown_module(module):
    # Remove file generated by test_generate_openmp_enabled_py but
    # somehow needed in test_cython_autoextensions
    tmpfile = 'openmp_enabled.py'
    if os.path.exists(tmpfile):
        os.remove(tmpfile)


POSSIBLE_COMPILERS = ['unix', 'msvc', 'bcpp', 'cygwin', 'mingw32']


def test_get_compiler():
    assert get_compiler() in POSSIBLE_COMPILERS


def _extension_test_package(tmpdir, request, extension_type='c',
                            include_numpy=False):
    """Creates a simple test package with an extension module."""

    test_pkg = tmpdir.mkdir('test_pkg')
    test_pkg.mkdir('helpers_test_package').ensure('__init__.py')

    # TODO: It might be later worth making this particular test package into a
    # reusable fixture for other build_ext tests

    if extension_type in ('c', 'both'):
        # A minimal C extension for testing
        test_pkg.join('helpers_test_package', 'unit01.c').write(dedent("""\
            #include <Python.h>

            static struct PyModuleDef moduledef = {
                PyModuleDef_HEAD_INIT,
                "unit01",
                NULL,
                -1,
                NULL
            };
            PyMODINIT_FUNC
            PyInit_unit01(void) {
                return PyModule_Create(&moduledef);
            }
        """))

    if extension_type in ('pyx', 'both'):
        # A minimal Cython extension for testing
        test_pkg.join('helpers_test_package', 'unit02.pyx').write(dedent("""\
            print("Hello cruel angel.")
        """))

    if extension_type == 'c':
        extensions = ['unit01.c']
    elif extension_type == 'pyx':
        extensions = ['unit02.pyx']
    elif extension_type == 'both':
        extensions = ['unit01.c', 'unit02.pyx']

    include_dirs = ['numpy'] if include_numpy else []

    extensions_list = [
        "Extension('helpers_test_package.{0}', "
        "[join('helpers_test_package', '{1}')], "
        "include_dirs={2})".format(
            os.path.splitext(extension)[0], extension, include_dirs)
        for extension in extensions]

    test_pkg.join('helpers_test_package', 'setup_package.py').write(dedent("""\
        from setuptools import Extension
        from os.path import join
        def get_extensions():
            return [{0}]
    """.format(', '.join(extensions_list))))

    test_pkg.join('setup.py').write(dedent("""\
        import sys
        from os.path import join
        from setuptools import setup, find_packages
        sys.path.insert(0, r'{extension_helpers_path}')
        from extension_helpers import get_extensions

        setup(
            name='helpers_test_package',
            version='0.1',
            packages=find_packages(),
            ext_modules=get_extensions()
        )
    """.format(extension_helpers_path=extension_helpers_PATH)))

    if '' in sys.path:
        sys.path.remove('')

    sys.path.insert(0, '')

    def finalize():
        cleanup_import('helpers_test_package')

    request.addfinalizer(finalize)

    return test_pkg


@pytest.fixture
def extension_test_package(tmpdir, request):
    return _extension_test_package(tmpdir, request, extension_type='both')


@pytest.fixture
def c_extension_test_package(tmpdir, request):
    # Check whether numpy is installed in the test environment
    has_numpy = bool(importlib.util.find_spec('numpy'))
    return _extension_test_package(tmpdir, request, extension_type='c',
                                   include_numpy=has_numpy)


@pytest.fixture
def pyx_extension_test_package(tmpdir, request):
    return _extension_test_package(tmpdir, request, extension_type='pyx')


def test_cython_autoextensions(tmpdir):
    """
    Regression test for https://github.com/astropy/astropy-helpers/pull/19

    Ensures that Cython extensions in sub-packages are discovered and built
    only once.
    """

    # Make a simple test package
    test_pkg = tmpdir.mkdir('test_pkg')
    test_pkg.mkdir('yoda').mkdir('luke')
    test_pkg.ensure('yoda', '__init__.py')
    test_pkg.ensure('yoda', 'luke', '__init__.py')
    test_pkg.join('yoda', 'luke', 'dagobah.pyx').write(
        """def testfunc(): pass""")

    # Required, currently, for get_extensions to work
    ext_modules = get_extensions(str(test_pkg))

    assert len(ext_modules) == 2
    assert ext_modules[0].name == 'yoda.luke.dagobah'


def test_compiler_module(capsys, c_extension_test_package):
    """
    Test ensuring that the compiler module is built and installed for packages
    that have extension modules.
    """

    test_pkg = c_extension_test_package
    install_temp = test_pkg.mkdir('install_temp')

    with test_pkg.as_cwd():
        # This is one of the simplest ways to install just a package into a
        # test directory
        run_setup('setup.py',
                  ['install',
                   '--single-version-externally-managed',
                   '--install-lib={0}'.format(install_temp),
                   '--record={0}'.format(install_temp.join('record.txt'))])

    with install_temp.as_cwd():
        import helpers_test_package

        # Make sure we imported the helpers_test_package package from the correct place
        dirname = os.path.abspath(os.path.dirname(helpers_test_package.__file__))
        assert dirname == str(install_temp.join('helpers_test_package'))

        import helpers_test_package.compiler_version
        assert helpers_test_package.compiler_version != 'unknown'


@pytest.mark.parametrize('use_extension_helpers', [None, False, True])
def test_no_setup_py(tmpdir, use_extension_helpers):
    """
    Test that makes sure that extension-helpers can be enabled without a
    setup.py file.
    """

    package_name = 'helpers_test_package_' + str(uuid.uuid4()).replace('-', '_')

    test_pkg = tmpdir.mkdir('test_pkg')
    test_pkg.mkdir(package_name).ensure('__init__.py')

    simple_c = test_pkg.join(package_name, 'simple.c')

    simple_c.write(dedent("""\
        #include <Python.h>

        static struct PyModuleDef moduledef = {
            PyModuleDef_HEAD_INIT,
            "simple",
            NULL,
            -1,
            NULL
        };
        PyMODINIT_FUNC
        PyInit_simple(void) {
            return PyModule_Create(&moduledef);
        }
    """))

    test_pkg.join(package_name, 'setup_package.py').write(dedent(f"""\
        from setuptools import Extension
        from os.path import join
        def get_extensions():
            return [Extension('{package_name}.simple', [join('{package_name}', 'simple.c')])]
        """))

    if use_extension_helpers is None:
        test_pkg.join('setup.cfg').write(dedent(f"""\
            [metadata]
            name = {package_name}
            version = 0.1

            [options]
            packages = find:
        """))
    else:
        test_pkg.join('setup.cfg').write(dedent(f"""\
            [metadata]
            name = {package_name}
            version = 0.1

            [options]
            packages = find:

            [extension-helpers]
            use_extension_helpers = {str(use_extension_helpers).lower()}
        """))

    test_pkg.join('pyproject.toml').write(dedent("""\
        [build-system]
        requires = ["setuptools>=43.0.0",
                    "wheel"]
        build-backend = 'setuptools.build_meta'
    """))

    install_temp = test_pkg.mkdir('install_temp')

    with test_pkg.as_cwd():
        # NOTE: we disable build isolation as we need to pick up the current
        # developer version of extension-helpers
        subprocess.call([sys.executable, '-m', 'pip', 'install', '.',
                         '--no-build-isolation',
                         f'--target={install_temp}'])

    if '' in sys.path:
        sys.path.remove('')

    sys.path.insert(0, '')

    with install_temp.as_cwd():

        importlib.import_module(package_name)

        if use_extension_helpers:
            compiler_version_mod = importlib.import_module(package_name + '.compiler_version')
            assert compiler_version_mod.compiler != 'unknown'
        else:
            try:
                importlib.import_module(package_name + '.compiler_version')
            except ImportError:
                pass
            else:
                raise AssertionError(package_name + '.compiler_version should not exist')
