from contextlib import contextmanager
import importlib
from pathlib import Path
import tempfile
import unittest

from artiq import tools


# Helper to create temporary modules
# Very simplified version of CPython's
# Lib/test/test_importlib/util.py:create_modules
@contextmanager
def create_modules(*names, extension=".py"):
    mapping = {}
    with tempfile.TemporaryDirectory() as temp_dir:
        mapping[".root"] = Path(temp_dir)

        for name in names:
            file_path = Path(temp_dir) / f"{name}{extension}"
            with file_path.open("w") as fp:
                print(f"_MODULE_NAME = {name!r}", file=fp)
            mapping[name] = file_path

        yield mapping


MODNAME = "modname"


class TestFileImport(unittest.TestCase):
    def test_import_and_prefix_is_present(self):
        prefix = "prefix_"
        with create_modules(MODNAME) as mods:
            mod = tools.file_import(str(mods[MODNAME]), prefix=prefix)
            self.assertEqual(prefix + MODNAME, mod.__name__)

    def test_can_import_from_same_level(self):
        m1_name, m2_name = "mod1", "mod2"
        with create_modules(m1_name, m2_name) as mods:
            with mods[m2_name].open("a") as fp:
                print(f"from {m1_name} import _MODULE_NAME as _M1_NAME", file=fp)

            mod1 = tools.file_import(str(mods[m1_name]))
            mod2 = tools.file_import(str(mods[m2_name]))

            self.assertEqual(mod2._M1_NAME, mod1._MODULE_NAME)

    def test_can_import_not_in_source_suffixes(self):
        for extension in ["", ".something"]:
            self.assertNotIn(extension, importlib.machinery.SOURCE_SUFFIXES)
            with create_modules(MODNAME, extension=extension) as mods:
                mod = tools.file_import(str(mods[MODNAME]))
                self.assertEqual(Path(mod.__file__).name, f"{MODNAME}{extension}")


class TestGetExperiment(unittest.TestCase):
    def test_fail_no_experiments(self):
        with create_modules(MODNAME) as mods:
            mod = tools.file_import(str(mods[MODNAME]))
            with self.assertRaises(ValueError):
                tools.get_experiment(mod)

    def test_fail_hidden_experiment(self):
        with create_modules(MODNAME) as mods:
            with mods[MODNAME].open("a") as fp:
                print(
                    """
from artiq.experiment import *

class _Exp1(EnvExperiment):
    pass
                """,
                    file=fp,
                )

            mod = tools.file_import(str(mods[MODNAME]))
            with self.assertRaises(ValueError):
                tools.get_experiment(mod)

    def test_multiple_experiments(self):
        with create_modules(MODNAME) as mods:
            with mods[MODNAME].open("a") as fp:
                print(
                    """
from artiq.experiment import *

class Exp1(EnvExperiment):
    pass

class Exp2(EnvExperiment):
    pass
                """,
                    file=fp,
                )

            mod = tools.file_import(str(mods[MODNAME]))

            # by class name
            self.assertIs(mod.Exp1, tools.get_experiment(mod, "Exp1"))
            self.assertIs(mod.Exp2, tools.get_experiment(mod, "Exp2"))

            # by elimination should fail
            with self.assertRaises(ValueError):
                tools.get_experiment(mod)

    def test_single_experiment(self):
        with create_modules(MODNAME) as mods:
            with mods[MODNAME].open("a") as fp:
                print(
                    """
from artiq.experiment import *

class Exp1(EnvExperiment):
    pass
                """,
                    file=fp,
                )

            mod = tools.file_import(str(mods[MODNAME]))

            # by class name
            self.assertIs(mod.Exp1, tools.get_experiment(mod, "Exp1"))

            # by elimination
            self.assertIs(mod.Exp1, tools.get_experiment(mod))

    def test_nested_experiment(self):
        with create_modules(MODNAME) as mods:
            with mods[MODNAME].open("a") as fp:
                print(
                    """
from artiq.experiment import *

class Foo:
    class Exp1(EnvExperiment):
        pass
                """,
                    file=fp,
                )

            mod = tools.file_import(str(mods[MODNAME]))

            # by class name
            self.assertIs(mod.Foo.Exp1, tools.get_experiment(mod, "Foo.Exp1"))

            # by elimination should fail
            with self.assertRaises(ValueError):
                tools.get_experiment(mod)
