import unittest
from array import array
from collections import UserDict, UserList, UserString
from collections.abc import Mapping
from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union

from typing_extensions import Annotated as ExtAnnotated, TypeForm as ExtTypeForm

try:
    from typing import Annotated
except ImportError:
    Annotated = ExtAnnotated
try:
    from typing import TypeForm
except ImportError:
    TypeForm = ExtTypeForm

from robot.utils import (
    is_dict_like, is_falsy, is_list_like, is_truthy, is_union, PY_VERSION, type_name,
    type_repr
)
from robot.utils.asserts import assert_equal, assert_true


class MyMapping(Mapping):

    def __getitem__(self, item):
        return 0

    def __len__(self):
        return 0

    def __iter__(self):
        return iter([])


def generator():
    yield "generated"


class TestIsMisc(unittest.TestCase):

    def test_is_union(self):
        assert is_union(Union[int, str])
        assert not is_union((int, str))
        if PY_VERSION >= (3, 10):
            assert is_union(eval("int | str"))
        for not_union in "string", 3, [int, str], list, List[int]:
            assert not is_union(not_union)


class TestListLike(unittest.TestCase):

    def test_strings_are_not_list_like(self):
        for thing in ["string", UserString("user")]:
            assert_equal(is_list_like(thing), False, thing)

    def test_bytes_are_not_list_like(self):
        for thing in [b"bytes", bytearray(b"bytes")]:
            assert_equal(is_list_like(thing), False, thing)

    def test_dict_likes_are_list_like(self):
        for thing in [dict(), UserDict(), MyMapping()]:
            assert_equal(is_list_like(thing), True, thing)

    def test_files_are_not_list_like(self):
        with open(__file__, encoding="UTF-8") as f:
            assert_equal(is_list_like(f), False)
        assert_equal(is_list_like(f), False)

    def test_iter_makes_object_iterable_regardless_implementation(self):
        class Example:
            def __iter__(self):
                1 / 0

        assert_equal(is_list_like(Example()), True)

    def test_only_getitem_does_not_make_object_iterable(self):
        class Example:
            def __getitem__(self, item):
                return "I'm not iterable!"

        assert_equal(is_list_like(Example()), False)

    def test_iterables_in_general_are_list_like(self):
        for thing in [[], (), set(), range(1), generator(), array("i"), UserList()]:
            assert_equal(is_list_like(thing), True, thing)

    def test_others_are_not_list_like(self):
        for thing in [1, None, True, object()]:
            assert_equal(is_list_like(thing), False, thing)

    def test_generators_are_not_consumed(self):
        g = generator()
        assert_equal(is_list_like(g), True)
        assert_equal(is_list_like(g), True)
        assert_equal(list(g), ["generated"])
        assert_equal(list(g), [])
        assert_equal(is_list_like(g), True)


class TestDictLike(unittest.TestCase):

    def test_dict_likes(self):
        for thing in [dict(), UserDict(), MyMapping()]:
            assert_equal(is_dict_like(thing), True, thing)

    def test_others(self):
        for thing in ["", b"", 1, None, True, object(), [], (), set()]:
            assert_equal(is_dict_like(thing), False, thing)


class TestTypeName(unittest.TestCase):

    def test_base_types(self):
        for item, exp in [
            ("x", "string"),
            (b"x", "bytes"),
            (bytearray(), "bytearray"),
            (1, "integer"),
            (1.0, "float"),
            (True, "boolean"),
            (None, "None"),
            (set(), "set"),
            ([], "list"),
            ((), "tuple"),
            ({}, "dictionary"),
        ]:
            assert_equal(type_name(item), exp)

    def test_file(self):
        with open(__file__, encoding="UTF-8") as f:
            assert_equal(type_name(f), "file")

    def test_custom_objects(self):
        class CamelCase:
            pass

        class lower:
            pass

        for item, exp in [
            (CamelCase(), "CamelCase"),
            (lower(), "lower"),
            (CamelCase, "CamelCase"),
        ]:
            assert_equal(type_name(item), exp)

    def test_strip_underscores(self):
        class _Foo_:
            pass

        assert_equal(type_name(_Foo_), "Foo")

    def test_underscore_name_is_not_used(self):
        class StrName:
            _name = "Don't use me!"

        class NoneName:
            _name = None

        assert_equal(type_name(StrName()), "StrName")
        assert_equal(type_name(StrName), "StrName")
        assert_equal(type_name(NoneName()), "NoneName")
        assert_equal(type_name(NoneName), "NoneName")

    def test_typing(self):
        for item, exp in [
            (List, "list"),
            (List[int], "list"),
            (Tuple, "tuple"),
            (Tuple[int], "tuple"),
            (Set, "set"),
            (Set[int], "set"),
            (Dict, "dictionary"),
            (Dict[int, str], "dictionary"),
            (Union, "Union"),
            (Union[int, str], "Union"),
            (Optional, "Optional"),
            (Optional[int], "Union"),
            (Literal, "Literal"),
            (Literal["x", 1], "Literal"),
            (Any, "Any"),
            (Annotated, "Annotated"),
            (Annotated[int, "xxx"], "Annotated"),
            (ExtAnnotated, "Annotated"),
            (ExtAnnotated[int, "xxx"], "Annotated"),
            (TypeForm, "TypeForm"),
            (TypeForm["str | int"], "TypeForm"),
            (ExtTypeForm, "TypeForm"),
            (ExtTypeForm["str | int"], "TypeForm"),
        ]:
            assert_equal(type_name(item), exp, str(item))

    if PY_VERSION >= (3, 10):

        def test_union_syntax(self):
            assert_equal(type_name(int | float), "Union")

    def test_capitalize(self):
        class lowerclass:
            pass

        class CamelClass:
            pass

        assert_equal(type_name("hello!", capitalize=True), "String")
        assert_equal(type_name(None, capitalize=True), "None")
        assert_equal(type_name(lowerclass(), capitalize=True), "Lowerclass")
        assert_equal(type_name(CamelClass(), capitalize=True), "CamelClass")


class TestTypeRepr(unittest.TestCase):

    def test_class(self):
        class Foo:
            pass

        assert_equal(type_repr(Foo), "Foo")

    def test_none(self):
        assert_equal(type_repr(None), "None")

    def test_ellipsis(self):
        assert_equal(type_repr(...), "...")

    def test_string(self):
        assert_equal(type_repr("MyType"), "MyType")

    def test_no_typing_prefix(self):
        assert_equal(type_repr(List), "List")

    def test_generics_from_typing(self):
        assert_equal(type_repr(List[Any]), "List[Any]")
        assert_equal(type_repr(Dict[int, None]), "Dict[int, None]")
        assert_equal(type_repr(Tuple[int, ...]), "Tuple[int, ...]")

    if PY_VERSION >= (3, 9):

        def test_generics(self):
            assert_equal(type_repr(list[Any]), "list[Any]")
            assert_equal(type_repr(dict[int, None]), "dict[int, None]")

    def test_union(self):
        assert_equal(type_repr(Union[int, float]), "int | float")
        assert_equal(type_repr(Union[int, None, List[Any]]), "int | None | List[Any]")
        assert_equal(type_repr(Union), "Union")
        if PY_VERSION >= (3, 10):
            assert_equal(type_repr(int | None | list[Any]), "int | None | list[Any]")

    def test_literal(self):
        assert_equal(type_repr(Literal["x", 1, True]), "Literal['x', 1, True]")
        assert_equal(type_repr(Literal["x", 1, True], nested=False), "Literal")

    def test_parameterized_special_forms(self):
        for item, exp in [
            (Annotated[int, "xxx"], "Annotated[int, 'xxx']"),
            (ExtAnnotated[int, "xxx"], "Annotated[int, 'xxx']"),
            (TypeForm[int], "TypeForm[int]"),
            (ExtTypeForm[int], "TypeForm[int]"),
        ]:
            assert_equal(type_repr(item), exp)

    def test_underscore_name_is_not_used(self):
        class StrName:
            _name = "Don't use me!"

            def __str__(self):
                return "type_repr should only be used with types"

        class NoneName(StrName):
            _name = None

        assert_equal(type_repr(StrName), "StrName")
        assert_equal(type_repr(StrName()), "type_repr should only be used with types")
        assert_equal(type_repr(NoneName), "NoneName")
        assert_equal(type_repr(NoneName()), "type_repr should only be used with types")


class TestIsTruthyFalsy(unittest.TestCase):

    def test_truthy_values(self):
        for item in [True, 1, [False], unittest.TestCase, "truE", "whatEver"]:
            for item in self._strings_also_in_different_cases(item):
                assert_true(is_truthy(item) is True)
                assert_true(is_falsy(item) is False)

    def test_falsy_values(self):
        class AlwaysFalse:
            __bool__ = __nonzero__ = lambda self: False

        falsy_strings = ["", "faLse", "nO", "nOne", "oFF", "0"]
        for item in falsy_strings + [False, None, 0, [], {}, AlwaysFalse()]:
            for item in self._strings_also_in_different_cases(item):
                assert_true(is_truthy(item) is False)
                assert_true(is_falsy(item) is True)

    def _strings_also_in_different_cases(self, item):
        yield item
        if isinstance(item, str):
            yield item.lower()
            yield item.upper()
            yield item.title()


if __name__ == "__main__":
    unittest.main()
