from typing import Any
import inspect


def compare_attributes(obj1: Any, obj2: Any, *, ignore_simple_underscore: bool = False) -> bool:
    """Compares all attributes of two objects, except for methods, __dunderattrs__, and, optionally, _private_attrs.

    Args:
        obj1 (Any): First object to compare.
        obj2 (Any): Second object to compare.
        ignore_simple_underscore (:class:`bool`, optional): If ``True``, attributes starting with a single `_` won't be
            compared. Defaults to ``False`` (compares attributes starting with a single `_`, but not with two).

    Returns:
        :class:`bool`: ``True`` if obj1.X == obj2.X for all X (according to the criteria at the function description);
        ``False`` otherwise.

    Examples:
        .. testsetup:: *

            from serpcord.utils.model import compare_attributes
        .. doctest::

            >>> class A:
            ...     pass
            >>> a, b, c = A(), A(), A()
            >>> a.x = 5; a.y = 6; a.z = 7
            >>> b.x = 5; b.y = 7; b.z = 7
            >>> c.x = 5; c.y = 6; c.z = 7
            >>> compare_attributes(a, b)  # they differ in one attribute (y)
            False
            >>> compare_attributes(a, c)  # all attributes are equal
            True
    """
    for k1, v1 in inspect.getmembers(obj1):
        if k1.startswith("__") or (not ignore_simple_underscore and k1.startswith("_")):
            continue
        is_v1_method = inspect.ismethod(v1)
        for k2, v2 in filter(lambda k: k and k[0] == k1, inspect.getmembers(obj2)):
            if is_v1_method != inspect.ismethod(v2):  # one is a method and the other isn't?
                return False
            if not is_v1_method and v1 != v2:
                return False
    return True