import sys
import typing
from collections import namedtuple
from typing import Callable, NamedTuple

import pytest
from typing_extensions import Literal, get_origin

from pydantic import Field  # noqa: F401
from pydantic._internal._typing_extra import is_literal_type, is_namedtuple, is_none_type, origin_is_union

try:
    from typing import TypedDict as typing_TypedDict
except ImportError:
    typing_TypedDict = None

try:
    from typing_extensions import TypedDict as typing_extensions_TypedDict
except ImportError:
    typing_extensions_TypedDict = None


try:
    from mypy_extensions import TypedDict as mypy_extensions_TypedDict
except ImportError:
    mypy_extensions_TypedDict = None

ALL_TYPEDDICT_KINDS = (typing_TypedDict, typing_extensions_TypedDict, mypy_extensions_TypedDict)


def test_is_namedtuple():
    class Employee(NamedTuple):
        name: str
        id: int = 3

    assert is_namedtuple(namedtuple('Point', 'x y')) is True
    assert is_namedtuple(Employee) is True
    assert is_namedtuple(NamedTuple('Employee', [('name', str), ('id', int)])) is True

    class Other(tuple):
        name: str
        id: int

    assert is_namedtuple(Other) is False


def test_is_none_type():
    assert is_none_type(Literal[None]) is True
    assert is_none_type(None) is True
    assert is_none_type(type(None)) is True
    assert is_none_type(6) is False
    assert is_none_type({}) is False
    # WARNING: It's important to test `typing.Callable` not
    # `collections.abc.Callable` (even with python >= 3.9) as they behave
    # differently
    assert is_none_type(Callable) is False


@pytest.mark.parametrize('union_gen', [lambda: typing.Union[int, str], lambda: int | str])
def test_is_union(union_gen):
    try:
        union = union_gen()
    except TypeError:
        pytest.skip('not supported in this python version')
    else:
        origin = get_origin(union)
        assert origin_is_union(origin)


def test_is_literal_with_typing_extension_literal():
    from typing_extensions import Literal

    assert is_literal_type(Literal) is False
    assert is_literal_type(Literal['foo']) is True


@pytest.mark.skipif(sys.version_info < (3, 8), reason='`typing.Literal` is available for python 3.8 and above.')
def test_is_literal_with_typing_literal():
    from typing import Literal

    assert is_literal_type(Literal) is False
    assert is_literal_type(Literal['foo']) is True
