import importlib
import importlib.util
import inspect
import logging
import sys
import threading
from pathlib import Path
from unittest import case

from .. import tools
from .tag_selector import TagsSelector
from .suite import OdooSuite
from .result import OdooTestResult


_logger = logging.getLogger(__name__)


def get_module_test_cases(module):
    """Return a suite of all test cases contained in the given module"""
    for obj in module.__dict__.values():
        if not isinstance(obj, type):
            continue
        if not issubclass(obj, case.TestCase):
            continue
        if obj.__module__ != module.__name__:
            continue

        test_case_class = obj
        test_cases = test_case_class.__dict__.items()
        if getattr(test_case_class, 'allow_inherited_tests_method', False):
            # keep iherited method for specific classes.
            # This is likely to be removed once a better solution is found
            test_cases = inspect.getmembers(test_case_class, callable)
        else:
            # sort test case to keep the initial behaviour.
            # This is likely to be removed in the future
            test_cases = sorted(test_cases, key=lambda pair: pair[0])

        for method_name, method in test_cases:
            if not callable(method):
                continue
            if not method_name.startswith('test'):
                continue
            yield test_case_class(method_name)


def get_test_modules(module):
    """ Return a list of module for the addons potentially containing tests to
    feed get_module_test_cases() """
    results = _get_tests_modules(importlib.util.find_spec(f'odoo.addons.{module}'))
    results += list(_get_upgrade_test_modules(module))

    return results


def _get_tests_modules(mod):
    spec = importlib.util.find_spec('.tests', mod.name)
    if not spec:
        return []

    tests_mod = importlib.import_module(spec.name)
    return [
        mod_obj
        for name, mod_obj in inspect.getmembers(tests_mod, inspect.ismodule)
        if name.startswith('test_')
    ]


def _get_upgrade_test_modules(module):
    upgrade_modules = (
        f"odoo.upgrade.{module}",
        f"odoo.addons.{module}.migrations",
        f"odoo.addons.{module}.upgrades",
    )
    for module_name in upgrade_modules:
        if not importlib.util.find_spec(module_name):
            continue

        upg = importlib.import_module(module_name)
        for path in map(Path, upg.__path__):
            for test in path.glob("tests/test_*.py"):
                spec = importlib.util.spec_from_file_location(f"{upg.__name__}.tests.{test.stem}", test)
                if not spec:
                    continue
                pymod = importlib.util.module_from_spec(spec)
                sys.modules[spec.name] = pymod
                spec.loader.exec_module(pymod)
                yield pymod


def make_suite(module_names, position='at_install'):
    """ Creates a test suite for all the tests in the specified modules,
    filtered by the provided ``position`` and the current test tags

    :param list[str] module_names: modules to load tests from
    :param str position: "at_install" or "post_install"
    """
    config_tags = TagsSelector(tools.config['test_tags'])
    position_tag = TagsSelector(position)
    tests = (
        t
        for module_name in module_names
        for m in get_test_modules(module_name)
        for t in get_module_test_cases(m)
        if position_tag.check(t) and config_tags.check(t)
    )
    return OdooSuite(sorted(tests, key=lambda t: t.test_sequence))


def run_suite(suite):
    # avoid dependency hell
    from ..modules import module
    module.current_test = True
    threading.current_thread().testing = True

    results = OdooTestResult()
    suite(results)

    threading.current_thread().testing = False
    module.current_test = False
    return results