"""
Comprehensive unit tests for context_detector module.
Tests all methods and edge cases to achieve 100% coverage.
"""

import pytest
from pathlib import Path
from unittest.mock import Mock, MagicMock
from typing import Any, Dict, List, Optional, Tuple

from storm_logger.context_detector import (
    ContextDetector, ContextInfo, ContextType, 
    get_current_context, is_test_context, create_context_detector
)
from storm_logger.context_interfaces import (
    FrameInspector, FileSystemInspector, PathAnalyzer, PatternMatcher
)


class MockFrameInspector(FrameInspector):
    """Mock implementation of FrameInspector for testing."""
    
    def __init__(self):
        self.current_frame = None
        self.frame_info = ("test.py", "test_function", 42)
        self.frame_locals = {}
        self.parent_frame = None
    
    def get_current_frame(self) -> Optional[Any]:
        return self.current_frame
    
    def get_frame_info(self, frame: Any) -> Tuple[str, str, int]:
        return self.frame_info
    
    def get_frame_locals(self, frame: Any) -> Dict[str, Any]:
        return self.frame_locals
    
    def get_parent_frame(self, frame: Any) -> Optional[Any]:
        return self.parent_frame


class MockFileSystemInspector(FileSystemInspector):
    """Mock implementation of FileSystemInspector for testing."""
    
    def __init__(self):
        self.current_dir = Path("/project")
        self.exists_map = {
            Path("/project/manage.py"): True,
            Path("/project/settings.py"): False
        }
        self.parents_map = {
            Path("/project"): [Path("/")]
        }
    
    def get_current_directory(self) -> Path:
        return self.current_dir
    
    def exists(self, path: Path) -> bool:
        return self.exists_map.get(path, False)
    
    def get_parents(self, path: Path) -> List[Path]:
        return self.parents_map.get(path, [])


class MockPathAnalyzer(PathAnalyzer):
    """Mock implementation of PathAnalyzer for testing."""
    
    def __init__(self):
        self.relative_paths = {}
        self.parts_map = {}
        self.stem_map = {}
    
    def relative_to(self, path: Path, base: Path) -> Optional[Path]:
        key = (path, base)
        return self.relative_paths.get(key)
    
    def get_parts(self, path: Path) -> Tuple[str, ...]:
        return self.parts_map.get(path, ())
    
    def get_stem(self, path: Path) -> str:
        return self.stem_map.get(path, "unknown")


class MockPatternMatcher(PatternMatcher):
    """Mock implementation of PatternMatcher for testing."""
    
    def __init__(self):
        self.file_matches = {}
        self.function_matches = {}
        self.class_matches = {}
    
    def match_file_pattern(self, filename: str, pattern: str) -> bool:
        return self.file_matches.get((filename, pattern), False)
    
    def match_function_pattern(self, function_name: str, pattern: str) -> bool:
        return self.function_matches.get((function_name, pattern), False)
    
    def match_class_pattern(self, class_name: str, pattern: str) -> bool:
        return self.class_matches.get((class_name, pattern), False)


class TestContextDetector:
    """Test suite for ContextDetector class."""
    
    @pytest.fixture
    def mock_frame_inspector(self):
        """Provide mock frame inspector."""
        return MockFrameInspector()
    
    @pytest.fixture
    def mock_filesystem_inspector(self):
        """Provide mock filesystem inspector."""
        return MockFileSystemInspector()
    
    @pytest.fixture
    def mock_path_analyzer(self):
        """Provide mock path analyzer."""
        return MockPathAnalyzer()
    
    @pytest.fixture
    def mock_pattern_matcher(self):
        """Provide mock pattern matcher."""
        return MockPatternMatcher()
    
    @pytest.fixture
    def detector(self, mock_frame_inspector, mock_filesystem_inspector, 
                 mock_path_analyzer, mock_pattern_matcher):
        """Provide configured ContextDetector with mocks."""
        return ContextDetector(
            framework="django",
            frame_inspector=mock_frame_inspector,
            filesystem_inspector=mock_filesystem_inspector,
            path_analyzer=mock_path_analyzer,
            pattern_matcher=mock_pattern_matcher
        )
    
    def test_init_with_defaults(self):
        """Test initialization with default dependencies."""
        detector = ContextDetector()
        assert detector.framework is None
        assert detector._manual_context is None
        # Project root will be found based on manage.py/settings.py location
        assert isinstance(detector.project_root, Path)
    
    def test_init_with_custom_dependencies(self, mock_frame_inspector):
        """Test initialization with custom dependencies."""
        detector = ContextDetector(
            framework="flask",
            frame_inspector=mock_frame_inspector
        )
        assert detector.framework == "flask"
        assert detector._frame_inspector is mock_frame_inspector
    
    def test_set_manual_context(self, detector):
        """Test setting manual context."""
        context = {"context": "manual", "data": "test"}
        detector.set_manual_context(context)
        assert detector._manual_context == context
    
    def test_clear_manual_context(self, detector):
        """Test clearing manual context."""
        detector.set_manual_context({"test": "data"})
        detector.clear_manual_context()
        assert detector._manual_context is None
    
    def test_get_context_with_manual_override(self, detector):
        """Test get_context returns manual context when set."""
        detector.set_manual_context({"context": "custom"})
        result = detector.get_context()
        assert result == "custom"
    
    def test_get_context_with_framework(self, detector, mock_frame_inspector, mock_pattern_matcher):
        """Test get_context with framework prefix."""
        # Setup frame to return valid context
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("/project/tests/test_file.py", "test_function", 10)
        # Setup pattern to match test file
        mock_pattern_matcher.file_matches[("/project/tests/test_file.py", r'test_.*\.py$')] = True
        
        result = detector.get_context()
        assert result == "django.test"
    
    def test_get_context_exception_handling(self, detector, mock_frame_inspector):
        """Test get_context handles exceptions gracefully."""
        mock_frame_inspector.get_current_frame = Mock(side_effect=Exception("Test error"))
        result = detector.get_context()
        # With framework set to "django", unknown context will be "django.unknown"
        assert result == "django.unknown"
    
    def test_get_context_without_framework(self, mock_frame_inspector, mock_pattern_matcher):
        """Test get_context without framework returns app prefix."""
        # Create detector without framework
        detector = ContextDetector(
            framework=None,
            frame_inspector=mock_frame_inspector,
            pattern_matcher=mock_pattern_matcher
        )
        
        # Setup frame
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("/project/app/views.py", "index", 50)
        mock_pattern_matcher.file_matches[("/project/app/views.py", r'.*views\.py$')] = True
        
        result = detector.get_context()
        assert result == "app.view"
    
    def test_find_project_root_with_manage_py(self, mock_filesystem_inspector):
        """Test finding project root with manage.py."""
        detector = ContextDetector(filesystem_inspector=mock_filesystem_inspector)
        assert detector.project_root == Path("/project")
    
    def test_find_project_root_with_settings_py(self, mock_filesystem_inspector):
        """Test finding project root with settings.py."""
        # Change exists map to have settings.py instead
        mock_filesystem_inspector.exists_map = {
            Path("/project/manage.py"): False,
            Path("/project/settings.py"): True
        }
        detector = ContextDetector(filesystem_inspector=mock_filesystem_inspector)
        assert detector.project_root == Path("/project")
    
    def test_find_project_root_not_found(self, mock_filesystem_inspector):
        """Test project root defaults to current dir when not found."""
        mock_filesystem_inspector.exists_map = {}
        detector = ContextDetector(filesystem_inspector=mock_filesystem_inspector)
        assert detector.project_root == Path("/project")
    
    def test_detect_context_no_frame(self, detector, mock_frame_inspector):
        """Test detect_context when no frame available."""
        mock_frame_inspector.current_frame = None
        context = detector.detect_context()
        assert context.context_type == ContextType.UNKNOWN
        assert context.module_name == "unknown"
    
    def test_detect_context_test_file(self, detector, mock_frame_inspector, 
                                     mock_pattern_matcher, mock_path_analyzer):
        """Test detecting test context from filename."""
        # Setup frame
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("/project/tests/test_models.py", "test_user_creation", 25)
        
        # Setup path analysis
        test_path = Path("/project/tests/test_models.py")
        rel_path = Path("tests/test_models.py")
        mock_path_analyzer.relative_paths[(test_path, Path("/project"))] = rel_path
        mock_path_analyzer.parts_map[rel_path] = ("tests", "test_models.py")
        mock_path_analyzer.stem_map[rel_path] = "test_models"
        
        # Setup pattern matching
        mock_pattern_matcher.file_matches[("/project/tests/test_models.py", r'test_.*\.py$')] = True
        
        context = detector.detect_context()
        assert context.context_type == ContextType.TEST
        assert context.is_test is True
        assert context.test_method == "test_user_creation"
    
    def test_detect_context_view_function(self, detector, mock_frame_inspector,
                                         mock_pattern_matcher, mock_path_analyzer):
        """Test detecting view context from function name."""
        # Setup frame
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("/project/app/views.py", "get", 50)
        
        # Setup path analysis
        mock_path_analyzer.relative_paths[(Path("/project/app/views.py"), Path("/project"))] = Path("app/views.py")
        mock_path_analyzer.parts_map[Path("app/views.py")] = ("app", "views.py")
        mock_path_analyzer.stem_map[Path("app/views.py")] = "views"
        
        # Setup pattern matching for view
        mock_pattern_matcher.file_matches[("/project/app/views.py", r'.*views\.py$')] = True
        
        context = detector.detect_context()
        assert context.context_type == ContextType.VIEW
        assert context.function_name == "get"
    
    def test_detect_context_with_class(self, detector, mock_frame_inspector,
                                      mock_pattern_matcher, mock_path_analyzer):
        """Test detecting context with class information."""
        # Setup frame with class
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("/project/app/models.py", "save", 100)
        
        # Add self to locals
        mock_self = Mock()
        mock_self.__class__.__name__ = "UserModel"
        mock_frame_inspector.frame_locals = {"self": mock_self}
        
        # Setup path
        mock_path_analyzer.relative_paths[(Path("/project/app/models.py"), Path("/project"))] = Path("app/models.py")
        mock_path_analyzer.parts_map[Path("app/models.py")] = ("app", "models.py")
        mock_path_analyzer.stem_map[Path("app/models.py")] = "models"
        
        # Setup pattern matching
        mock_pattern_matcher.file_matches[("/project/app/models.py", r'.*models\.py$')] = True
        
        context = detector.detect_context()
        assert context.context_type == ContextType.MODEL
        assert context.class_name == "UserModel"
    
    def test_detect_context_class_method(self, detector, mock_frame_inspector,
                                        mock_pattern_matcher, mock_path_analyzer):
        """Test detecting class method context."""
        # Setup frame with cls
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("/project/app/utils.py", "create_instance", 200)
        
        # Add cls to locals
        mock_cls = Mock()
        mock_cls.__name__ = "Factory"
        mock_frame_inspector.frame_locals = {"cls": mock_cls}
        
        # Setup path
        mock_path_analyzer.relative_paths[(Path("/project/app/utils.py"), Path("/project"))] = Path("app/utils.py")
        mock_path_analyzer.parts_map[Path("app/utils.py")] = ("app", "utils.py")
        mock_path_analyzer.stem_map[Path("app/utils.py")] = "utils"
        
        context = detector.detect_context()
        assert context.context_type == ContextType.CLASS_METHOD
        assert context.class_name == "Factory"
    
    def test_detect_context_exception_handling(self, detector, mock_frame_inspector):
        """Test detect_context handles exceptions in frame inspection."""
        mock_frame_inspector.get_frame_info = Mock(side_effect=Exception("Frame error"))
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        
        context = detector.detect_context()
        assert context.context_type == ContextType.UNKNOWN
        assert "Frame error" in context.friendly_description
    
    def test_get_module_name_with_relative_path(self, detector, mock_path_analyzer):
        """Test module name extraction with relative path."""
        test_file = Path("/project/app/models/user.py")
        rel_path = Path("app/models/user.py")
        
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = rel_path
        mock_path_analyzer.parts_map[rel_path] = ("app", "models", "user.py")
        mock_path_analyzer.stem_map[rel_path] = "user"
        
        result = detector._get_module_name(str(test_file))
        assert result == "app.models.user"
    
    def test_get_module_name_init_file(self, detector, mock_path_analyzer):
        """Test module name extraction for __init__.py."""
        test_file = Path("/project/app/__init__.py")
        rel_path = Path("app/__init__.py")
        
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = rel_path
        mock_path_analyzer.parts_map[rel_path] = ("app", "__init__.py")
        mock_path_analyzer.stem_map[rel_path] = "__init__"
        
        result = detector._get_module_name(str(test_file))
        assert result == "app"
    
    def test_get_module_name_no_relative_path(self, detector, mock_path_analyzer):
        """Test module name when file is not relative to project."""
        test_file = Path("/other/location/file.py")
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = None
        mock_path_analyzer.stem_map[test_file] = "file"
        
        result = detector._get_module_name(str(test_file))
        assert result == "file"
    
    def test_get_module_name_exception(self, detector, mock_path_analyzer):
        """Test module name extraction handles exceptions."""
        mock_path_analyzer.relative_to = Mock(side_effect=Exception("Path error"))
        test_file = Path("/project/app/file.py")
        mock_path_analyzer.stem_map[test_file] = "file"
        
        result = detector._get_module_name(str(test_file))
        assert result == "file"
    
    def test_get_class_name_with_self(self, detector, mock_frame_inspector):
        """Test extracting class name from self."""
        mock_frame = Mock()
        mock_self = Mock()
        mock_self.__class__.__name__ = "TestClass"
        mock_frame_inspector.frame_locals = {"self": mock_self}
        
        result = detector._get_class_name(mock_frame)
        assert result == "TestClass"
    
    def test_get_class_name_with_cls(self, detector, mock_frame_inspector):
        """Test extracting class name from cls."""
        mock_frame = Mock()
        mock_cls = Mock()
        mock_cls.__name__ = "ClassMethod"
        mock_frame_inspector.frame_locals = {"cls": mock_cls}
        
        result = detector._get_class_name(mock_frame)
        assert result == "ClassMethod"
    
    def test_get_class_name_no_class(self, detector, mock_frame_inspector):
        """Test when no class in frame."""
        mock_frame = Mock()
        mock_frame_inspector.frame_locals = {"var": "value"}
        
        result = detector._get_class_name(mock_frame)
        assert result is None
    
    def test_get_class_name_exception(self, detector, mock_frame_inspector):
        """Test class name extraction handles exceptions."""
        mock_frame = Mock()
        mock_frame_inspector.get_frame_locals = Mock(side_effect=Exception("Locals error"))
        
        result = detector._get_class_name(mock_frame)
        assert result is None
    
    def test_determine_context_type_by_file_pattern(self, detector, mock_pattern_matcher):
        """Test context type determination by file pattern."""
        mock_pattern_matcher.file_matches[("test_file.py", r'test_.*\.py$')] = True
        
        context_type = detector._determine_context_type("test_file.py", "function", None)
        assert context_type == ContextType.TEST
    
    def test_determine_context_type_by_function_pattern(self, detector, mock_pattern_matcher):
        """Test context type determination by function pattern."""
        mock_pattern_matcher.function_matches[("test_something", r'^test_.*')] = True
        
        context_type = detector._determine_context_type("file.py", "test_something", None)
        assert context_type == ContextType.TEST
    
    def test_determine_context_type_by_class_pattern(self, detector, mock_pattern_matcher):
        """Test context type determination by class pattern."""
        mock_pattern_matcher.class_matches[("TestCase", r'^Test.*')] = True
        
        context_type = detector._determine_context_type("file.py", "method", "TestCase")
        assert context_type == ContextType.TEST
    
    def test_determine_context_type_default_with_class(self, detector, mock_pattern_matcher):
        """Test default context type when class present."""
        context_type = detector._determine_context_type("file.py", "method", "SomeClass")
        assert context_type == ContextType.CLASS_METHOD
    
    def test_determine_context_type_default_no_class(self, detector, mock_pattern_matcher):
        """Test default context type without class."""
        context_type = detector._determine_context_type("file.py", "function", None)
        assert context_type == ContextType.FUNCTION
    
    def test_get_app_name_valid(self, detector, mock_path_analyzer):
        """Test extracting valid app name."""
        test_file = Path("/project/myapp/models.py")
        rel_path = Path("myapp/models.py")
        
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = rel_path
        mock_path_analyzer.parts_map[rel_path] = ("myapp", "models.py")
        
        result = detector._get_app_name(str(test_file))
        assert result == "myapp"
    
    def test_get_app_name_skip_non_app_dirs(self, detector, mock_path_analyzer):
        """Test skipping non-app directories."""
        test_file = Path("/project/static/css/style.css")
        rel_path = Path("static/css/style.css")
        
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = rel_path
        mock_path_analyzer.parts_map[rel_path] = ("static", "css", "style.css")
        
        result = detector._get_app_name(str(test_file))
        assert result is None
    
    def test_get_app_name_no_relative_path(self, detector, mock_path_analyzer):
        """Test app name when no relative path."""
        test_file = Path("/other/file.py")
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = None
        
        result = detector._get_app_name(str(test_file))
        assert result is None
    
    def test_get_app_name_exception(self, detector, mock_path_analyzer):
        """Test app name extraction handles exceptions."""
        mock_path_analyzer.relative_to = Mock(side_effect=Exception("Path error"))
        
        result = detector._get_app_name("file.py")
        assert result is None
    
    def test_create_friendly_description_test(self, detector):
        """Test friendly description for test context."""
        context_info = ContextInfo(
            context_type=ContextType.TEST,
            module_name="tests.test_models",
            function_name="test_user_save",
            class_name="TestUserModel",
            app_name="myapp",
            test_method="test_user_save"
        )
        
        description = detector._create_friendly_description(context_info)
        assert "🧪 Test" in description
        assert "myapp" in description
        assert "TestUserModel" in description
        assert "test_user_save" in description
    
    def test_create_friendly_description_view(self, detector):
        """Test friendly description for view context."""
        context_info = ContextInfo(
            context_type=ContextType.VIEW,
            module_name="app.views",
            function_name="user_list",
            class_name="UserListView",
            app_name="users"
        )
        
        description = detector._create_friendly_description(context_info)
        assert "🌐 View" in description
        assert "users" in description
        assert "UserListView" in description
        assert "user_list" in description
    
    def test_create_friendly_description_no_app(self, detector):
        """Test friendly description without app name."""
        context_info = ContextInfo(
            context_type=ContextType.FUNCTION,
            module_name="utils",
            function_name="helper",
            class_name=None,
            app_name=None
        )
        
        description = detector._create_friendly_description(context_info)
        assert "⚡ Function" in description
        assert "helper" in description
    
    def test_create_friendly_description_all_types(self, detector):
        """Test friendly description for all context types."""
        # Test contexts not covered in other tests - they default to "❓ Unknown"
        test_cases = [
            (ContextType.API, "❓ Unknown"),
            (ContextType.CLI, "❓ Unknown"),
            (ContextType.SERVICE, "❓ Unknown"),
            (ContextType.WORKER, "❓ Unknown"),
            (ContextType.SCHEDULER, "❓ Unknown"),
        ]
        
        for context_type, expected_prefix in test_cases:
            context_info = ContextInfo(
                context_type=context_type,
                module_name="test",
                function_name="test_func"
            )
            description = detector._create_friendly_description(context_info)
            assert expected_prefix in description
            assert "test_func" in description  # Should include function name
    
    def test_create_unknown_context(self, detector):
        """Test creating unknown context."""
        context = detector._create_unknown_context()
        assert context.context_type == ContextType.UNKNOWN
        assert context.module_name == "unknown"
        assert context.function_name == "unknown"
        assert "❓ Unknown Context" in context.friendly_description
    
    def test_create_unknown_context_with_error(self, detector):
        """Test creating unknown context with error message."""
        context = detector._create_unknown_context("Test error")
        assert "Test error" in context.friendly_description
    
    def test_is_test_context_true(self, detector):
        """Test is_test_context returns True for test context."""
        test_context = ContextInfo(
            context_type=ContextType.TEST,
            module_name="test",
            function_name="test"
        )
        assert detector.is_test_context(test_context) is True
    
    def test_is_test_context_false(self, detector):
        """Test is_test_context returns False for non-test context."""
        view_context = ContextInfo(
            context_type=ContextType.VIEW,
            module_name="views",
            function_name="index"
        )
        assert detector.is_test_context(view_context) is False
    
    def test_is_test_context_detect(self, detector, mock_frame_inspector, mock_pattern_matcher):
        """Test is_test_context with auto-detection."""
        # Setup to detect test context
        mock_frame = Mock()
        mock_frame_inspector.current_frame = mock_frame
        mock_frame_inspector.parent_frame = mock_frame
        mock_frame_inspector.frame_info = ("test_file.py", "test_func", 10)
        mock_pattern_matcher.file_matches[("test_file.py", r'test_.*\.py$')] = True
        
        assert detector.is_test_context() is True
    
    def test_get_context_emoji(self, detector):
        """Test emoji mapping for context types."""
        # Test all defined context types
        assert detector.get_context_emoji(ContextType.TEST) == "🧪"
        assert detector.get_context_emoji(ContextType.VIEW) == "🌐"
        assert detector.get_context_emoji(ContextType.MODEL) == "📊"
        assert detector.get_context_emoji(ContextType.FORM) == "📝"
        assert detector.get_context_emoji(ContextType.SERIALIZER) == "🔄"
        assert detector.get_context_emoji(ContextType.MIDDLEWARE) == "🔗"
        assert detector.get_context_emoji(ContextType.MANAGEMENT_COMMAND) == "⚙️"
        assert detector.get_context_emoji(ContextType.CELERY_TASK) == "🔄"
        assert detector.get_context_emoji(ContextType.FUNCTION) == "⚡"
        assert detector.get_context_emoji(ContextType.CLASS_METHOD) == "🏗️"
        assert detector.get_context_emoji(ContextType.API) == "❓"  # Not in emoji_map
        assert detector.get_context_emoji(ContextType.CLI) == "❓"  # Not in emoji_map
        assert detector.get_context_emoji(ContextType.SERVICE) == "❓"  # Not in emoji_map
        assert detector.get_context_emoji(ContextType.WORKER) == "❓"  # Not in emoji_map
        assert detector.get_context_emoji(ContextType.SCHEDULER) == "❓"  # Not in emoji_map
        assert detector.get_context_emoji(ContextType.UNKNOWN) == "❓"
        # Test unknown enum value
        assert detector.get_context_emoji(None) == "❓"
    
    def test_all_context_patterns(self, detector, mock_pattern_matcher):
        """Test all context pattern types are handled."""
        # Test each context type pattern
        for context_type in [ContextType.FORM, ContextType.SERIALIZER, 
                           ContextType.MIDDLEWARE, ContextType.MANAGEMENT_COMMAND,
                           ContextType.CELERY_TASK]:
            patterns = detector.CONTEXT_PATTERNS.get(context_type, [])
            if patterns:
                mock_pattern_matcher.file_matches[(f"{context_type.value}.py", patterns[0])] = True
                result = detector._determine_context_type(f"{context_type.value}.py", "func", None)
                assert result == context_type
    
    def test_all_function_patterns(self, detector, mock_pattern_matcher):
        """Test all function pattern types."""
        # Test VIEW function patterns that might match first
        view_funcs = {"get": r'^get$', "post": r'^post$', "put": r'^put$', 
                     "patch": r'^patch$', "delete": r'^delete$', "dispatch": r'^dispatch$'}
        
        # Test MODEL function patterns
        model_funcs = {"save": r'^save$', "clean": r'^clean$', 
                      "get_absolute_url": r'^get_absolute_url$', 
                      "__str__": r'^__str__$', "__repr__": r'^__repr__$'}
        
        # First test VIEW patterns
        for func_name, pattern in view_funcs.items():
            mock_pattern_matcher.function_matches.clear()
            mock_pattern_matcher.function_matches[(func_name, pattern)] = True
            result = detector._determine_context_type("file.py", func_name, None)
            assert result == ContextType.VIEW
        
        # Then test MODEL patterns (delete is already tested above as VIEW)
        for func_name, pattern in model_funcs.items():
            mock_pattern_matcher.function_matches.clear()
            mock_pattern_matcher.function_matches[(func_name, pattern)] = True
            result = detector._determine_context_type("file.py", func_name, None)
            assert result == ContextType.MODEL
    
    def test_all_class_patterns(self, detector, mock_pattern_matcher):
        """Test all class pattern types."""
        # Test FORM class pattern
        mock_pattern_matcher.class_matches[("UserForm", r'^.*Form$')] = True
        result = detector._determine_context_type("file.py", "method", "UserForm")
        assert result == ContextType.FORM
        
        # Test SERIALIZER class pattern
        mock_pattern_matcher.class_matches[("UserSerializer", r'^.*Serializer$')] = True
        result = detector._determine_context_type("file.py", "method", "UserSerializer")
        assert result == ContextType.SERIALIZER
    
    def test_stack_depth_iteration(self, detector, mock_frame_inspector):
        """Test stack depth iteration."""
        # Create chain of frames
        frame1 = Mock()
        frame2 = Mock()
        frame3 = Mock()
        
        mock_frame_inspector.current_frame = frame1
        mock_frame_inspector.get_parent_frame = Mock(side_effect=[frame2, frame3, None])
        
        # Test with stack_depth=2
        detector.detect_context(stack_depth=2)
        assert mock_frame_inspector.get_parent_frame.call_count == 2
    
    def test_get_app_name_empty_parts(self, detector, mock_path_analyzer):
        """Test app name when relative path has no parts."""
        test_file = Path("/project/file.py")
        rel_path = Path(".")  # Empty parts
        
        mock_path_analyzer.relative_paths[(test_file, Path("/project"))] = rel_path
        mock_path_analyzer.parts_map[rel_path] = ()  # No parts
        
        result = detector._get_app_name(str(test_file))
        assert result is None
    
    def test_create_friendly_description_test_no_method(self, detector):
        """Test friendly description for test without test_method."""
        context_info = ContextInfo(
            context_type=ContextType.TEST,
            module_name="tests.test_models",
            function_name="setup",
            class_name="TestCase",
            test_method=None  # No test method
        )
        
        description = detector._create_friendly_description(context_info)
        assert "🧪 Test" in description
        # When test_method is None, it doesn't add function name for TEST context type
        assert "TestCase" in description  # Should have class name


class TestModuleFunctions:
    """Test module-level functions."""
    
    def test_get_current_context(self, monkeypatch):
        """Test get_current_context function."""
        mock_context = ContextInfo(
            context_type=ContextType.FUNCTION,
            module_name="test",
            function_name="test"
        )
        
        mock_detector = Mock()
        mock_detector.detect_context.return_value = mock_context
        
        import storm_logger.context_detector as cd
        monkeypatch.setattr(cd, 'context_detector', mock_detector)
        
        result = get_current_context()
        assert result == mock_context
        mock_detector.detect_context.assert_called_once_with(stack_depth=3)
    
    def test_is_test_context_function(self, monkeypatch):
        """Test is_test_context function."""
        mock_detector = Mock()
        mock_detector.is_test_context.return_value = True
        
        import storm_logger.context_detector as cd
        monkeypatch.setattr(cd, 'context_detector', mock_detector)
        
        result = is_test_context()
        assert result is True
        mock_detector.is_test_context.assert_called_once()
    
    def test_create_context_detector(self):
        """Test factory function for creating context detector."""
        # Create proper mock filesystem inspector with required methods
        mock_filesystem_inspector = MockFileSystemInspector()
        mock_frame_inspector = Mock()
        mock_path_analyzer = Mock()
        mock_pattern_matcher = Mock()
        
        detector = create_context_detector(
            framework="custom",
            frame_inspector=mock_frame_inspector,
            filesystem_inspector=mock_filesystem_inspector,
            path_analyzer=mock_path_analyzer,
            pattern_matcher=mock_pattern_matcher
        )
        
        assert isinstance(detector, ContextDetector)
        assert detector.framework == "custom"
        assert detector._frame_inspector is mock_frame_inspector
        assert detector._filesystem_inspector is mock_filesystem_inspector
        assert detector._path_analyzer is mock_path_analyzer
        assert detector._pattern_matcher is mock_pattern_matcher


class TestContextInfo:
    """Test ContextInfo dataclass."""
    
    def test_context_info_creation(self):
        """Test creating ContextInfo with all fields."""
        info = ContextInfo(
            context_type=ContextType.TEST,
            module_name="tests.test_models",
            function_name="test_save",
            class_name="TestModel",
            file_path="/project/tests/test_models.py",
            line_number=42,
            is_test=True,
            test_class="TestModel",
            test_method="test_save",
            app_name="myapp",
            friendly_description="Test in myapp",
            framework="django"
        )
        
        assert info.context_type == ContextType.TEST
        assert info.module_name == "tests.test_models"
        assert info.function_name == "test_save"
        assert info.class_name == "TestModel"
        assert info.file_path == "/project/tests/test_models.py"
        assert info.line_number == 42
        assert info.is_test is True
        assert info.test_class == "TestModel"
        assert info.test_method == "test_save"
        assert info.app_name == "myapp"
        assert info.friendly_description == "Test in myapp"
        assert info.framework == "django"
    
    def test_context_info_defaults(self):
        """Test ContextInfo with minimal fields."""
        info = ContextInfo(
            context_type=ContextType.UNKNOWN,
            module_name="unknown",
            function_name="unknown"
        )
        
        assert info.class_name is None
        assert info.file_path is None
        assert info.line_number is None
        assert info.is_test is False
        assert info.test_class is None
        assert info.test_method is None
        assert info.app_name is None
        assert info.friendly_description is None
        assert info.framework is None


class TestContextType:
    """Test ContextType enum."""
    
    def test_all_context_types(self):
        """Test all context types are defined."""
        expected_types = [
            "TEST", "VIEW", "MODEL", "FORM", "SERIALIZER",
            "MIDDLEWARE", "MANAGEMENT_COMMAND", "CELERY_TASK",
            "FUNCTION", "CLASS_METHOD", "API", "CLI",
            "SERVICE", "WORKER", "SCHEDULER", "UNKNOWN"
        ]
        
        actual_types = [ct.name for ct in ContextType]
        assert set(actual_types) == set(expected_types)
    
    def test_context_type_values(self):
        """Test context type string values."""
        assert ContextType.TEST.value == "test"
        assert ContextType.VIEW.value == "view"
        assert ContextType.MODEL.value == "model"
        assert ContextType.UNKNOWN.value == "unknown"


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