"""
Property-based tests for CodeSandbox using Hypothesis.

**Validates: Requirements 4.1, 4.4**
"""

import pytest
from hypothesis import given, strategies as st, assume
from fishertools.learn.repl.code_sandbox import CodeSandbox


class TestCodeSandboxProperties:
    """Property-based tests for code execution safety."""
    
    @given(st.text(min_size=1))
    def test_execution_always_returns_tuple(self, code):
        """
        For any code input, execute should always return a tuple of (bool, str).
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        result = sandbox.execute(code)
        
        assert isinstance(result, tuple), "Result should be a tuple"
        assert len(result) == 2, "Result should have 2 elements"
        assert isinstance(result[0], bool), "First element should be bool"
        assert isinstance(result[1], str), "Second element should be str"
    
    @given(st.text(min_size=1))
    def test_execution_never_crashes(self, code):
        """
        For any code input, execute should never crash the sandbox.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        try:
            result = sandbox.execute(code)
            # Should always return successfully
            assert result is not None
        except Exception as e:
            pytest.fail(f"Sandbox crashed with: {e}")
    
    @given(st.text(min_size=1))
    def test_no_file_operations_allowed(self, code):
        """
        For any code containing file operations, execution should fail safely.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        
        # Check if code contains file operations
        dangerous_patterns = ["open(", "read(", "write(", "file("]
        has_dangerous = any(pattern in code.lower() for pattern in dangerous_patterns)
        
        if has_dangerous:
            success, output = sandbox.execute(code)
            # Should either fail or not actually perform file operations
            # We can't guarantee it fails because the code might have syntax errors
            # But we can verify the sandbox doesn't crash
            assert isinstance(success, bool)
            assert isinstance(output, str)
    
    @given(st.text(min_size=1))
    def test_no_imports_allowed(self, code):
        """
        For any code containing imports, execution should fail safely.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        
        # Check if code contains imports
        import_patterns = ["import ", "from "]
        has_import = any(pattern in code.lower() for pattern in import_patterns)
        
        if has_import:
            success, output = sandbox.execute(code)
            # Should either fail or not actually perform imports
            assert isinstance(success, bool)
            assert isinstance(output, str)
    
    @given(st.text(min_size=1))
    def test_execution_is_deterministic(self, code):
        """
        For any code input, executing it twice should produce the same result.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        
        # Skip code that might have non-deterministic behavior
        if any(x in code.lower() for x in ["random", "time", "datetime"]):
            return
        
        result1 = sandbox.execute(code)
        result2 = sandbox.execute(code)
        
        # Results should be identical
        assert result1 == result2, f"Execution not deterministic for: {code}"
    
    @given(st.text(min_size=1))
    def test_output_is_string(self, code):
        """
        For any code execution, the output should always be a string.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        success, output = sandbox.execute(code)
        
        assert isinstance(output, str), "Output should always be a string"
        # Output should not be None
        assert output is not None, "Output should not be None"
    
    @given(st.text(min_size=1))
    def test_success_flag_is_boolean(self, code):
        """
        For any code execution, the success flag should always be a boolean.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        success, output = sandbox.execute(code)
        
        assert isinstance(success, bool), "Success flag should be boolean"
        assert success in [True, False], "Success should be True or False"
    
    @given(st.text(min_size=1))
    def test_dangerous_functions_blocked(self, code):
        """
        For any code containing dangerous functions, execution should fail safely.
        
        **Validates: Requirements 4.1, 4.4**
        """
        sandbox = CodeSandbox()
        
        # Check if code contains dangerous functions
        dangerous_funcs = ["exec(", "eval(", "compile(", "globals(", "locals("]
        has_dangerous = any(func in code.lower() for func in dangerous_funcs)
        
        if has_dangerous:
            success, output = sandbox.execute(code)
            # Should fail or not execute the dangerous function
            assert isinstance(success, bool)
            assert isinstance(output, str)
