"""
Unit tests for event handler registry.

Tests the secure event handler registration and dispatch system,
ensuring safe function execution and proper security validation.
"""

import pytest
from unittest.mock import Mock, MagicMock
from ws.event_handlers import (
    EventHandlerRegistry,
    InvalidEventError,
    register_event_handler,
    call_event_handler,
    is_event_registered,
    _registry
)


class TestHandlerRegistration:
    """Test cases for event handler registration."""

    def test_register_handler_adds_to_registry(self):
        """Handler registered successfully and appears in registry."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act
        registry.register("testEvent", handler)

        # Assert
        assert registry.is_registered("testEvent")
        assert "testEvent" in registry.list_events()

    def test_register_handler_prevents_dunder_methods(self):
        """__init__, __dict__, and other dunder methods rejected."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act & Assert
        with pytest.raises(ValueError, match="cannot contain __ or start with _"):
            registry.register("__init__", handler)

        with pytest.raises(ValueError, match="cannot contain __ or start with _"):
            registry.register("__dict__", handler)

        with pytest.raises(ValueError, match="cannot contain __ or start with _"):
            registry.register("test__method", handler)

    def test_register_handler_prevents_private_methods(self):
        """_private methods rejected."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act & Assert
        with pytest.raises(ValueError, match="cannot contain __ or start with _"):
            registry.register("_private", handler)

        with pytest.raises(ValueError, match="cannot contain __ or start with _"):
            registry.register("_privateMethod", handler)

    def test_register_handler_allows_valid_names(self):
        """Normal function names accepted."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act
        registry.register("characterSetup", handler)
        registry.register("applyForJob", handler)
        registry.register("purchaseItem", handler)
        registry.register("handleEvent123", handler)

        # Assert
        assert registry.is_registered("characterSetup")
        assert registry.is_registered("applyForJob")
        assert registry.is_registered("purchaseItem")
        assert registry.is_registered("handleEvent123")

    def test_register_handler_requires_callable(self):
        """Non-callable objects rejected."""
        # Arrange
        registry = EventHandlerRegistry()

        # Act & Assert
        with pytest.raises(ValueError, match="Handler must be callable"):
            registry.register("event", "not_a_function")

        with pytest.raises(ValueError, match="Handler must be callable"):
            registry.register("event", 123)

        with pytest.raises(ValueError, match="Handler must be callable"):
            registry.register("event", None)

    def test_register_handler_requires_valid_event_type(self):
        """Invalid event types rejected."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act & Assert
        with pytest.raises(ValueError, match="Invalid event type"):
            registry.register("", handler)

        with pytest.raises(ValueError, match="Invalid event type"):
            registry.register(None, handler)

    def test_register_handler_allows_duplicate_registration(self):
        """Registering same handler twice does not raise error (for hot-reload)."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act
        registry.register("testEvent", handler)
        registry.register("testEvent", handler)  # Should not raise

        # Assert
        assert registry.is_registered("testEvent")


class TestHandlerExecution:
    """Test cases for event handler execution."""

    def test_execute_handler_calls_function(self):
        """Registered function executed when called."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock(return_value="success")
        registry.register("testEvent", handler)
        player = Mock()

        # Act
        result = registry.call("testEvent", player, "answer", "key1", {"data": "value"})

        # Assert
        handler.assert_called_once()
        assert result == "success"

    def test_execute_handler_passes_arguments(self):
        """Arguments forwarded correctly to handler."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()
        registry.register("testEvent", handler)
        player = Mock()
        mode = "answer"
        key = "eventKey"
        message = {"field": "value"}

        # Act
        registry.call("testEvent", player, mode, key, message)

        # Assert
        handler.assert_called_once_with(player, mode, key, message)

    def test_execute_handler_returns_result(self):
        """Return value propagated from handler."""
        # Arrange
        registry = EventHandlerRegistry()
        expected_result = {"status": "success", "data": [1, 2, 3]}
        handler = Mock(return_value=expected_result)
        registry.register("testEvent", handler)
        player = Mock()

        # Act
        result = registry.call("testEvent", player, "answer", "key", {})

        # Assert
        assert result == expected_result

    def test_execute_handler_raises_on_unknown(self):
        """Unknown handler raises InvalidEventError."""
        # Arrange
        registry = EventHandlerRegistry()
        player = Mock()

        # Act & Assert
        with pytest.raises(InvalidEventError, match="No handler registered for event"):
            registry.call("unknownEvent", player, "answer", "key", {})

    def test_execute_handler_raises_on_invalid_event_type(self):
        """Invalid event type raises InvalidEventError."""
        # Arrange
        registry = EventHandlerRegistry()
        player = Mock()

        # Act & Assert
        with pytest.raises(InvalidEventError, match="Invalid event type"):
            registry.call("", player, "answer", "key", {})

        with pytest.raises(InvalidEventError, match="Invalid event type"):
            registry.call(None, player, "answer", "key", {})

    def test_execute_handler_rejects_dangerous_patterns(self):
        """Dangerous event patterns rejected at execution time."""
        # Arrange
        registry = EventHandlerRegistry()
        player = Mock()

        # Act & Assert
        with pytest.raises(InvalidEventError, match="Dangerous event type rejected"):
            registry.call("__init__", player, "answer", "key", {})

        with pytest.raises(InvalidEventError, match="Dangerous event type rejected"):
            registry.call("_private", player, "answer", "key", {})

    def test_execute_handler_propagates_exceptions(self):
        """Exceptions from handler bubble up correctly."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock(side_effect=ValueError("Handler error"))
        registry.register("testEvent", handler)
        player = Mock()

        # Act & Assert
        with pytest.raises(ValueError, match="Handler error"):
            registry.call("testEvent", player, "answer", "key", {})


class TestSecurityValidation:
    """Test cases for security validation and sandboxing."""

    def test_handler_cannot_access_globals_via_name(self):
        """Handler cannot be registered with names that access __globals__."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act & Assert
        with pytest.raises(ValueError, match="cannot contain __ or start with _"):
            registry.register("__globals__", handler)

    def test_handler_cannot_be_registered_with_dangerous_names(self):
        """Cannot register handlers with dangerous method names."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act & Assert - Test various dangerous patterns
        dangerous_names = [
            "__import__",
            "__builtins__",
            "__code__",
            "__class__",
            "_dangerous",
        ]

        for name in dangerous_names:
            with pytest.raises(ValueError, match="cannot contain __ or start with _"):
                registry.register(name, handler)

    def test_handler_execution_is_sandboxed(self):
        """Handler execution does not expose dangerous internals."""
        # Arrange
        registry = EventHandlerRegistry()

        # Create a handler that tries to access dangerous attributes
        def dangerous_handler(player, mode, key, message):
            # This should execute, but we verify it doesn't break security
            return "executed"

        registry.register("testEvent", dangerous_handler)
        player = Mock()

        # Act
        result = registry.call("testEvent", player, "answer", "key", {})

        # Assert - Handler executes but cannot be called via dangerous names
        assert result == "executed"

        # Verify dangerous names are blocked
        with pytest.raises(InvalidEventError):
            registry.call("__globals__", player, "answer", "key", {})

    def test_handler_cannot_import_via_registration(self):
        """Cannot register handler that would allow dangerous imports."""
        # Arrange
        registry = EventHandlerRegistry()

        # Note: We can't prevent what a handler does internally,
        # but we can prevent registration of dangerous names

        # Act & Assert
        with pytest.raises(ValueError):
            registry.register("__import__", lambda: __import__('os'))


class TestEdgeCases:
    """Test cases for edge cases and special scenarios."""

    def test_register_multiple_handlers(self):
        """Multiple handlers can be registered independently."""
        # Arrange
        registry = EventHandlerRegistry()
        handler1 = Mock(return_value="result1")
        handler2 = Mock(return_value="result2")
        handler3 = Mock(return_value="result3")

        # Act
        registry.register("event1", handler1)
        registry.register("event2", handler2)
        registry.register("event3", handler3)

        # Assert
        assert len(registry.list_events()) == 3
        assert registry.is_registered("event1")
        assert registry.is_registered("event2")
        assert registry.is_registered("event3")

        # Verify each handler is called independently
        player = Mock()
        assert registry.call("event1", player, "answer", "k", {}) == "result1"
        assert registry.call("event2", player, "answer", "k", {}) == "result2"
        assert registry.call("event3", player, "answer", "k", {}) == "result3"

    def test_execute_handler_with_complex_arguments(self):
        """Handler can receive complex argument types."""
        # Arrange
        registry = EventHandlerRegistry()
        received_args = []

        def capture_handler(player, mode, key, message):
            received_args.append((player, mode, key, message))
            return "captured"

        registry.register("testEvent", capture_handler)

        # Complex arguments
        player = Mock(id="player123", name="Test Player", data={"nested": True})
        mode = "answer"
        key = {"complex": "key", "with": ["multiple", "values"]}
        message = {
            "nested": {
                "structure": True,
                "list": [1, 2, 3],
                "dict": {"a": "b"}
            }
        }

        # Act
        result = registry.call("testEvent", player, mode, key, message)

        # Assert
        assert result == "captured"
        assert len(received_args) == 1
        assert received_args[0][0] == player
        assert received_args[0][1] == mode
        assert received_args[0][2] == key
        assert received_args[0][3] == message

    def test_handler_error_propagation(self):
        """Different exception types propagate correctly."""
        # Arrange
        registry = EventHandlerRegistry()
        player = Mock()

        # Test ValueError
        registry.register("valueError", Mock(side_effect=ValueError("value error")))
        with pytest.raises(ValueError, match="value error"):
            registry.call("valueError", player, "answer", "k", {})

        # Test RuntimeError
        registry.register("runtimeError", Mock(side_effect=RuntimeError("runtime error")))
        with pytest.raises(RuntimeError, match="runtime error"):
            registry.call("runtimeError", player, "answer", "k", {})

        # Test custom exception
        class CustomError(Exception):
            pass

        registry.register("customError", Mock(side_effect=CustomError("custom")))
        with pytest.raises(CustomError, match="custom"):
            registry.call("customError", player, "answer", "k", {})

    def test_list_events_returns_all_registered(self):
        """list_events() returns all registered event types."""
        # Arrange
        registry = EventHandlerRegistry()
        handler = Mock()

        # Act
        registry.register("event1", handler)
        registry.register("event2", handler)
        registry.register("event3", handler)

        # Assert
        events = registry.list_events()
        assert len(events) == 3
        assert "event1" in events
        assert "event2" in events
        assert "event3" in events

    def test_is_registered_returns_false_for_unregistered(self):
        """is_registered() returns False for unregistered events."""
        # Arrange
        registry = EventHandlerRegistry()

        # Act & Assert
        assert not registry.is_registered("nonexistent")
        assert not registry.is_registered("anotherEvent")


class TestGlobalRegistryHelpers:
    """Test cases for global registry helper functions."""

    def setup_method(self):
        """Clear global registry before each test."""
        # Note: We need to be careful with global state
        # In production, consider using fixtures to manage this
        pass

    def test_register_event_handler_adds_to_global_registry(self):
        """register_event_handler() adds to global registry."""
        # Arrange
        handler = Mock()
        event_name = "globalTestEvent1"

        # Act
        register_event_handler(event_name, handler)

        # Assert
        assert is_event_registered(event_name)

    def test_call_event_handler_executes_from_global_registry(self):
        """call_event_handler() executes from global registry."""
        # Arrange
        handler = Mock(return_value="global_result")
        event_name = "globalTestEvent2"
        register_event_handler(event_name, handler)
        player = Mock()

        # Act
        result = call_event_handler(event_name, player, "answer", "key", {"data": "value"})

        # Assert
        assert result == "global_result"
        handler.assert_called_once_with(player, "answer", "key", {"data": "value"})

    def test_is_event_registered_checks_global_registry(self):
        """is_event_registered() checks global registry."""
        # Arrange
        handler = Mock()
        event_name = "globalTestEvent3"

        # Act
        assert not is_event_registered(event_name)
        register_event_handler(event_name, handler)

        # Assert
        assert is_event_registered(event_name)


class TestIntegrationScenarios:
    """Integration test cases for realistic usage scenarios."""

    def test_complete_event_lifecycle(self):
        """Complete event registration and execution lifecycle."""
        # Arrange
        registry = EventHandlerRegistry()

        # Simulate a character setup handler
        def character_setup_handler(player, mode, key, message):
            player.c.firstname = message.get("name")
            player.c.ageYears = message.get("age")
            player.c.sex = message.get("sex")
            return {"status": "success", "character": player.c}

        registry.register("characterSetup", character_setup_handler)

        # Create mock player
        player = Mock()
        player.c = Mock()

        message = {
            "name": "Alice",
            "age": 25,
            "sex": "female"
        }

        # Act
        result = registry.call("characterSetup", player, "answer", "setup", message)

        # Assert
        assert result["status"] == "success"
        assert player.c.firstname == "Alice"
        assert player.c.ageYears == 25
        assert player.c.sex == "female"

    def test_multiple_sequential_calls(self):
        """Multiple sequential handler calls work correctly."""
        # Arrange
        registry = EventHandlerRegistry()
        call_count = {"count": 0}

        def counter_handler(player, mode, key, message):
            call_count["count"] += 1
            return call_count["count"]

        registry.register("counter", counter_handler)
        player = Mock()

        # Act & Assert
        assert registry.call("counter", player, "answer", "k", {}) == 1
        assert registry.call("counter", player, "answer", "k", {}) == 2
        assert registry.call("counter", player, "answer", "k", {}) == 3

    def test_registry_isolation(self):
        """Multiple registry instances are isolated."""
        # Arrange
        registry1 = EventHandlerRegistry()
        registry2 = EventHandlerRegistry()

        handler1 = Mock(return_value="handler1")
        handler2 = Mock(return_value="handler2")

        # Act
        registry1.register("event", handler1)
        registry2.register("event", handler2)

        player = Mock()

        # Assert
        assert registry1.call("event", player, "answer", "k", {}) == "handler1"
        assert registry2.call("event", player, "answer", "k", {}) == "handler2"
