"""
Advanced Penetration Tests for mizan Server Functions

These tests simulate a professional security researcher attempting to break
the protocol. Focus areas:

1. Race conditions and TOCTOU vulnerabilities
2. Memory exhaustion and resource depletion
3. Type confusion at serialization boundaries
4. Session/authentication state manipulation
5. Pydantic validation bypass attempts
6. WebSocket protocol-level attacks
7. Timing side-channel attacks
8. Concurrent state corruption
9. Deserialization attacks
10. Unicode normalization exploits

SAFE TO RUN: These tests don't execute actual exploits - they verify
that the defenses hold against attack patterns.
"""

import asyncio
import concurrent.futures
import gc
import json
import sys
import threading
import time
from decimal import Decimal
from fractions import Fraction
from typing import Any
from unittest.mock import MagicMock, AsyncMock, patch

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

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


# =============================================================================
# Test Models for Attack Scenarios
# =============================================================================


class SimpleOutput(BaseModel):
    value: str


class NumericOutput(BaseModel):
    result: float


class TimingOutput(BaseModel):
    authenticated: bool
    timestamp: float


# =============================================================================
# 1. MEMORY EXHAUSTION ATTACKS
# =============================================================================


class MemoryExhaustionTests(TestCase):
    """
    Attempt to exhaust server memory through crafted inputs.

    Attack vectors:
    - Exponential string expansion via repeated patterns
    - Hash collision attacks on dict keys
    - Deeply nested structures that explode on parsing
    """

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

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

        register(process_data, "process_data")

        @client
        def process_string(request: HttpRequest, text: str) -> SimpleOutput:
            return SimpleOutput(value=f"len={len(text)}")

        register(process_string, "process_string")

        @client
        def process_list(request: HttpRequest, items: list) -> SimpleOutput:
            return SimpleOutput(value=str(len(items)))

        register(process_list, "process_list")

    def tearDown(self):
        clear_registry()
        gc.collect()

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

    def test_hash_collision_dict_keys(self):
        """
        Attempt hash collision attack on dict keys.

        In Python, certain strings hash to the same value, which can cause
        O(n²) dict operations. Test that we handle this gracefully.
        """
        request = self._make_request()

        # These strings were chosen to potentially cause hash collisions
        # in older Python versions. Modern Python uses SipHash which mitigates this.
        collision_keys = {f"key_{i}_{chr(i % 256)}": i for i in range(10000)}

        start = time.time()
        result = execute_function(request, "process_data", {"data": collision_keys})
        elapsed = time.time() - start

        # Should complete in reasonable time (< 5 seconds)
        self.assertLess(elapsed, 5.0, "Hash collision attack caused slowdown")
        self.assertIsInstance(result, FunctionResult)

    def test_recursive_dict_structure(self):
        """
        Test handling of deeply recursive dict structures.

        Create maximum nesting that doesn't exceed Python's recursion limit
        but might exhaust memory during processing.
        """
        request = self._make_request()

        # Create a structure with wide fanout at each level
        def create_wide_nested(depth, width):
            if depth == 0:
                return "leaf"
            return {
                f"key_{i}": create_wide_nested(depth - 1, width) for i in range(width)
            }

        # 5 levels deep, 10 wide = 10^5 = 100,000 nodes
        wide_structure = create_wide_nested(5, 10)

        result = execute_function(request, "process_data", {"data": wide_structure})

        # process_data returns len(str(data)); should succeed with a numeric string
        self.assertIsInstance(result, FunctionResult)
        self.assertTrue(result.data["value"].isdigit())

    def test_billion_laughs_style_attack(self):
        """
        Test protection against billion laughs (exponential entity expansion).

        In XML this exploits entity expansion. In JSON, we try similar patterns
        with nested references that could expand exponentially.
        """
        request = self._make_request()

        # This is a simplified version - JSON doesn't support entity expansion
        # but we test that repeated nested structures don't cause issues
        lol = {"lol1": "lol" * 10}
        for i in range(2, 10):
            lol[f"lol{i}"] = [lol[f"lol{i-1}"]] * 10

        result = execute_function(request, "process_data", {"data": lol})
        # process_data returns len(str(data)); should succeed
        self.assertIsInstance(result, FunctionResult)
        self.assertTrue(result.data["value"].isdigit())

    def test_string_multiplication_via_repeat(self):
        """
        Test that string repeat patterns don't cause memory exhaustion.
        """
        request = self._make_request()

        # Large but not insane string
        large_string = "A" * (50 * 1024 * 1024)  # 50MB

        start_mem = sys.getsizeof(large_string)
        result = execute_function(request, "process_string", {"text": large_string})

        # Should succeed without memory explosion
        self.assertIsInstance(result, FunctionResult)

    def test_many_small_items_list(self):
        """
        Test handling of lists with many small items.
        """
        request = self._make_request()

        # 1 million small items
        many_items = list(range(1_000_000))

        result = execute_function(request, "process_list", {"items": many_items})
        # process_list returns len(items) as string
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["value"], "1000000")


# =============================================================================
# 2. TYPE CONFUSION AND SERIALIZATION BOUNDARY ATTACKS
# =============================================================================


class TypeConfusionTests(TestCase):
    """
    Attempt to confuse the type system at serialization boundaries.

    Attack vectors:
    - JSON types that don't map cleanly to Python
    - Special float values (NaN, Inf)
    - Type coercion edge cases
    - Custom __reduce__ / __getstate__ exploitation
    """

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

        @client
        def numeric_func(request: HttpRequest, value: float) -> NumericOutput:
            return NumericOutput(result=value * 2)

        register(numeric_func, "numeric_func")

        @client
        def any_input(request: HttpRequest, data: Any) -> SimpleOutput:
            return SimpleOutput(value=str(type(data).__name__))

        register(any_input, "any_input")

        @client
        def bool_func(request: HttpRequest, flag: bool) -> SimpleOutput:
            return SimpleOutput(value="yes" if flag else "no")

        register(bool_func, "bool_func")

    def tearDown(self):
        clear_registry()

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

    def test_nan_value_handling(self):
        """
        Test handling of NaN (Not a Number) values.

        NaN has special properties: NaN != NaN, can cause comparison issues.
        """
        request = self._make_request()

        import math

        # JSON doesn't support NaN directly, but we test the boundary
        result = execute_function(request, "numeric_func", {"value": float("nan")})

        # numeric_func doubles the value; NaN * 2 is still NaN
        self.assertIsInstance(result, FunctionResult)
        self.assertTrue(math.isnan(result.data["result"]))

    def test_infinity_handling(self):
        """
        Test handling of infinity values.
        """
        request = self._make_request()

        result = execute_function(request, "numeric_func", {"value": float("inf")})

        # inf * 2 is still inf
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["result"], float("inf"))

    def test_negative_infinity_handling(self):
        """Test handling of negative infinity."""
        request = self._make_request()

        result = execute_function(request, "numeric_func", {"value": float("-inf")})

        # -inf * 2 is still -inf
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["result"], float("-inf"))

    def test_very_small_float(self):
        """Test handling of very small floats (denormalized)."""
        request = self._make_request()

        # Smallest positive denormalized float
        tiny = sys.float_info.min * sys.float_info.epsilon

        result = execute_function(request, "numeric_func", {"value": tiny})
        # numeric_func doubles the value
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["result"], tiny * 2)

    def test_very_large_float(self):
        """Test handling of very large floats."""
        request = self._make_request()

        huge = sys.float_info.max

        result = execute_function(request, "numeric_func", {"value": huge})
        # Doubling max float should overflow to inf
        if isinstance(result, FunctionResult):
            self.assertEqual(result.data["result"], float("inf"))

    def test_boolean_type_confusion(self):
        """
        Test type confusion between bool, int, and string.

        In Python, bool is a subclass of int. Test edge cases.
        """
        request = self._make_request()

        # These should all be coerced correctly by Pydantic
        coercible_cases = [
            (True, "yes"),
            (False, "no"),
            (1, "yes"),  # int 1 -> bool True
            (0, "no"),  # int 0 -> bool False
            ("true", "yes"),  # string coercion
            ("false", "no"),
        ]

        for input_val, expected in coercible_cases:
            result = execute_function(request, "bool_func", {"flag": input_val})
            # Pydantic handles bool coercion
            self.assertIsInstance(result, FunctionResult)
            self.assertEqual(result.data["value"], expected)

        # Empty string is NOT a valid bool in Pydantic v2 — should fail validation
        result = execute_function(request, "bool_func", {"flag": ""})
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.VALIDATION_ERROR)

    def test_class_injection_via_dict(self):
        """
        Attempt to inject class references via dict keys.

        Test that __class__, __bases__, __mro__ etc. are just dict keys.
        """
        request = self._make_request()

        malicious_dict = {
            "__class__": "injected",
            "__bases__": ["object"],
            "__mro__": ["evil"],
            "__dict__": {"pwned": True},
            "__init__": "code",
            "__new__": "constructor",
            "__reduce__": ["os.system", ["whoami"]],
            "__reduce_ex__": "exploit",
            "__getstate__": "state",
            "__setstate__": "state",
        }

        result = execute_function(request, "any_input", {"data": malicious_dict})

        # Should just be treated as a dict
        self.assertIsInstance(result, FunctionResult)
        self.assertEqual(result.data["value"], "dict")


# =============================================================================
# 3. RACE CONDITIONS AND TOCTOU ATTACKS
# =============================================================================


class RaceConditionTests(TestCase):
    """
    Test for race conditions and Time-of-Check-Time-of-Use vulnerabilities.

    Attack vectors:
    - Concurrent requests with same session
    - Authorization check vs execution race
    - State modification during execution
    """

    def setUp(self):
        clear_registry()
        self.factory = RequestFactory()
        self.call_count = 0
        self.auth_checks = []
        self.executions = []

        # Track timing of auth checks vs executions
        test_instance = self

        @client
        def timed_auth_func(request: HttpRequest) -> TimingOutput:
            check_time = time.time()
            test_instance.auth_checks.append(check_time)

            # Simulate some processing delay
            time.sleep(0.01)

            is_auth = request.user.is_authenticated
            exec_time = time.time()
            test_instance.executions.append(exec_time)

            return TimingOutput(authenticated=is_auth, timestamp=exec_time)

        register(timed_auth_func, "timed_auth_func")

        @client
        def counter_func(request: HttpRequest) -> SimpleOutput:
            test_instance.call_count += 1
            return SimpleOutput(value=str(test_instance.call_count))

        register(counter_func, "counter_func")

    def tearDown(self):
        clear_registry()

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

    def test_concurrent_execution_isolation(self):
        """
        Test that concurrent executions don't interfere with each other.

        Execute many requests simultaneously and verify results are correct.
        """
        results = []
        errors = []

        def make_call(i):
            try:
                request = self._make_request()
                result = execute_function(request, "counter_func", None)
                results.append((i, result))
            except Exception as e:
                errors.append((i, e))

        # Execute 100 concurrent calls
        with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
            futures = [executor.submit(make_call, i) for i in range(100)]
            concurrent.futures.wait(futures)

        # All should succeed
        self.assertEqual(len(errors), 0, f"Errors: {errors}")
        self.assertEqual(len(results), 100)

        # All results should be FunctionResult
        for i, result in results:
            self.assertIsInstance(result, FunctionResult)

    def test_user_state_consistency(self):
        """
        Test that user state remains consistent throughout execution.

        Simulates checking if the user authentication state could change
        between validation and execution.
        """

        # Create a user mock that changes state
        class MutableUser:
            def __init__(self):
                self._is_authenticated = True
                self.call_count = 0

            @property
            def is_authenticated(self):
                self.call_count += 1
                # Change state on second access (simulating race)
                if self.call_count > 1:
                    self._is_authenticated = False
                return self._is_authenticated

        mutable_user = MutableUser()
        request = self._make_request(user=mutable_user)

        result = execute_function(request, "timed_auth_func", None)

        # The result reflects the state at time of check
        # This test documents the behavior - the user's is_authenticated
        # is read during execution, and if it changes, that's reflected
        self.assertIsInstance(result, FunctionResult)
        # On first access is_authenticated returns True, on second it returns False
        # The function reads is_authenticated once, so it sees the first value (True)
        self.assertTrue(result.data["authenticated"])


# =============================================================================
# 4. PYDANTIC VALIDATION BYPASS ATTEMPTS
# =============================================================================


class PydanticBypassTests(TestCase):
    """
    Attempt to bypass Pydantic validation.

    Note: The @client decorator creates dynamic Pydantic models from function
    parameters. Custom validators must be on the parameter types themselves,
    not separate classes. This tests the actual validation behavior.

    Attack vectors:
    - Type coercion bypass
    - model_construct() exposure
    - __dict__ manipulation
    """

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

        # Register function with constrained types
        # Pydantic validates types automatically
        @client
        def typed_func(request: HttpRequest, count: int, name: str) -> SimpleOutput:
            return SimpleOutput(value=f"{name}:{count}")

        register(typed_func, "typed_func")

        @client
        def strict_func(request: HttpRequest, email: str) -> SimpleOutput:
            # Business logic validation (not Pydantic)
            if "@" not in email:
                raise ValueError("Invalid email format")
            return SimpleOutput(value=email)

        register(strict_func, "strict_func")

    def tearDown(self):
        clear_registry()

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

    def test_type_validation_enforced(self):
        """Verify that Pydantic type validation is enforced."""
        request = self._make_request()

        # Valid input
        result = execute_function(request, "typed_func", {"count": 42, "name": "test"})
        self.assertIsInstance(result, FunctionResult)

        # Invalid type - dict for int
        result = execute_function(
            request, "typed_func", {"count": {"nested": 1}, "name": "test"}
        )
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.VALIDATION_ERROR)

    def test_missing_required_field_rejected(self):
        """Verify that missing required fields are rejected."""
        request = self._make_request()

        # Missing 'name' field
        result = execute_function(request, "typed_func", {"count": 42})
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.VALIDATION_ERROR)

    def test_model_construct_not_exposed(self):
        """
        Verify that model_construct (which skips validation) isn't accessible.

        Pydantic's model_construct() creates instances without validation.
        Ensure special keys don't affect behavior.
        """
        request = self._make_request()

        # Try to send instructions to use model_construct
        malicious_input = {
            "__pydantic_validator__": "skip",
            "model_construct": True,
            "count": {"evil": "dict"},  # Should fail type validation
            "name": "test",
        }

        result = execute_function(request, "typed_func", malicious_input)

        # Extra fields ignored, dict for int should fail validation
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.VALIDATION_ERROR)

    def test_business_logic_validation_works(self):
        """Test that validation in function body works correctly."""
        request = self._make_request()

        # Invalid email (business logic check)
        result = execute_function(request, "strict_func", {"email": "notanemail"})
        # This raises ValueError in the function, caught as INTERNAL_ERROR
        self.assertIsInstance(result, FunctionError)


# =============================================================================
# 5. WEBSOCKET PROTOCOL ATTACKS
# =============================================================================


class WebSocketProtocolTests(TestCase):
    """
    Attack the WebSocket protocol layer.

    Attack vectors:
    - Malformed JSON payloads
    - Missing required protocol fields
    - Protocol state manipulation
    - Message ordering attacks
    - Subscription state corruption
    """

    def setUp(self):
        clear_registry()

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

        register(ws_func, "ws_func")

    def tearDown(self):
        clear_registry()

    def _create_consumer(self, user=None):
        from mizan.channels.connection import DjangoReactConsumer

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

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

        return consumer, sent_messages

    def test_malformed_action_field(self):
        """Test handling of malformed action field."""
        from asgiref.sync import async_to_sync

        consumer, messages = self._create_consumer()

        # Various malformed actions
        malformed_actions = [
            {"action": None},
            {"action": 123},
            {"action": ["subscribe"]},
            {"action": {"type": "subscribe"}},
            {"action": ""},
            {"action": "SUBSCRIBE"},  # Wrong case
            {"action": "subscribe\x00inject"},  # Null byte
        ]

        for payload in malformed_actions:
            messages.clear()
            async_to_sync(consumer.receive_json)(payload)

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

    def test_rpc_id_manipulation(self):
        """
        Test RPC ID manipulation attacks.

        Try to inject special IDs that might cause issues.
        """
        from asgiref.sync import async_to_sync

        consumer, messages = self._create_consumer()

        malicious_ids = [
            "",
            None,
            123,  # Non-string
            ["array"],
            {"object": True},
            "../../../etc/passwd",
            "id\x00inject",
            "a" * 10000,  # Very long ID
            "__proto__",
            "constructor",
        ]

        for mal_id in malicious_ids:
            messages.clear()
            payload = {
                "action": "rpc",
                "id": mal_id,
                "fn": "ws_func",
                "args": {"data": "test"},
            }
            async_to_sync(consumer.receive_json)(payload)

            # Should handle gracefully
            self.assertGreaterEqual(len(messages), 1)

    def test_subscription_state_corruption(self):
        """
        Attempt to corrupt subscription state.

        Try rapid subscribe/unsubscribe cycles and malformed params.
        """
        from mizan.channels import register as register_channel, ReactChannel
        from mizan.channels import _registry as channels_registry
        from asgiref.sync import async_to_sync

        channels_registry.clear()

        class TestChannel(ReactChannel):
            class Params(BaseModel):
                room: str

            class DjangoMessage(BaseModel):
                text: str

            def authorize(self, params):
                return True

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

        register_channel(TestChannel, "test-channel")

        consumer, messages = self._create_consumer()

        # Rapid subscribe/unsubscribe
        for i in range(50):
            async_to_sync(consumer._handle_subscribe)(
                {"channel": "test-channel", "params": {"room": f"room_{i}"}}
            )
            async_to_sync(consumer._handle_unsubscribe)(
                {"channel": "test-channel", "params": {"room": f"room_{i}"}}
            )

        # Should not have any lingering subscriptions
        self.assertEqual(len(consumer._subscriptions), 0)

        channels_registry.clear()

    def test_double_subscription_attack(self):
        """
        Test attempting to subscribe to the same channel twice.
        """
        from mizan.channels import register as register_channel, ReactChannel
        from mizan.channels import _registry as channels_registry
        from asgiref.sync import async_to_sync

        channels_registry.clear()

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

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

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

        register_channel(TestChannel, "dup-channel")

        consumer, messages = self._create_consumer()

        # First subscription
        async_to_sync(consumer._handle_subscribe)(
            {"channel": "dup-channel", "params": {}}
        )
        self.assertIn("subscribed", messages[-1])

        # Second subscription to same channel
        async_to_sync(consumer._handle_subscribe)(
            {"channel": "dup-channel", "params": {}}
        )

        # Should return error about already subscribed
        self.assertIn("error", messages[-1])

        channels_registry.clear()


# =============================================================================
# 6. TIMING SIDE-CHANNEL ATTACKS
# =============================================================================


class TimingSideChannelTests(TestCase):
    """
    Test for timing side-channel vulnerabilities.

    Attack vectors:
    - Auth check timing differences
    - Function existence timing
    - Validation timing differences
    """

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

        @client
        def existing_func(request: HttpRequest) -> SimpleOutput:
            return SimpleOutput(value="exists")

        register(existing_func, "existing_func")

        @client
        def auth_func(request: HttpRequest) -> SimpleOutput:
            if not request.user.is_authenticated:
                raise PermissionError("Auth required")
            return SimpleOutput(value="authenticated")

        register(auth_func, "auth_func")

    def tearDown(self):
        clear_registry()

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

    def test_function_existence_timing(self):
        """
        Test if function existence can be determined by timing.

        Measure timing difference between existing and non-existing functions.
        """
        request = self._make_request()

        # Time existing function
        existing_times = []
        for _ in range(100):
            start = time.perf_counter()
            execute_function(request, "existing_func", None)
            existing_times.append(time.perf_counter() - start)

        # Time non-existing function
        nonexistent_times = []
        for _ in range(100):
            start = time.perf_counter()
            execute_function(request, "nonexistent_func_12345", None)
            nonexistent_times.append(time.perf_counter() - start)

        # Calculate averages
        avg_existing = sum(existing_times) / len(existing_times)
        avg_nonexistent = sum(nonexistent_times) / len(nonexistent_times)

        # The timing difference should be small (< 10x)
        # Large differences could leak function existence
        ratio = max(avg_existing, avg_nonexistent) / min(avg_existing, avg_nonexistent)

        # Document the ratio but don't fail - this is informational
        print(f"\nTiming ratio (existing/nonexistent): {ratio:.2f}")
        print(f"Avg existing: {avg_existing*1000:.3f}ms")
        print(f"Avg nonexistent: {avg_nonexistent*1000:.3f}ms")

        # Both should be fast
        self.assertLess(avg_existing, 0.1)  # < 100ms
        self.assertLess(avg_nonexistent, 0.1)

    def test_auth_timing_attack(self):
        """
        Test if authentication status can be determined by timing.
        """
        # Authenticated user
        auth_user = MagicMock()
        auth_user.is_authenticated = True

        auth_times = []
        for _ in range(100):
            request = self._make_request(user=auth_user)
            start = time.perf_counter()
            execute_function(request, "auth_func", None)
            auth_times.append(time.perf_counter() - start)

        # Anonymous user
        anon_times = []
        for _ in range(100):
            request = self._make_request(user=AnonymousUser())
            start = time.perf_counter()
            execute_function(request, "auth_func", None)
            anon_times.append(time.perf_counter() - start)

        avg_auth = sum(auth_times) / len(auth_times)
        avg_anon = sum(anon_times) / len(anon_times)

        print(f"\nAuth timing - authenticated: {avg_auth*1000:.3f}ms")
        print(f"Auth timing - anonymous: {avg_anon*1000:.3f}ms")

        # Both should be fast
        self.assertLess(avg_auth, 0.1)
        self.assertLess(avg_anon, 0.1)


# =============================================================================
# 7. UNICODE NORMALIZATION ATTACKS
# =============================================================================


class UnicodeNormalizationTests(TestCase):
    """
    Test Unicode normalization vulnerabilities.

    Different Unicode representations of "same" character might bypass
    checks or cause comparison issues.
    """

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

        @client
        def username_func(request: HttpRequest, username: str) -> SimpleOutput:
            # Simulates a function that checks username
            if username == "admin":
                raise PermissionError("Reserved username")
            return SimpleOutput(value=f"Hello, {username}")

        register(username_func, "username_func")

    def tearDown(self):
        clear_registry()

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

    def test_unicode_lookalike_bypass(self):
        """
        Test if Unicode lookalikes can bypass string checks.

        Various Unicode characters look like ASCII but are different.
        """
        request = self._make_request()

        # These look like "admin" but use different Unicode characters
        lookalikes = [
            "\u0430dmin",  # Cyrillic 'а' (U+0430) instead of Latin 'a'
            "adm\u0131n",  # Turkish dotless i (U+0131)
            "\u00e1dmin",  # Latin a with acute
            "\uff41\uff44\uff4d\uff49\uff4e",  # Fullwidth characters
            "\u0251dmin",  # Latin alpha
            "\u0430\u0501m\u0456n",  # Mix of Cyrillic characters
        ]

        for lookalike in lookalikes:
            result = execute_function(request, "username_func", {"username": lookalike})
            # These should NOT be blocked (they're different strings from "admin")
            self.assertIsInstance(result, FunctionResult)
            self.assertIn("Hello, ", result.data["value"])

    def test_unicode_normalization_forms(self):
        """
        Test different Unicode normalization forms of same character.

        The same visual character can have multiple representations.
        """
        import unicodedata

        request = self._make_request()

        # é can be represented as:
        # 1. U+00E9 (precomposed)
        # 2. U+0065 U+0301 (decomposed: e + combining acute)
        precomposed = "caf\u00e9"  # café with precomposed é
        decomposed = "cafe\u0301"  # café with combining acute

        # These look identical but are different byte sequences
        self.assertNotEqual(precomposed, decomposed)

        # Both should be handled as valid input
        result1 = execute_function(request, "username_func", {"username": precomposed})
        result2 = execute_function(request, "username_func", {"username": decomposed})

        self.assertIsInstance(result1, FunctionResult)
        self.assertIsInstance(result2, FunctionResult)

    def test_zero_width_character_injection(self):
        """
        Test zero-width character injection.

        Zero-width characters are invisible but affect string comparison.
        """
        request = self._make_request()

        zero_width_chars = [
            "\u200b",  # Zero-width space
            "\u200c",  # Zero-width non-joiner
            "\u200d",  # Zero-width joiner
            "\u2060",  # Word joiner
            "\ufeff",  # Zero-width no-break space (BOM)
        ]

        for zwc in zero_width_chars:
            # "admin" with zero-width character inserted
            username = f"ad{zwc}min"
            result = execute_function(request, "username_func", {"username": username})
            # Should NOT match "admin" (different string)
            self.assertIsInstance(result, FunctionResult)


# =============================================================================
# 8. JSON PARSING EDGE CASES
# =============================================================================


class JSONParsingEdgeCaseTests(TestCase):
    """
    Test JSON parsing edge cases that might cause issues.
    """

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

        @client
        def json_func(request: HttpRequest, data: dict) -> SimpleOutput:
            return SimpleOutput(value=json.dumps(data))

        register(json_func, "json_func")

    def tearDown(self):
        clear_registry()

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

    def test_duplicate_keys(self):
        """
        Test handling of duplicate keys in JSON.

        JSON spec says duplicate keys have undefined behavior.
        Python's json module takes the last value.
        """
        request = self._make_request()

        # This would be: {"key": "first", "key": "second"}
        # We can't easily send this through our API, but test the principle
        data = {"key": "value"}
        result = execute_function(request, "json_func", {"data": data})
        self.assertIsInstance(result, FunctionResult)

    def test_deep_json_nesting(self):
        """
        Test very deeply nested JSON (recursion limit).
        """
        request = self._make_request()

        # Create deeply nested structure (approaching recursion limit)
        nested = {"value": "bottom"}
        for _ in range(500):
            nested = {"nested": nested}

        try:
            result = execute_function(request, "json_func", {"data": nested})
            # Should either succeed or fail gracefully
            self.assertIn(type(result), [FunctionResult, FunctionError])
        except RecursionError:
            # This is acceptable - Python's recursion limit hit
            pass

    def test_json_number_precision(self):
        """
        Test JSON number precision edge cases.
        """
        request = self._make_request()

        edge_numbers = {
            "max_safe_int": 9007199254740991,  # 2^53 - 1
            "beyond_safe": 9007199254740993,  # 2^53 + 1 (loses precision in JS)
            "huge_int": 10**100,
            "tiny_float": 1e-308,
            "huge_float": 1e308,
        }

        result = execute_function(request, "json_func", {"data": edge_numbers})
        # Python handles these fine, but good to verify
        self.assertIsInstance(result, FunctionResult)


# =============================================================================
# 9. AUTHORIZATION BOUNDARY TESTS
# =============================================================================


class AuthorizationBoundaryTests(TestCase):
    """
    Test authorization at boundaries between different privilege levels.
    """

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

        @client
        def escalation_func(request: HttpRequest, target_role: str) -> SimpleOutput:
            # Simulates a function that might allow privilege escalation
            allowed_roles = ["user", "moderator"]
            if not request.user.is_authenticated:
                raise PermissionError("Auth required")
            if target_role not in allowed_roles:
                raise PermissionError(f"Cannot escalate to {target_role}")
            return SimpleOutput(value=f"Role set to {target_role}")

        register(escalation_func, "escalation_func")

    def tearDown(self):
        clear_registry()

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

    def test_role_escalation_blocked(self):
        """Test that privilege escalation attempts are blocked."""
        user = MagicMock()
        user.is_authenticated = True

        request = self._make_request(user=user)

        # Try to escalate to admin
        result = execute_function(request, "escalation_func", {"target_role": "admin"})
        self.assertIsInstance(result, FunctionError)
        self.assertEqual(result.code, ErrorCode.FORBIDDEN)

    def test_role_case_sensitivity(self):
        """Test that role checks are case-sensitive."""
        user = MagicMock()
        user.is_authenticated = True

        request = self._make_request(user=user)

        # Try variations
        variations = ["ADMIN", "Admin", "MODERATOR", "Moderator", "USER"]

        for role in variations:
            result = execute_function(request, "escalation_func", {"target_role": role})
            # Only exact lowercase matches should work; all uppercase variants
            # should be rejected since the allowed list is ["user", "moderator"]
            self.assertIsInstance(result, FunctionError)
            self.assertEqual(result.code, ErrorCode.FORBIDDEN)


# =============================================================================
# 10. FUNCTION REGISTRATION ATTACKS
# =============================================================================


class RegistrationSecurityTests(TestCase):
    """
    Test security of the function registration system.
    """

    def setUp(self):
        clear_registry()

    def tearDown(self):
        clear_registry()

    def test_cannot_override_with_different_function(self):
        """
        Test that a different function cannot override an existing one.

        Note: Re-registration of the same function name IS allowed for hot reload.
        But a DIFFERENT function cannot take over an existing name.
        """
        from mizan.client import ServerFunction
        from mizan_core.registry import register

        # Register first function
        class OriginalFunc(ServerFunction):
            class Output(BaseModel):
                value: str

            def call(self, input):
                return self.Output(value="original")

        register(OriginalFunc, "protected_func")

        # Different class trying to take the same name
        class EvilFunc(ServerFunction):
            class Output(BaseModel):
                value: str

            def call(self, input):
                return self.Output(value="evil")

        # This should raise because EvilFunc != OriginalFunc
        with self.assertRaises(ValueError) as ctx:
            register(EvilFunc, "protected_func")

        self.assertIn("already registered", str(ctx.exception).lower())

    def test_function_name_validation(self):
        """Test that function names are validated."""
        # The @client decorator uses the function name directly
        # Test that special characters in function names don't cause issues

        # This should work - underscores are fine
        @client
        def normal_func_name(request: HttpRequest) -> SimpleOutput:
            return SimpleOutput(value="ok")

        register(normal_func_name, "normal_func_name")

        fn = get_function("normal_func_name")
        self.assertIsNotNone(fn)

    def test_internal_functions_not_exposed(self):
        """Verify that internal/private functions aren't accidentally exposed."""
        factory = RequestFactory()
        request = factory.post("/")
        request.user = AnonymousUser()

        # Try to access functions that might exist internally
        internal_names = [
            "__init__",
            "__call__",
            "_private",
            "execute_function",
            "get_function",
            "clear_registry",
            "register",
        ]

        for name in internal_names:
            result = execute_function(request, name, None)
            self.assertIsInstance(result, FunctionError)
            self.assertEqual(result.code, ErrorCode.NOT_FOUND)
