"""Validation helpers for interactive discovery features.

These helpers enforce that classes implement the interactive discovery patterns
documented in docs/conventions/interactive-discovery.md.
"""

from typing import Any


class InteractiveValidationError(AssertionError):
    """Raised when a class doesn't implement required interactive features."""


def assert_has_custom_repr(cls: type, instance: object) -> None:
    """Verify class has custom __repr__ implementation.

    Args:
        cls: The class to check
        instance: An instance of the class for testing

    Raises:
        InteractiveValidationError: If __repr__ is not overridden
    """
    # Verify it actually returns a string
    repr_result = repr(instance)
    if not isinstance(repr_result, str):
        raise InteractiveValidationError(
            f"{cls.__name__}.__repr__() must return str, got {type(repr_result)}"
        )

    # Verify it's not the default object repr format
    if repr_result.startswith("<") and "object at 0x" in repr_result:
        raise InteractiveValidationError(
            f"{cls.__name__}.__repr__() returns default format: {repr_result}. "
            "Must implement custom __repr__()"
        )


def assert_has_custom_dir(cls: type, instance: object) -> None:
    """Verify class has custom __dir__ implementation.

    Args:
        cls: The class to check
        instance: An instance of the class for testing

    Raises:
        InteractiveValidationError: If __dir__ is not overridden
    """
    # Verify it returns a list
    dir_result = dir(instance)
    if not isinstance(dir_result, list):
        raise InteractiveValidationError(
            f"{cls.__name__}.__dir__() must return list, got {type(dir_result)}"
        )

    # Check if filtering is happening (should have fewer attrs than default)
    # If custom __dir__ is implemented, it should filter out private attributes
    private_count = sum(1 for x in dir_result if x.startswith("_"))
    if private_count > 0:
        raise InteractiveValidationError(
            f"{cls.__name__}.__dir__() should filter private attributes but found {private_count}. "
            "Likely using default object.__dir__() - implement custom __dir__()"
        )


def assert_dir_hides_private_attrs(instance: object, cls_name: str | None = None) -> None:
    """Verify dir() doesn't expose private/internal attributes.

    Args:
        instance: The instance to check
        cls_name: Optional class name for error messages

    Raises:
        InteractiveValidationError: If private attributes are exposed
    """
    dir_result = dir(instance)
    private = [x for x in dir_result if x.startswith("_")]

    if private:
        name = cls_name or instance.__class__.__name__
        raise InteractiveValidationError(
            f"{name}.__dir__() exposes private attributes: {private}. "
            "Should filter out attributes starting with '_'"
        )


def assert_repr_hides_secrets(instance: object, secret_substrings: list[str]) -> None:
    """Verify __repr__ doesn't leak secrets like API keys.

    Args:
        instance: The instance to check
        secret_substrings: List of secret strings that should NOT appear in repr

    Raises:
        InteractiveValidationError: If secrets are found in repr output
    """
    repr_result = repr(instance)
    cls_name = instance.__class__.__name__

    for secret in secret_substrings:
        if secret in repr_result:
            raise InteractiveValidationError(
                f"{cls_name}.__repr__() leaks secret: found '{secret}' in output. "
                "Secrets like API keys should never appear in repr."
            )


def assert_repr_truncates_arrays(
    instance: object, max_length: int = 500, cls_name: str | None = None
) -> None:
    """Verify __repr__ truncates large arrays instead of showing full contents.

    Args:
        instance: The instance to check
        max_length: Maximum acceptable repr length
        cls_name: Optional class name for error messages

    Raises:
        InteractiveValidationError: If repr is too long (likely showing full arrays)
    """
    repr_result = repr(instance)
    name = cls_name or instance.__class__.__name__

    if len(repr_result) > max_length:
        raise InteractiveValidationError(
            f"{name}.__repr__() output too long ({len(repr_result)} chars > {max_length}). "
            "Should show summary info with truncated arrays, not full contents."
        )


def assert_has_repr_pretty(cls: type, instance: object) -> None:
    """Verify class has _repr_pretty_ for IPython support.

    Args:
        cls: The class to check
        instance: An instance of the class for testing

    Raises:
        InteractiveValidationError: If _repr_pretty_ is missing or broken
    """
    if not hasattr(instance, "_repr_pretty_"):
        raise InteractiveValidationError(
            f"{cls.__name__} should implement _repr_pretty_() for IPython/Jupyter support"
        )

    # Test that it doesn't crash with mock printer
    class MockPrinter:
        def __init__(self):
            self.output = []

        def text(self, s: str) -> None:
            self.output.append(s)

        def breakable(self) -> None:
            pass

        def group(self, *args: Any, **kwargs: Any) -> "MockPrinter":
            return self

        def __enter__(self) -> "MockPrinter":
            return self

        def __exit__(self, *args: Any) -> None:
            pass

    # Test normal case
    try:
        p = MockPrinter()
        instance._repr_pretty_(p, cycle=False)
    except Exception as e:
        raise InteractiveValidationError(
            f"{cls.__name__}._repr_pretty_() raised exception: {e}"
        ) from e

    # Test cycle detection
    try:
        p = MockPrinter()
        instance._repr_pretty_(p, cycle=True)
    except Exception as e:
        raise InteractiveValidationError(
            f"{cls.__name__}._repr_pretty_() doesn't handle cycle=True: {e}"
        ) from e


def assert_has_repr_html(cls: type, instance: object) -> None:
    """Verify class has _repr_html_ for Jupyter notebook support.

    Args:
        cls: The class to check
        instance: An instance of the class for testing

    Raises:
        InteractiveValidationError: If _repr_html_ is missing or broken
    """
    if not hasattr(instance, "_repr_html_"):
        raise InteractiveValidationError(
            f"{cls.__name__} should implement _repr_html_() for Jupyter notebook support"
        )

    # Test that it returns valid HTML
    try:
        html = instance._repr_html_()
    except Exception as e:
        raise InteractiveValidationError(
            f"{cls.__name__}._repr_html_() raised exception: {e}"
        ) from e

    if not isinstance(html, str):
        raise InteractiveValidationError(
            f"{cls.__name__}._repr_html_() must return str, got {type(html)}"
        )

    # Basic HTML validation
    if not ("<" in html and ">" in html):
        raise InteractiveValidationError(
            f"{cls.__name__}._repr_html_() doesn't return valid HTML: {html[:100]}"
        )


def assert_repr_html_no_xss(instance: object, user_provided_data: list[str]) -> None:
    """Verify _repr_html_ escapes user data to prevent XSS.

    Args:
        instance: The instance to check
        user_provided_data: List of user-provided strings that should be escaped

    Raises:
        InteractiveValidationError: If user data is not properly escaped
    """
    if not hasattr(instance, "_repr_html_"):
        return  # Skip if method doesn't exist

    html = instance._repr_html_()
    cls_name = instance.__class__.__name__

    for data in user_provided_data:
        if "<script>" in data.lower() and "<script>" in html.lower():
            raise InteractiveValidationError(
                f"{cls_name}._repr_html_() doesn't escape user data - found '<script>' tag. "
                "Use html.escape() to prevent XSS attacks."
            )


def validate_client_class(cls: type, instance: object, api_key: str) -> None:
    """Validate all interactive features for client/namespace classes.

    Args:
        cls: The class to check
        instance: An instance of the class
        api_key: The API key used (to verify it's not leaked)

    Raises:
        InteractiveValidationError: If any validation fails
    """
    assert_has_custom_repr(cls, instance)
    assert_has_custom_dir(cls, instance)
    assert_dir_hides_private_attrs(instance)
    assert_repr_hides_secrets(instance, [api_key])


def validate_response_class(cls: type, instance: object, *, require_rich_repr: bool = True) -> None:
    """Validate all interactive features for response classes.

    Args:
        cls: The class to check
        instance: An instance of the class
        require_rich_repr: Whether to require _repr_pretty_ and _repr_html_

    Raises:
        InteractiveValidationError: If any validation fails
    """
    assert_has_custom_repr(cls, instance)
    assert_has_custom_dir(cls, instance)
    assert_dir_hides_private_attrs(instance)
    assert_repr_truncates_arrays(instance)

    if require_rich_repr:
        assert_has_repr_pretty(cls, instance)
        assert_has_repr_html(cls, instance)
