"""
Context Detection Module for Storm Logger
Automatically identifies the calling context (tests, views, models, functions)
"""

import inspect
import re
from pathlib import Path
from typing import Optional, Dict, Any
from dataclasses import dataclass
from enum import Enum


class ContextType(Enum):
    """Types of code contexts the logger can detect."""
    TEST = "test"
    VIEW = "view"
    MODEL = "model"
    FORM = "form"
    SERIALIZER = "serializer"
    MIDDLEWARE = "middleware"
    MANAGEMENT_COMMAND = "management_command"
    CELERY_TASK = "celery_task"
    FUNCTION = "function"
    CLASS_METHOD = "class_method"
    API = "api"
    CLI = "cli"
    SERVICE = "service"
    WORKER = "worker"
    SCHEDULER = "scheduler"
    UNKNOWN = "unknown"


@dataclass
class ContextInfo:
    """Information about the calling context."""
    context_type: ContextType
    module_name: str
    function_name: str
    class_name: Optional[str] = None
    file_path: Optional[str] = None
    line_number: Optional[int] = None
    is_test: bool = False
    test_class: Optional[str] = None
    test_method: Optional[str] = None
    app_name: Optional[str] = None
    friendly_description: Optional[str] = None
    framework: Optional[str] = None


class ContextDetector:
    """
    Intelligent context detection for Storm Logger.
    Automatically determines what type of code is calling the logger.
    """
    
    # Pattern mappings for different context types
    CONTEXT_PATTERNS = {
        ContextType.TEST: [
            r'test_.*\.py$',
            r'.*_test\.py$',
            r'.*tests\.py$',
            r'tests/.*\.py$',
            r'.*conftest\.py$',
        ],
        ContextType.VIEW: [
            r'.*views\.py$',
            r'.*view_.*\.py$',
            r'.*api\.py$',
            r'.*viewsets\.py$',
        ],
        ContextType.MODEL: [
            r'.*models\.py$',
            r'.*model_.*\.py$',
        ],
        ContextType.FORM: [
            r'.*forms\.py$',
            r'.*form_.*\.py$',
        ],
        ContextType.SERIALIZER: [
            r'.*serializers\.py$',
            r'.*serializer_.*\.py$',
        ],
        ContextType.MIDDLEWARE: [
            r'.*middleware\.py$',
            r'.*middleware_.*\.py$',
        ],
        ContextType.MANAGEMENT_COMMAND: [
            r'management/commands/.*\.py$',
        ],
        ContextType.CELERY_TASK: [
            r'.*tasks\.py$',
            r'.*task_.*\.py$',
            r'.*celery_.*\.py$',
        ],
    }
    
    # Function name patterns that indicate specific contexts
    FUNCTION_PATTERNS = {
        ContextType.TEST: [
            r'^test_.*',
            r'^.*_test$',
            r'^setup.*',
            r'^teardown.*',
        ],
        ContextType.VIEW: [
            r'^.*_view$',
            r'^get$',
            r'^post$',
            r'^put$',
            r'^patch$',
            r'^delete$',
            r'^dispatch$',
        ],
        ContextType.MODEL: [
            r'^save$',
            r'^delete$',
            r'^clean$',
            r'^get_absolute_url$',
            r'^__str__$',
            r'^__repr__$',
        ],
    }
    
    # Class name patterns
    CLASS_PATTERNS = {
        ContextType.TEST: [
            r'^Test.*',
            r'^.*Test$',
            r'^.*TestCase$',
        ],
        ContextType.VIEW: [
            r'^.*View$',
            r'^.*ViewSet$',
            r'^.*APIView$',
        ],
        ContextType.MODEL: [
            r'^.*Model$',
            r'.*(?<!Test)$',  # Models usually don't have specific suffixes
        ],
        ContextType.FORM: [
            r'^.*Form$',
        ],
        ContextType.SERIALIZER: [
            r'^.*Serializer$',
        ],
    }
    
    def __init__(self, framework: Optional[str] = None):
        """
        Initialize the context detector.
        
        Args:
            framework: Optional framework name for context-specific detection
        """
        self.framework = framework
        self._manual_context: Optional[Dict[str, Any]] = None
        self.project_root = self._find_project_root()
        
    def set_manual_context(self, context: Dict[str, Any]) -> None:
        """Set manual context override."""
        self._manual_context = context
        
    def clear_manual_context(self) -> None:
        """Clear manual context override."""
        self._manual_context = None
    
    def get_context(self, stack_depth: int = 3) -> str:
        """
        Get simple context string for logging.
        
        Args:
            stack_depth: How far up the stack to look
            
        Returns:
            Context string (e.g., 'django.view', 'app.service')
        """
        if self._manual_context:
            return self._manual_context.get('context', 'manual')
        
        try:
            context_info = self.detect_context(stack_depth)
            if self.framework:
                return f"{self.framework}.{context_info.context_type.value}"
            else:
                return f"app.{context_info.context_type.value}"
        except Exception:
            return "unknown"
    
    def _find_project_root(self) -> Path:
        """Find the Django project root directory."""
        current_dir = Path.cwd()
        
        # Look for manage.py or settings.py to identify project root
        for path in [current_dir] + list(current_dir.parents):
            if (path / "manage.py").exists() or (path / "settings.py").exists():
                return path
        
        return current_dir
    
    def detect_context(self, stack_depth: int = 2) -> ContextInfo:
        """
        Detect the calling context from the call stack.
        
        Args:
            stack_depth: How far up the stack to look (default 2 to skip this method)
        
        Returns:
            ContextInfo: Detailed information about the calling context
        """
        try:
            # Get the calling frame
            frame = inspect.currentframe()
            for _ in range(stack_depth):
                frame = frame.f_back
            
            if not frame:
                return self._create_unknown_context()
            
            # Extract frame information
            filename = frame.f_code.co_filename
            function_name = frame.f_code.co_name
            line_number = frame.f_lineno
            
            # Get module information
            module_name = self._get_module_name(filename)
            
            # Get class information if available
            class_name = self._get_class_name(frame)
            
            # Determine context type
            context_type = self._determine_context_type(filename, function_name, class_name)
            
            # Get app name
            app_name = self._get_app_name(filename)
            
            # Create context info
            context_info = ContextInfo(
                context_type=context_type,
                module_name=module_name,
                function_name=function_name,
                class_name=class_name,
                file_path=filename,
                line_number=line_number,
                app_name=app_name,
            )
            
            # Add test-specific information
            if context_type == ContextType.TEST:
                context_info.is_test = True
                context_info.test_class = class_name
                context_info.test_method = function_name
            
            # Add friendly description
            context_info.friendly_description = self._create_friendly_description(context_info)
            
            return context_info
            
        except Exception as e:
            # If anything goes wrong, return unknown context
            return self._create_unknown_context(str(e))
    
    def _get_module_name(self, filename: str) -> str:
        """Extract module name from filename."""
        try:
            # Convert to relative path from project root
            rel_path = Path(filename).relative_to(self.project_root)
            
            # Convert path to module name
            module_parts = rel_path.parts[:-1]  # Remove filename
            module_name = '.'.join(module_parts)
            
            # Add filename without extension
            file_stem = rel_path.stem
            if file_stem != '__init__':
                module_name = f"{module_name}.{file_stem}" if module_name else file_stem
            
            return module_name or 'unknown'
            
        except (ValueError, OSError):
            return Path(filename).stem
    
    def _get_class_name(self, frame) -> Optional[str]:
        """Extract class name from frame if available."""
        try:
            # Look for 'self' in local variables
            if 'self' in frame.f_locals:
                return frame.f_locals['self'].__class__.__name__
            
            # Look for 'cls' in local variables (class methods)
            if 'cls' in frame.f_locals:
                return frame.f_locals['cls'].__name__
            
            return None
            
        except Exception:
            return None
    
    def _determine_context_type(self, filename: str, function_name: str, class_name: Optional[str]) -> ContextType:
        """Determine the context type based on filename, function, and class patterns."""
        
        # Check file patterns first
        for context_type, patterns in self.CONTEXT_PATTERNS.items():
            for pattern in patterns:
                if re.search(pattern, filename):
                    return context_type
        
        # Check function patterns
        for context_type, patterns in self.FUNCTION_PATTERNS.items():
            for pattern in patterns:
                if re.match(pattern, function_name):
                    return context_type
        
        # Check class patterns
        if class_name:
            for context_type, patterns in self.CLASS_PATTERNS.items():
                for pattern in patterns:
                    if re.match(pattern, class_name):
                        return context_type
        
        # Default based on common patterns
        if class_name:
            return ContextType.CLASS_METHOD
        else:
            return ContextType.FUNCTION
    
    def _get_app_name(self, filename: str) -> Optional[str]:
        """Extract Django app name from filename."""
        try:
            rel_path = Path(filename).relative_to(self.project_root)
            
            # First part of path is usually the app name
            if rel_path.parts:
                potential_app = rel_path.parts[0]
                
                # Skip common non-app directories
                if potential_app not in ['manage.py', 'static', 'media', 'templates', 'storm_logger', '_core']:
                    return potential_app
            
            return None
            
        except (ValueError, OSError):
            return None
    
    def _create_friendly_description(self, context_info: ContextInfo) -> str:
        """Create a human-friendly description of the context."""
        
        context_descriptions = {
            ContextType.TEST: "🧪 Test",
            ContextType.VIEW: "🌐 View",
            ContextType.MODEL: "📊 Model",
            ContextType.FORM: "📝 Form",
            ContextType.SERIALIZER: "🔄 Serializer",
            ContextType.MIDDLEWARE: "🔗 Middleware",
            ContextType.MANAGEMENT_COMMAND: "⚙️ Management Command",
            ContextType.CELERY_TASK: "🔄 Background Task",
            ContextType.FUNCTION: "⚡ Function",
            ContextType.CLASS_METHOD: "🏗️ Class Method",
            ContextType.UNKNOWN: "❓ Unknown Context",
        }
        
        base_description = context_descriptions.get(context_info.context_type, "❓ Unknown")
        
        # Add specific details
        if context_info.app_name:
            base_description += f" in {context_info.app_name}"
        
        if context_info.class_name:
            base_description += f" ({context_info.class_name})"
        
        if context_info.context_type == ContextType.TEST:
            if context_info.test_method:
                base_description += f" → {context_info.test_method}"
        else:
            base_description += f" → {context_info.function_name}"
        
        return base_description
    
    def _create_unknown_context(self, error: str = None) -> ContextInfo:
        """Create a context info for unknown context."""
        return ContextInfo(
            context_type=ContextType.UNKNOWN,
            module_name="unknown",
            function_name="unknown",
            friendly_description="❓ Unknown Context" + (f" (Error: {error})" if error else ""),
        )
    
    def is_test_context(self, context_info: ContextInfo = None) -> bool:
        """Check if current context is a test."""
        if context_info is None:
            context_info = self.detect_context()
        
        return context_info.context_type == ContextType.TEST
    
    def get_context_emoji(self, context_type: ContextType) -> str:
        """Get emoji representation for context type."""
        emoji_map = {
            ContextType.TEST: "🧪",
            ContextType.VIEW: "🌐",
            ContextType.MODEL: "📊",
            ContextType.FORM: "📝",
            ContextType.SERIALIZER: "🔄",
            ContextType.MIDDLEWARE: "🔗",
            ContextType.MANAGEMENT_COMMAND: "⚙️",
            ContextType.CELERY_TASK: "🔄",
            ContextType.FUNCTION: "⚡",
            ContextType.CLASS_METHOD: "🏗️",
            ContextType.UNKNOWN: "❓",
        }
        
        return emoji_map.get(context_type, "❓")


# Global instance for easy access
context_detector = ContextDetector()


def get_current_context() -> ContextInfo:
    """Get current calling context information."""
    return context_detector.detect_context(stack_depth=3)


def is_test_context() -> bool:
    """Check if current context is a test."""
    return context_detector.is_test_context()


# Example usage and testing
if __name__ == "__main__":
    # Test the context detector
    detector = ContextDetector()
    
    print("Testing Context Detection:")
    print("=" * 50)
    
    # Test from different contexts
    context = detector.detect_context()
    print(f"Context Type: {context.context_type}")
    print(f"Module: {context.module_name}")
    print(f"Function: {context.function_name}")
    print(f"Class: {context.class_name}")
    print(f"App: {context.app_name}")
    print(f"Description: {context.friendly_description}")
    print(f"Is Test: {context.is_test}")
    
    # Test convenience functions
    print(f"\\nIs Test Context: {is_test_context()}")
    print(f"Current Context: {get_current_context().friendly_description}")