import pytest
from maya_brew import OpenMaya2, cmds
from maya_brew.attributes.node_attribute import (
    Attribute,
    FloatAttribute,
    MessageAttribute,
    BoolAttribute,
    Float3Attribute,
    Float4Attribute,
    Float2Attribute,
    MatrixAttribute,
    EnumAttribute,
    StringAttribute,
)
from maya_brew.log import get_logger
from maya_brew.nodes.node_types import DagNode, Node

logger = get_logger(__name__)


def test_cast_attributes(brew_transform):
    tx = Attribute(f"{brew_transform.node_path}.translateX")
    assert isinstance(tx, FloatAttribute)
    message = Attribute(f"{brew_transform}.message")
    assert isinstance(message, MessageAttribute)


def test_message_attribute_get_value_error(brew_transform):
    with pytest.raises(AttributeError):
        Attribute._get_value(brew_transform, "message")


def test_attribute_get_value(brew_transform):
    tx = Attribute(f"{brew_transform.node_path}.translateX")
    value = tx.get()
    assert value == 0
    cmds.setAttr(f"{brew_transform.node_path}.translateX", 5)
    value = FloatAttribute._get_value(brew_transform, "translateX")
    assert value == 5


def test_factory_with_mplug(translatex_plug):
    a = Attribute(translatex_plug)
    assert isinstance(a, FloatAttribute)
    assert a.get() == 0


def test_subclass_with_mplug(translatex_plug):
    fa = FloatAttribute(translatex_plug)
    assert fa.get() == 0


def test_invalid_path_missing_dot():
    with pytest.raises(ValueError):
        Attribute("bad_path_without_dot")


def test_invalid_plug_type():
    with pytest.raises(ValueError):
        Attribute(123)  # type: ignore


def test_get_node_from_plug_dag_and_non_dag(translatex_plug, non_dag_plug):
    dag = Attribute._get_node_from_plug(translatex_plug)
    assert isinstance(dag, DagNode)
    expected_dag_path = OpenMaya2.MFnDagNode(translatex_plug.node()).fullPathName()
    assert str(dag) == expected_dag_path

    non_dag = Attribute._get_node_from_plug(non_dag_plug)
    assert isinstance(non_dag, Node)
    assert not isinstance(non_dag, DagNode)
    expected_non_dag_name = OpenMaya2.MFnDependencyNode(non_dag_plug.node()).name()
    non_dag_name = str(non_dag)
    assert non_dag_name == expected_non_dag_name


def test_attribute_set_instance(brew_transform):
    tx = Attribute(f"{brew_transform.node_path}.translateX")
    tx.set(3.14)
    assert cmds.getAttr(f"{brew_transform.node_path}.translateX") == 3.14


def test_attribute_set_redo(brew_transform):
    tx = Attribute(f"{brew_transform.node_path}.translateX")
    initial = cmds.getAttr(f"{brew_transform.node_path}.translateX")
    tx.set(8.88)
    with logger.silence():
        cmds.undo()
    assert cmds.getAttr(f"{brew_transform.node_path}.translateX") == initial
    with logger.silence():
        cmds.redo()
    assert cmds.getAttr(f"{brew_transform.node_path}.translateX") == 8.88


def test_message_attribute_set_value_error(brew_transform):
    message_attr = Attribute(f"{brew_transform}.message")
    with pytest.raises(AttributeError):
        message_attr.set(1)


def test_connect_and_force_overwrite_and_disconnect(brew_transform, test_cube):
    tx_attr = Attribute(f"{brew_transform.node_path}.translateX")
    ty_attr = Attribute(f"{brew_transform.node_path}.translateY")
    cube_ty = Attribute(f"{test_cube}.translateY")

    def get_ty_connections():
        return cmds.listConnections(cube_ty.plug.name(), source=True, plugs=True)

    tx_attr.connect(cube_ty)
    connections = get_ty_connections()
    expected_connection = tx_attr.plug.name()
    assert connections == [expected_connection]

    cmds.undo()
    assert not get_ty_connections()
    cmds.redo()
    assert get_ty_connections() == [expected_connection]

    ty_attr.connect(cube_ty, force=True)
    connections = get_ty_connections()
    expected_connection = ty_attr.plug.name()
    assert connections == [expected_connection]

    cmds.undo()
    assert get_ty_connections() == [tx_attr.plug.name()]
    cmds.redo()
    assert get_ty_connections() == [expected_connection]

    ty_attr.disconnect(cube_ty)
    assert not get_ty_connections()

    cmds.undo()
    assert get_ty_connections() == [ty_attr.plug.name()]
    cmds.redo()
    assert not get_ty_connections()


def test_string_attribute_factory_dispatch(brew_transform):
    """
    Verifies that Attribute("node.attr") dispatches to StringAttribute for string-typed
    attributes. create/set/get/delete coverage is handled by test_attribute_creation.
    """
    node_path = brew_transform.node_path
    attr_name = "my_string_attr"

    created_attr = StringAttribute.create(node_name=node_path, attr_name=attr_name)
    created_attr.set("hello")

    cast_attr = Attribute(f"{node_path}.{attr_name}")
    assert isinstance(cast_attr, StringAttribute)
    assert cast_attr.get() == "hello"


def test_attribute_creation(brew_transform):
    identity = OpenMaya2.MMatrix()
    enum_data = {"Red": 0, "Green": 1, "Blue": 2}
    green = enum_data["Green"]
    data = [
        (Float2Attribute, (2.0, 1.0), {}),
        (Float3Attribute, (1.1, 2.0, 3.3), {}),
        (Float4Attribute, (1.0, 2.0, 3.0, 4.0), {}),
        (FloatAttribute, 3.14, {}),
        (BoolAttribute, True, {}),
        (EnumAttribute, green, {"enum_data": enum_data}),
        (MatrixAttribute, identity, {}),
        (StringAttribute, "hello", {}),
    ]
    for attr_class, value, create_kwargs in data:
        attr_name = f"my_{attr_class.__name__}"
        created_attr = attr_class.create(
            node_name=brew_transform.node_path,
            attr_name=attr_name,
            category="my_attributes",
            **create_kwargs,
        )
        created_attr.set(value)
        assert created_attr.get() == value
        assert cmds.attributeQuery(
            attr_name, node=brew_transform.node_path, exists=True
        )
        assert "my_attributes" in cmds.attributeQuery(
            attr_name, node=brew_transform.node_path, categories=True
        )
        created_attr.delete()
        assert not cmds.attributeQuery(
            attr_name, node=brew_transform.node_path, exists=True
        )


def test_is_readable(brew_transform):
    tx = Attribute(f"{brew_transform.node_path}.translateX")
    assert tx.is_readable() is True

    created_attr = FloatAttribute.create(
        node_name=brew_transform.node_path,
        attr_name="my_readable_float",
        readable=True,
    )
    assert created_attr.is_readable() is True

    created_attr_non_readable = FloatAttribute.create(
        node_name=brew_transform.node_path,
        attr_name="my_non_readable_float",
        readable=False,
    )
    assert created_attr_non_readable.is_readable() is False
