from enum import Enum, IntEnum
from typing import Annotated

import pytest

import strawberry
from strawberry.exceptions import ObjectIsNotAnEnumError
from strawberry.types.base import get_object_definition
from strawberry.types.enum import StrawberryEnumDefinition


def test_basic_enum():
    @strawberry.enum
    class IceCreamFlavour(Enum):
        VANILLA = "vanilla"
        STRAWBERRY = "strawberry"
        CHOCOLATE = "chocolate"

    definition = IceCreamFlavour.__strawberry_definition__

    assert definition.name == "IceCreamFlavour"
    assert definition.description is None

    assert definition.values[0].name == "VANILLA"
    assert definition.values[0].value == "vanilla"

    assert definition.values[1].name == "STRAWBERRY"
    assert definition.values[1].value == "strawberry"

    assert definition.values[2].name == "CHOCOLATE"
    assert definition.values[2].value == "chocolate"


def test_can_pass_name_and_description():
    @strawberry.enum(name="Flavour", description="example")
    class IceCreamFlavour(Enum):
        VANILLA = "vanilla"
        STRAWBERRY = "strawberry"
        CHOCOLATE = "chocolate"

    definition = IceCreamFlavour.__strawberry_definition__

    assert definition.name == "Flavour"
    assert definition.description == "example"


def test_can_use_enum_as_arguments():
    @strawberry.enum
    class IceCreamFlavour(Enum):
        VANILLA = "vanilla"
        STRAWBERRY = "strawberry"
        CHOCOLATE = "chocolate"

    @strawberry.type
    class Query:
        @strawberry.field
        def flavour_available(self, flavour: IceCreamFlavour) -> bool:
            return flavour == IceCreamFlavour.STRAWBERRY

    field = Query.__strawberry_definition__.fields[0]

    assert isinstance(field.arguments[0].type, StrawberryEnumDefinition)


@pytest.mark.raises_strawberry_exception(
    ObjectIsNotAnEnumError,
    match="strawberry.enum can only be used with subclasses of Enum. ",
)
def test_raises_error_when_using_enum_with_a_not_enum_class():
    @strawberry.enum
    class AClass:
        hello = "world"


def test_can_deprecate_enum_values():
    @strawberry.enum
    class IceCreamFlavour(Enum):
        VANILLA = strawberry.enum_value("vanilla")
        STRAWBERRY = strawberry.enum_value(
            "strawberry", deprecation_reason="We ran out"
        )
        CHOCOLATE = "chocolate"

    definition = IceCreamFlavour.__strawberry_definition__

    assert definition.values[0].name == "VANILLA"
    assert definition.values[0].value == "vanilla"
    assert definition.values[0].deprecation_reason is None

    assert definition.values[1].name == "STRAWBERRY"
    assert definition.values[1].value == "strawberry"
    assert definition.values[1].deprecation_reason == "We ran out"

    assert definition.values[2].name == "CHOCOLATE"
    assert definition.values[2].value == "chocolate"
    assert definition.values[2].deprecation_reason is None


def test_can_describe_enum_values():
    @strawberry.enum
    class IceCreamFlavour(Enum):
        VANILLA = strawberry.enum_value("vanilla")
        STRAWBERRY = strawberry.enum_value(
            "strawberry",
            description="Our favourite",
        )
        CHOCOLATE = "chocolate"

    definition = IceCreamFlavour.__strawberry_definition__

    assert definition.values[0].name == "VANILLA"
    assert definition.values[0].value == "vanilla"
    assert definition.values[0].description is None

    assert definition.values[1].name == "STRAWBERRY"
    assert definition.values[1].value == "strawberry"
    assert definition.values[1].description == "Our favourite"

    assert definition.values[2].name == "CHOCOLATE"
    assert definition.values[2].value == "chocolate"
    assert definition.values[2].description is None


def test_can_use_enum_values():
    @strawberry.enum
    class TestEnum(Enum):
        A = "A"
        B = strawberry.enum_value("B")
        C = strawberry.enum_value("Coconut", deprecation_reason="We ran out")

    assert TestEnum.B.value == "B"

    assert [x.value for x in TestEnum.__members__.values()] == [
        "A",
        "B",
        "Coconut",
    ]


def test_int_enums():
    @strawberry.enum
    class TestEnum(IntEnum):
        A = 1
        B = 2
        C = 3
        D = strawberry.enum_value(4, description="D")

    assert TestEnum.A.value == 1
    assert TestEnum.B.value == 2
    assert TestEnum.C.value == 3
    assert TestEnum.D.value == 4

    assert [x.value for x in TestEnum.__members__.values()] == [1, 2, 3, 4]


def test_default_enum_implementation() -> None:
    class Foo(Enum):
        BAR = "bar"
        BAZ = "baz"

    @strawberry.type
    class Query:
        @strawberry.field
        def foo(self, foo: Foo) -> Foo:
            return foo

    schema = strawberry.Schema(Query)
    res = schema.execute_sync("{ foo(foo: BAR) }")
    assert not res.errors
    assert res.data
    assert res.data["foo"] == "BAR"


def test_default_enum_reuse() -> None:
    class Foo(Enum):
        BAR = "bar"
        BAZ = "baz"

    @strawberry.type
    class SomeType:
        foo: Foo
        bar: Foo

    definition = get_object_definition(SomeType, strict=True)
    assert definition.fields[1].type is definition.fields[1].type


def test_default_enum_with_enum_name_and_value() -> None:
    class Foo(Enum):
        FOO = "foo"
        BAR = strawberry.enum_value("bar")
        BAZ = strawberry.enum_value("baz", name="Baz")

    @strawberry.type
    class Query:
        @strawberry.field
        def foo(self, enums: list[Foo]) -> list[Foo]:
            assert enums == [Foo.FOO, Foo.BAR, Foo.BAZ]
            return enums

    schema = strawberry.Schema(Query)
    res = schema.execute_sync("{ foo(enums: [FOO, BAR, Baz]) }")
    assert not res.errors
    assert res.data
    assert res.data["foo"] == ["FOO", "BAR", "Baz"]


def test_use_enum_values() -> None:
    @strawberry.enum(graphql_name_from="value")
    class Foo(Enum):
        FOO = "foo"
        BAR = strawberry.enum_value("bar")
        BAZ = strawberry.enum_value("baz", name="Baz")

    @strawberry.type
    class Query:
        @strawberry.field
        def foo(self, enums: list[Foo]) -> list[Foo]:
            assert enums == [Foo.FOO, Foo.BAR, Foo.BAZ]
            return enums

    schema = strawberry.Schema(Query)
    res = schema.execute_sync("{ foo(enums: [foo, bar, Baz]) }")
    assert not res.errors
    assert res.data
    assert res.data["foo"] == ["foo", "bar", "Baz"]


def test_invalid_enum_value() -> None:
    @strawberry.enum(graphql_name_from="value")
    class Foo(Enum):
        FOO = 5  # Should be an string

    @strawberry.type
    class Query:
        foo: Foo

    with pytest.raises(TypeError, match=r"Expected name to be a string."):
        strawberry.Schema(Query)


def test_annotated_enum_with_description():
    class IceCreamFlavour(Enum):
        VANILLA = "vanilla"
        STRAWBERRY = "strawberry"
        CHOCOLATE = "chocolate"

    MyIceCreamFlavour = Annotated[
        IceCreamFlavour, strawberry.enum(description="Ice cream flavours")
    ]

    @strawberry.type
    class Query:
        @strawberry.field
        def flavour(self) -> MyIceCreamFlavour:
            return IceCreamFlavour.VANILLA

    schema = strawberry.Schema(Query)
    schema_str = str(schema)
    assert '"""Ice cream flavours"""' in schema_str
    assert "enum IceCreamFlavour" in schema_str


def test_annotated_enum_with_name():
    class Flavour(Enum):
        VANILLA = "vanilla"
        STRAWBERRY = "strawberry"

    MyIceCreamFlavour = Annotated[Flavour, strawberry.enum(name="IceCreamFlavour")]

    @strawberry.type
    class Query:
        @strawberry.field
        def flavour(self) -> MyIceCreamFlavour:
            return Flavour.VANILLA

    schema = strawberry.Schema(Query)
    assert "IceCreamFlavour" in str(schema)


def test_annotated_enum_on_already_decorated():
    @strawberry.enum
    class Flavour(Enum):
        VANILLA = "vanilla"
        STRAWBERRY = "strawberry"

    MyFlavour = Annotated[Flavour, strawberry.enum(description="Flavours")]

    @strawberry.type
    class Query:
        @strawberry.field
        def flavour(self) -> MyFlavour:
            return Flavour.VANILLA

    schema = strawberry.Schema(Query)
    assert "enum Flavour" in str(schema)


def test_annotated_enum_as_resolver_parameter():
    class Color(Enum):
        RED = "red"
        GREEN = "green"
        BLUE = "blue"

    GqlColor = Annotated[Color, strawberry.enum()]

    @strawberry.type
    class Query:
        @strawberry.field
        def color_name(self, color: GqlColor) -> str:
            return color.value

    schema = strawberry.Schema(query=Query)
    result = schema.execute_sync("{ colorName(color: RED) }")
    assert result.errors is None
    assert result.data == {"colorName": "red"}
