# -*- coding: utf-8 -*-
"""
    tests.ext
    ~~~~~~~~~~~~~~~~~~~

    Tests the extension import thing.

    :copyright: (c) 2015 by Armin Ronacher.
    :license: BSD, see LICENSE for more details.
"""

import sys
import pytest

try:
    from imp import reload as reload_module
except ImportError:
    reload_module = reload

from flask._compat import PY2


@pytest.fixture(autouse=True)
def disable_extwarnings(request, recwarn):
    from flask.exthook import ExtDeprecationWarning

    def inner():
        assert set(w.category for w in recwarn.list) \
            <= set([ExtDeprecationWarning])
        recwarn.clear()

    request.addfinalizer(inner)


@pytest.fixture(autouse=True)
def importhook_setup(monkeypatch, request):
    # we clear this out for various reasons.  The most important one is
    # that a real flaskext could be in there which would disable our
    # fake package.  Secondly we want to make sure that the flaskext
    # import hook does not break on reloading.
    for entry, value in list(sys.modules.items()):
        if (
            entry.startswith('flask.ext.') or
            entry.startswith('flask_') or
            entry.startswith('flaskext.') or
            entry == 'flaskext'
        ) and value is not None:
            monkeypatch.delitem(sys.modules, entry)
    from flask import ext
    reload_module(ext)

    # reloading must not add more hooks
    import_hooks = 0
    for item in sys.meta_path:
        cls = type(item)
        if cls.__module__ == 'flask.exthook' and \
           cls.__name__ == 'ExtensionImporter':
            import_hooks += 1
    assert import_hooks == 1

    def teardown():
        from flask import ext
        for key in ext.__dict__:
            assert '.' not in key

    request.addfinalizer(teardown)


@pytest.fixture
def newext_simple(modules_tmpdir):
    x = modules_tmpdir.join('flask_newext_simple.py')
    x.write('ext_id = "newext_simple"')


@pytest.fixture
def oldext_simple(modules_tmpdir):
    flaskext = modules_tmpdir.mkdir('flaskext')
    flaskext.join('__init__.py').write('\n')
    flaskext.join('oldext_simple.py').write('ext_id = "oldext_simple"')


@pytest.fixture
def newext_package(modules_tmpdir):
    pkg = modules_tmpdir.mkdir('flask_newext_package')
    pkg.join('__init__.py').write('ext_id = "newext_package"')
    pkg.join('submodule.py').write('def test_function():\n    return 42\n')


@pytest.fixture
def oldext_package(modules_tmpdir):
    flaskext = modules_tmpdir.mkdir('flaskext')
    flaskext.join('__init__.py').write('\n')
    oldext = flaskext.mkdir('oldext_package')
    oldext.join('__init__.py').write('ext_id = "oldext_package"')
    oldext.join('submodule.py').write('def test_function():\n'
                                      '    return 42')


@pytest.fixture
def flaskext_broken(modules_tmpdir):
    ext = modules_tmpdir.mkdir('flask_broken')
    ext.join('b.py').write('\n')
    ext.join('__init__.py').write('import flask.ext.broken.b\n'
                                  'import missing_module')


def test_flaskext_new_simple_import_normal(newext_simple):
    from flask.ext.newext_simple import ext_id
    assert ext_id == 'newext_simple'


def test_flaskext_new_simple_import_module(newext_simple):
    from flask.ext import newext_simple
    assert newext_simple.ext_id == 'newext_simple'
    assert newext_simple.__name__ == 'flask_newext_simple'


def test_flaskext_new_package_import_normal(newext_package):
    from flask.ext.newext_package import ext_id
    assert ext_id == 'newext_package'


def test_flaskext_new_package_import_module(newext_package):
    from flask.ext import newext_package
    assert newext_package.ext_id == 'newext_package'
    assert newext_package.__name__ == 'flask_newext_package'


def test_flaskext_new_package_import_submodule_function(newext_package):
    from flask.ext.newext_package.submodule import test_function
    assert test_function() == 42


def test_flaskext_new_package_import_submodule(newext_package):
    from flask.ext.newext_package import submodule
    assert submodule.__name__ == 'flask_newext_package.submodule'
    assert submodule.test_function() == 42


def test_flaskext_old_simple_import_normal(oldext_simple):
    from flask.ext.oldext_simple import ext_id
    assert ext_id == 'oldext_simple'


def test_flaskext_old_simple_import_module(oldext_simple):
    from flask.ext import oldext_simple
    assert oldext_simple.ext_id == 'oldext_simple'
    assert oldext_simple.__name__ == 'flaskext.oldext_simple'


def test_flaskext_old_package_import_normal(oldext_package):
    from flask.ext.oldext_package import ext_id
    assert ext_id == 'oldext_package'


def test_flaskext_old_package_import_module(oldext_package):
    from flask.ext import oldext_package
    assert oldext_package.ext_id == 'oldext_package'
    assert oldext_package.__name__ == 'flaskext.oldext_package'


def test_flaskext_old_package_import_submodule(oldext_package):
    from flask.ext.oldext_package import submodule
    assert submodule.__name__ == 'flaskext.oldext_package.submodule'
    assert submodule.test_function() == 42


def test_flaskext_old_package_import_submodule_function(oldext_package):
    from flask.ext.oldext_package.submodule import test_function
    assert test_function() == 42


def test_flaskext_broken_package_no_module_caching(flaskext_broken):
    for x in range(2):
        with pytest.raises(ImportError):
            import flask.ext.broken


def test_no_error_swallowing(flaskext_broken):
    with pytest.raises(ImportError) as excinfo:
        import flask.ext.broken

    assert excinfo.type is ImportError
    if PY2:
        message = 'No module named missing_module'
    else:
        message = 'No module named \'missing_module\''
    assert str(excinfo.value) == message
    assert excinfo.tb.tb_frame.f_globals is globals()

    # reraise() adds a second frame so we need to skip that one too.
    # On PY3 we even have another one :(
    next = excinfo.tb.tb_next.tb_next
    if not PY2:
        next = next.tb_next

    import os.path
    assert os.path.join('flask_broken', '__init__.py') in \
        next.tb_frame.f_code.co_filename
