"""Unit tests for InteractiveIntrospectionMixin."""

from __future__ import annotations

import pytest

from pinecone._internal.introspection import InteractiveIntrospectionMixin


pytestmark = pytest.mark.unit


class SampleNamespace(InteractiveIntrospectionMixin):
    """Sample namespace class for testing the mixin."""

    def public_method_a(self) -> str:
        """A public method."""
        return "a"

    def public_method_b(self) -> str:
        """Another public method."""
        return "b"

    def _private_method(self) -> str:
        """A private method."""
        return "private"


class TestInteractiveIntrospectionMixin:
    """Tests for InteractiveIntrospectionMixin."""

    @pytest.fixture()
    def namespace(self) -> SampleNamespace:
        """Create a sample namespace instance."""
        return SampleNamespace()

    def test_dir_returns_public_methods_only(self, namespace: SampleNamespace):
        """Test that __dir__ returns only public methods."""
        methods = dir(namespace)

        # Should include public methods
        assert "public_method_a" in methods
        assert "public_method_b" in methods

        # Should NOT include private methods
        assert "_private_method" not in methods

        # Should NOT include dunder methods
        assert "__init__" not in methods
        assert "__dir__" not in methods
        assert "__getattr__" not in methods

    def test_dir_returns_sorted_list(self, namespace: SampleNamespace):
        """Test that __dir__ returns a sorted list."""
        methods = dir(namespace)

        assert methods == sorted(methods)

    def test_dir_includes_all_public_attributes(self, namespace: SampleNamespace):
        """Test that __dir__ includes all public attributes (not just callables)."""
        methods = dir(namespace)

        # Should include public methods
        assert "public_method_a" in methods
        assert "public_method_b" in methods

        # Verify it's the full public API (methods and properties)
        # using super().__dir__() means we get everything public
        assert all(not name.startswith("_") for name in methods)

    def test_getattr_nonexistent_attribute_shows_helpful_error(self, namespace: SampleNamespace):
        """Test that accessing non-existent attribute shows helpful error."""
        with pytest.raises(AttributeError) as exc_info:
            _ = namespace.nonexistent_method

        error_message = str(exc_info.value)

        # Should mention the attribute that was accessed
        assert "nonexistent_method" in error_message

        # Should mention the class name
        assert "SampleNamespace" in error_message

        # Should list available methods
        assert "public_method_a" in error_message
        assert "public_method_b" in error_message

        # Should suggest how to explore
        assert "dir(" in error_message or "help(" in error_message

    def test_getattr_uses_dynamic_discovery(self, namespace: SampleNamespace):
        """Test that __getattr__ dynamically discovers available methods."""
        with pytest.raises(AttributeError) as exc_info:
            _ = namespace.another_typo

        error_message = str(exc_info.value)

        # Should include actual available methods
        assert "public_method_a" in error_message
        assert "public_method_b" in error_message

        # Should NOT include private methods
        assert "_private_method" not in error_message

    def test_getattr_error_message_includes_class_name(self, namespace: SampleNamespace):
        """Test that error message uses actual class name, not hardcoded."""
        with pytest.raises(AttributeError) as exc_info:
            _ = namespace.missing

        error_message = str(exc_info.value)

        # Should use actual class name (not hardcoded)
        assert "SampleNamespace" in error_message

    def test_mixin_works_with_multiple_inheritance(self):
        """Test that mixin works correctly with multiple inheritance."""

        class OtherMixin:
            """Another mixin."""

            def other_method(self) -> str:
                return "other"

        class CombinedNamespace(InteractiveIntrospectionMixin, OtherMixin):
            """Namespace with multiple mixins."""

            def main_method(self) -> str:
                return "main"

        instance = CombinedNamespace()
        methods = dir(instance)

        # Should include methods from both mixins
        assert "main_method" in methods
        assert "other_method" in methods

        # Should not include private attributes
        assert "_private" not in "".join(methods)
