"""
Comprehensive unit tests for context_implementations module.
Tests all concrete implementations to achieve 100% coverage.
"""

import pytest
import inspect
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock

from storm_logger.context_implementations import (
    DefaultFrameInspector, DefaultFileSystemInspector,
    DefaultPathAnalyzer, DefaultPatternMatcher
)


class TestDefaultFrameInspector:
    """Test DefaultFrameInspector implementation."""
    
    def test_get_current_frame(self):
        """Test getting current frame."""
        inspector = DefaultFrameInspector()
        
        # The frame will be this test method's frame
        frame = inspector.get_current_frame()
        assert frame is not None
        assert hasattr(frame, 'f_code')
        assert hasattr(frame, 'f_locals')
        assert hasattr(frame, 'f_back')
    
    def test_get_frame_info(self):
        """Test extracting frame information."""
        inspector = DefaultFrameInspector()
        
        # Create a mock frame
        mock_frame = Mock()
        mock_frame.f_code.co_filename = "/test/file.py"
        mock_frame.f_code.co_name = "test_function"
        mock_frame.f_lineno = 42
        
        filename, function_name, line_number = inspector.get_frame_info(mock_frame)
        assert filename == "/test/file.py"
        assert function_name == "test_function"
        assert line_number == 42
    
    def test_get_frame_locals(self):
        """Test getting frame locals."""
        inspector = DefaultFrameInspector()
        
        # Create a mock frame with locals
        mock_frame = Mock()
        mock_frame.f_locals = {"var1": "value1", "var2": 42}
        
        locals_dict = inspector.get_frame_locals(mock_frame)
        assert locals_dict == {"var1": "value1", "var2": 42}
    
    def test_get_parent_frame(self):
        """Test getting parent frame."""
        inspector = DefaultFrameInspector()
        
        # Create mock frames
        parent_frame = Mock()
        current_frame = Mock()
        current_frame.f_back = parent_frame
        
        result = inspector.get_parent_frame(current_frame)
        assert result is parent_frame
    
    def test_get_parent_frame_none(self):
        """Test getting parent frame when there is none."""
        inspector = DefaultFrameInspector()
        
        # Create frame with no parent
        current_frame = Mock()
        current_frame.f_back = None
        
        result = inspector.get_parent_frame(current_frame)
        assert result is None


class TestDefaultFileSystemInspector:
    """Test DefaultFileSystemInspector implementation."""
    
    def test_get_current_directory(self):
        """Test getting current directory."""
        inspector = DefaultFileSystemInspector()
        
        with patch('pathlib.Path.cwd') as mock_cwd:
            mock_cwd.return_value = Path("/mocked/dir")
            result = inspector.get_current_directory()
            assert result == Path("/mocked/dir")
    
    def test_exists_true(self):
        """Test checking if path exists."""
        inspector = DefaultFileSystemInspector()
        
        mock_path = Mock(spec=Path)
        mock_path.exists.return_value = True
        
        result = inspector.exists(mock_path)
        assert result is True
        mock_path.exists.assert_called_once()
    
    def test_exists_false(self):
        """Test checking if path doesn't exist."""
        inspector = DefaultFileSystemInspector()
        
        mock_path = Mock(spec=Path)
        mock_path.exists.return_value = False
        
        result = inspector.exists(mock_path)
        assert result is False
    
    def test_get_parents(self):
        """Test getting parent directories."""
        inspector = DefaultFileSystemInspector()
        
        # Create mock path with parents
        mock_path = Mock(spec=Path)
        parent1 = Mock(spec=Path)
        parent2 = Mock(spec=Path)
        mock_path.parents = [parent1, parent2]
        
        result = inspector.get_parents(mock_path)
        assert result == [parent1, parent2]


class TestDefaultPathAnalyzer:
    """Test DefaultPathAnalyzer implementation."""
    
    def test_relative_to_success(self):
        """Test getting relative path successfully."""
        analyzer = DefaultPathAnalyzer()
        
        # Mock paths that can be made relative
        mock_path = Mock(spec=Path)
        mock_base = Mock(spec=Path)
        mock_relative = Mock(spec=Path)
        mock_path.relative_to.return_value = mock_relative
        
        result = analyzer.relative_to(mock_path, mock_base)
        assert result is mock_relative
        mock_path.relative_to.assert_called_once_with(mock_base)
    
    def test_relative_to_failure(self):
        """Test when path cannot be made relative."""
        analyzer = DefaultPathAnalyzer()
        
        # Mock paths that cannot be made relative
        mock_path = Mock(spec=Path)
        mock_base = Mock(spec=Path)
        mock_path.relative_to.side_effect = ValueError("Not relative")
        
        result = analyzer.relative_to(mock_path, mock_base)
        assert result is None
    
    def test_relative_to_os_error(self):
        """Test OS error when getting relative path."""
        analyzer = DefaultPathAnalyzer()
        
        mock_path = Mock(spec=Path)
        mock_base = Mock(spec=Path)
        mock_path.relative_to.side_effect = OSError("Permission denied")
        
        result = analyzer.relative_to(mock_path, mock_base)
        assert result is None
    
    def test_get_parts(self):
        """Test getting path parts."""
        analyzer = DefaultPathAnalyzer()
        
        mock_path = Mock(spec=Path)
        mock_path.parts = ("home", "user", "file.py")
        
        result = analyzer.get_parts(mock_path)
        assert result == ("home", "user", "file.py")
    
    def test_get_stem(self):
        """Test getting file stem."""
        analyzer = DefaultPathAnalyzer()
        
        mock_path = Mock(spec=Path)
        mock_path.stem = "filename"
        
        result = analyzer.get_stem(mock_path)
        assert result == "filename"


class TestDefaultPatternMatcher:
    """Test DefaultPatternMatcher implementation."""
    
    def test_match_file_pattern_match(self):
        """Test file pattern matching."""
        matcher = DefaultPatternMatcher()
        
        with patch('re.search') as mock_search:
            mock_search.return_value = Mock()  # Truthy value
            
            result = matcher.match_file_pattern("test_file.py", r'test_.*\.py$')
            assert result is True
            mock_search.assert_called_once_with(r'test_.*\.py$', "test_file.py")
    
    def test_match_file_pattern_no_match(self):
        """Test file pattern not matching."""
        matcher = DefaultPatternMatcher()
        
        with patch('re.search') as mock_search:
            mock_search.return_value = None
            
            result = matcher.match_file_pattern("file.py", r'test_.*\.py$')
            assert result is False
    
    def test_match_function_pattern_match(self):
        """Test function pattern matching."""
        matcher = DefaultPatternMatcher()
        
        with patch('re.match') as mock_match:
            mock_match.return_value = Mock()  # Truthy value
            
            result = matcher.match_function_pattern("test_something", r'^test_.*')
            assert result is True
            mock_match.assert_called_once_with(r'^test_.*', "test_something")
    
    def test_match_function_pattern_no_match(self):
        """Test function pattern not matching."""
        matcher = DefaultPatternMatcher()
        
        with patch('re.match') as mock_match:
            mock_match.return_value = None
            
            result = matcher.match_function_pattern("something", r'^test_.*')
            assert result is False
    
    def test_match_class_pattern_match(self):
        """Test class pattern matching."""
        matcher = DefaultPatternMatcher()
        
        with patch('re.match') as mock_match:
            mock_match.return_value = Mock()  # Truthy value
            
            result = matcher.match_class_pattern("TestCase", r'^Test.*')
            assert result is True
            mock_match.assert_called_once_with(r'^Test.*', "TestCase")
    
    def test_match_class_pattern_no_match(self):
        """Test class pattern not matching."""
        matcher = DefaultPatternMatcher()
        
        with patch('re.match') as mock_match:
            mock_match.return_value = None
            
            result = matcher.match_class_pattern("MyClass", r'^Test.*')
            assert result is False


if __name__ == "__main__":
    pytest.main([__file__, "-v"])