import dataclasses

import pytest
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
from typing_extensions import assert_type

import reflex as rx
from reflex.utils.types import GenericType
from reflex.vars.base import Var
from reflex.vars.object import LiteralObjectVar, ObjectVar
from reflex.vars.sequence import ArrayVar


class Bare:
    """A bare class with a single attribute."""

    quantity: int = 0


@rx.serializer
def serialize_bare(obj: Bare) -> dict:
    """A serializer for the bare class.

    Args:
        obj: The object to serialize.

    Returns:
        A dictionary with the quantity attribute.
    """
    return {"quantity": obj.quantity}


class Base(rx.Base):
    """A reflex base class with a single attribute."""

    quantity: int = 0


class SqlaBase(DeclarativeBase, MappedAsDataclass):
    """Sqlalchemy declarative mapping base class."""

    pass


class SqlaModel(SqlaBase):
    """A sqlalchemy model with a single attribute."""

    __tablename__: str = "sqla_model"

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True, init=False)
    quantity: Mapped[int] = mapped_column(default=0)


@dataclasses.dataclass
class Dataclass:
    """A dataclass with a single attribute."""

    quantity: int = 0


class ObjectState(rx.State):
    """A reflex state with bare, base and sqlalchemy base vars."""

    bare: rx.Field[Bare] = rx.field(Bare())
    bare_optional: rx.Field[Bare | None] = rx.field(None)
    base: rx.Field[Base] = rx.field(Base())
    base_optional: rx.Field[Base | None] = rx.field(None)
    sqlamodel: rx.Field[SqlaModel] = rx.field(SqlaModel())
    sqlamodel_optional: rx.Field[SqlaModel | None] = rx.field(None)
    dataclass: rx.Field[Dataclass] = rx.field(Dataclass())
    dataclass_optional: rx.Field[Dataclass | None] = rx.field(None)

    base_list: rx.Field[list[Base]] = rx.field([Base()])


@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_var_create(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None:
    my_object = type_()
    var = Var.create(my_object)
    assert var._var_type is type_
    assert isinstance(var, ObjectVar)
    quantity = var.quantity
    assert quantity._var_type is int


@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_literal_create(type_: GenericType) -> None:
    my_object = type_()
    var = LiteralObjectVar.create(my_object)
    assert var._var_type is type_

    quantity = var.quantity
    assert quantity._var_type is int


@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_guess(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None:
    my_object = type_()
    var = Var.create(my_object)
    var = var.guess_type()
    assert var._var_type is type_
    assert isinstance(var, ObjectVar)
    quantity = var.quantity
    assert quantity._var_type is int


@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_state(type_: GenericType) -> None:
    attr_name = type_.__name__.lower()
    var = getattr(ObjectState, attr_name)
    assert var._var_type is type_

    quantity = var.quantity
    assert quantity._var_type is int


@pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass])
def test_state_to_operation(type_: GenericType) -> None:
    attr_name = type_.__name__.lower()
    original_var = getattr(ObjectState, attr_name)

    var = original_var.to(ObjectVar, type_)
    assert var._var_type is type_

    var = original_var.to(ObjectVar)
    assert var._var_type is type_


def test_typing() -> None:
    # Bare
    var = ObjectState.bare.to(ObjectVar)
    _ = assert_type(var, ObjectVar[Bare])

    # Base
    var = ObjectState.base
    _ = assert_type(var, ObjectVar[Base])
    optional_var = ObjectState.base_optional
    _ = assert_type(optional_var, ObjectVar[Base | None])
    list_var = ObjectState.base_list
    _ = assert_type(list_var, ArrayVar[list[Base]])
    list_var_0 = list_var[0]
    _ = assert_type(list_var_0, ObjectVar[Base])

    # Sqla
    var = ObjectState.sqlamodel
    _ = assert_type(var, ObjectVar[SqlaModel])
    optional_var = ObjectState.sqlamodel_optional
    _ = assert_type(optional_var, ObjectVar[SqlaModel | None])
    list_var = ObjectState.base_list
    _ = assert_type(list_var, ArrayVar[list[Base]])
    list_var_0 = list_var[0]
    _ = assert_type(list_var_0, ObjectVar[Base])

    # Dataclass
    var = ObjectState.dataclass
    _ = assert_type(var, ObjectVar[Dataclass])
    optional_var = ObjectState.dataclass_optional
    _ = assert_type(optional_var, ObjectVar[Dataclass | None])
    list_var = ObjectState.base_list
    _ = assert_type(list_var, ArrayVar[list[Base]])
    list_var_0 = list_var[0]
    _ = assert_type(list_var_0, ObjectVar[Base])