from pathlib import Path
import tokenize

import IPython.utils.io
import IPython.testing.globalipapp
import ipython_autoimport
import pytest


@pytest.fixture(scope="module")
def global_ip():
    ip = IPython.testing.globalipapp.start_ipython()
    path = Path(__file__)
    ip.run_cell("import sys; sys.path[:0] = [{!r}, {!r}]".format(
        str(path.parents[1]), str(path.parents[0])))
    return ip


@pytest.fixture
def ip(global_ip):
    global_ip.run_cell("%reset -f")
    global_ip.run_cell("%load_ext ipython_autoimport")
    yield global_ip
    with IPython.utils.io.capture_output():
        global_ip.run_cell(
            "for name, mod in list(sys.modules.items()):\n"
            "    if getattr(mod, '__file__', '').startswith({!r}):\n"
            "        del sys.modules[name]"
            .format(str(Path(__file__).parent)))
    global_ip.run_cell("%unload_ext ipython_autoimport")


@pytest.mark.parametrize("name", ["a", "a.b", "a.b.c"])
def test_autoimport(ip, name):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("{}.__name__".format(name))
    parts = name.split(".")
    assert (captured.stdout
            == "{}Out[1]: {!r}\n".format(
                "".join("Autoimport: import {}\n".format(
                    ".".join(parts[:i + 1])) for i in range(len(parts))),
                name))


def test_sub_submodule(ip):
    ip.run_cell("import a.b")
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("a.b.c.__name__")
    assert captured.stdout == "Autoimport: import a.b.c\nOut[1]: 'a.b.c'\n"


def test_no_import(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("a.not_here")
    # Exact message changes between Python versions.
    assert "has no attribute 'not_here'" in captured.stdout.splitlines()[-1]
    assert "ImportError" not in captured.stdout


def test_setattr(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("a; a.b = 42; 'b' in vars(a), a.b")
    assert captured.stdout == "Autoimport: import a\nOut[1]: (True, 42)\n"


def test_closure(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("x = 1; (lambda: x)()")
    assert captured.stdout == "Out[1]: 1\n"


def test_del(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("x = 1; del x; print('ok')")
    assert captured.stdout == "ok\n"


def test_list(ip):
    ip.run_cell("os")
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("%autoimport -l")
    assert (captured.stdout ==
            "Autoimport: the following autoimports were run:\nimport os\n")


def test_no_list(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("%autoimport -l")
    assert (captured.stdout ==
            "Autoimport: no autoimports in this session yet.\n")


def test_noclear(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("%autoimport -c ipython_autoimport_test_noclear")
    assert (
        captured.stdout ==
        "Autoimport: didn't find symbol "
        "'ipython_autoimport_test_noclear' in autoimport cache.\n"
    )


@pytest.mark.parametrize("magic", ["time", "timeit -n 1 -r 1", "prun"])
def test_magics(ip, magic):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("{} x = 1".format(magic))
    assert "error" not in captured.stdout.lower()


def test_no_autoimport_in_time(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("%time type(get_ipython().user_ns)")
    assert "autoimport" not in captured.stdout.lower()


def test_unload(ip):
    with IPython.utils.io.capture_output() as captured:
        ip.run_cell("%unload_ext ipython_autoimport")
        ip.run_cell("try: a\nexcept NameError: print('ok')")
    assert captured.stdout == "ok\n"


class TestStyle:
    def _iter_stripped_lines(self):
        for path in [ipython_autoimport.__file__, __file__]:
            with tokenize.open(path) as src:
                for i, line in enumerate(src, 1):
                    yield "{}:{}".format(path, i), line.rstrip("\n")

    def test_line_length(self):
        for name, line in self._iter_stripped_lines():
            assert len(line) <= 79, f"{name} is too long"

    def test_trailing_whitespace(self):
        for name, line in self._iter_stripped_lines():
            assert not (line and line[-1].isspace()), \
                f"{name} has trailing whitespace"
