import sys

import astroid
from autoapi.mappers.python import astroid_utils, objects
import pytest


def generate_module_names():
    for i in range(1, 5):
        yield ".".join("module{}".format(j) for j in range(i))

    yield "package.repeat.repeat"


def imported_basename_cases():
    for module_name in generate_module_names():
        import_ = "import {}".format(module_name)
        basename = "{}.ImportedClass".format(module_name)
        expected = basename

        yield (import_, basename, expected)

        import_ = "import {} as aliased".format(module_name)
        basename = "aliased.ImportedClass"

        yield (import_, basename, expected)

        if "." in module_name:
            from_name, attribute = module_name.rsplit(".", 1)
            import_ = "from {} import {}".format(from_name, attribute)
            basename = "{}.ImportedClass".format(attribute)
            yield (import_, basename, expected)

            import_ += " as aliased"
            basename = "aliased.ImportedClass"
            yield (import_, basename, expected)

        import_ = "from {} import ImportedClass".format(module_name)
        basename = "ImportedClass"
        yield (import_, basename, expected)

        import_ = "from {} import ImportedClass as AliasedClass".format(module_name)
        basename = "AliasedClass"
        yield (import_, basename, expected)


def generate_args():
    for i in range(5):
        yield ", ".join("arg{}".format(j) for j in range(i))


def imported_call_cases():
    for args in generate_args():
        for import_, basename, expected in imported_basename_cases():
            basename += "({})".format(args)
            expected += "()"
            yield import_, basename, expected


class TestAstroidUtils:
    @pytest.mark.parametrize(
        ("import_", "basename", "expected"), list(imported_basename_cases())
    )
    def test_can_get_full_imported_basename(self, import_, basename, expected):
        source = """
        {}
        class ThisClass({}): #@
            pass
        """.format(
            import_, basename
        )
        node = astroid.extract_node(source)
        basenames = astroid_utils.resolve_qualname(node.bases[0], node.basenames[0])
        assert basenames == expected

    @pytest.mark.parametrize(
        ("import_", "basename", "expected"), list(imported_call_cases())
    )
    def test_can_get_full_function_basename(self, import_, basename, expected):
        source = """
        {}
        class ThisClass({}): #@
            pass
        """.format(
            import_, basename
        )
        node = astroid.extract_node(source)
        basenames = astroid_utils.resolve_qualname(node.bases[0], node.basenames[0])
        assert basenames == expected

    @pytest.mark.parametrize(
        ("source", "expected"),
        [
            ('a = "a"', ("a", "a")),
            ("a = 1", ("a", 1)),
            ("a, b, c = (1, 2, 3)", None),
            ("a = b = 1", None),
        ],
    )
    def test_can_get_assign_values(self, source, expected):
        node = astroid.extract_node(source)
        value = astroid_utils.get_assign_value(node)
        assert value == expected

    @pytest.mark.parametrize(
        "signature,expected",
        [
            (
                "a: bool, b: int = 5",
                [(None, "a", "bool", None), (None, "b", "int", "5")],
            ),
            pytest.param(
                "a: bool, /, b: int, *, c: str",
                [
                    (None, "a", "bool", None),
                    ("/", None, None, None),
                    (None, "b", "int", None),
                    ("*", None, None, None),
                    (None, "c", "str", None),
                ],
                marks=pytest.mark.skipif(
                    sys.version_info[:2] < (3, 8), reason="Uses Python 3.8+ syntax"
                ),
            ),
            pytest.param(
                "a: bool, /, b: int, *args, c: str, **kwargs",
                [
                    (None, "a", "bool", None),
                    ("/", None, None, None),
                    (None, "b", "int", None),
                    ("*", "args", None, None),
                    (None, "c", "str", None),
                    ("**", "kwargs", None, None),
                ],
                marks=pytest.mark.skipif(
                    sys.version_info[:2] < (3, 8), reason="Uses Python 3.8+ syntax"
                ),
            ),
            pytest.param(
                "a: int, *args, b: str, **kwargs",
                [
                    (None, "a", "int", None),
                    ("*", "args", None, None),
                    (None, "b", "str", None),
                    ("**", "kwargs", None, None),
                ],
                marks=pytest.mark.skipif(
                    sys.version_info[:2] < (3, 8), reason="Uses Python 3.8+ syntax"
                ),
            ),
        ],
    )
    def test_parse_annotations(self, signature, expected):
        node = astroid.extract_node(
            """
            def func({}) -> str: #@
                pass
        """.format(
                signature
            )
        )

        annotations = astroid_utils.get_args_info(node.args)
        assert annotations == expected

    @pytest.mark.parametrize(
        "signature,expected",
        [
            ("a: bool, b: int = 5, c='hi'", "a: bool, b: int = 5, c='hi'"),
            pytest.param(
                "a: bool, /, b: int, *, c: str",
                "a: bool, /, b: int, *, c: str",
                marks=pytest.mark.skipif(
                    sys.version_info[:2] < (3, 8), reason="Uses Python 3.8+ syntax"
                ),
            ),
            pytest.param(
                "a: bool, /, b: int, *args, c: str, **kwargs",
                "a: bool, /, b: int, *args, c: str, **kwargs",
                marks=pytest.mark.skipif(
                    sys.version_info[:2] < (3, 8), reason="Uses Python 3.8+ syntax"
                ),
            ),
            ("a: int, *args, b: str, **kwargs", "a: int, *args, b: str, **kwargs"),
            ("a: 'A'", "a: A"),
        ],
    )
    def test_format_args(self, signature, expected):
        node = astroid.extract_node(
            """
            def func({}) -> str: #@
                pass
        """.format(
                signature
            )
        )

        args_info = astroid_utils.get_args_info(node.args)
        formatted = objects._format_args(args_info)
        assert formatted == expected
