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

from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any

from .context_implementations import (
    DefaultFileSystemInspector,
    DefaultFrameInspector,
    DefaultPathAnalyzer,
    DefaultPatternMatcher,
)
from .context_interfaces import FileSystemInspector, FrameInspector, PathAnalyzer, PatternMatcher


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: str | None = None
    file_path: str | None = None
    line_number: int | None = None
    is_test: bool = False
    test_class: str | None = None
    test_method: str | None = None
    app_name: str | None = None
    friendly_description: str | None = None
    framework: str | None = 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: str | None = None,
        frame_inspector: FrameInspector | None = None,
        filesystem_inspector: FileSystemInspector | None = None,
        path_analyzer: PathAnalyzer | None = None,
        pattern_matcher: PatternMatcher | None = None,
    ):
        """
        Initialize the context detector.

        Args:
            framework: Optional framework name for context-specific detection
            frame_inspector: Frame inspection implementation
            filesystem_inspector: File system operations implementation
            path_analyzer: Path analysis implementation
            pattern_matcher: Pattern matching implementation
        """
        self.framework = framework
        self._manual_context: dict[str, Any] | None = None

        # Use injected dependencies or create defaults
        self._frame_inspector = frame_inspector or DefaultFrameInspector()
        self._filesystem_inspector = filesystem_inspector or DefaultFileSystemInspector()
        self._path_analyzer = path_analyzer or DefaultPathAnalyzer()
        self._pattern_matcher = pattern_matcher or DefaultPatternMatcher()

        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 str(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 = self._filesystem_inspector.get_current_directory()

        # Look for manage.py or settings.py to identify project root
        for path in [current_dir] + self._filesystem_inspector.get_parents(current_dir):
            if self._filesystem_inspector.exists(
                path / "manage.py"
            ) or self._filesystem_inspector.exists(path / "settings.py"):
                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 = self._frame_inspector.get_current_frame()
            for _ in range(stack_depth):
                frame = self._frame_inspector.get_parent_frame(frame)

            if not frame:
                return self._create_unknown_context()

            # Extract frame information
            filename, function_name, line_number = self._frame_inspector.get_frame_info(frame)

            # 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)

        except Exception as e:
            # If anything goes wrong, return unknown context
            return self._create_unknown_context(str(e))
        else:
            return context_info

    def _get_module_name(self, filename: str) -> str:
        """Extract module name from filename."""
        try:
            # Convert to relative path from project root
            file_path = Path(filename)
            rel_path = self._path_analyzer.relative_to(file_path, self.project_root)

            if rel_path is None:
                return self._path_analyzer.get_stem(file_path)

            # Convert path to module name
            parts = self._path_analyzer.get_parts(rel_path)
            module_parts = parts[:-1]  # Remove filename
            module_name = ".".join(module_parts)

            # Add filename without extension
            file_stem = self._path_analyzer.get_stem(rel_path)
            if file_stem != "__init__":
                module_name = f"{module_name}.{file_stem}" if module_name else file_stem

        except Exception:
            return self._path_analyzer.get_stem(Path(filename))
        else:
            return module_name or "unknown"

    def _get_class_name(self, frame: Any) -> str | None:
        """Extract class name from frame if available."""
        try:
            locals_dict = self._frame_inspector.get_frame_locals(frame)

            # Look for 'self' in local variables
            if "self" in locals_dict:
                return str(locals_dict["self"].__class__.__name__)

            # Look for 'cls' in local variables (class methods)
            if "cls" in locals_dict:
                return str(locals_dict["cls"].__name__)

        except Exception:
            return None
        else:
            return None

    def _determine_context_type(
        self, filename: str, function_name: str, class_name: str | None
    ) -> 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 self._pattern_matcher.match_file_pattern(filename, pattern):
                    return context_type

        # Check function patterns
        for context_type, patterns in self.FUNCTION_PATTERNS.items():
            for pattern in patterns:
                if self._pattern_matcher.match_function_pattern(function_name, pattern):
                    return context_type

        # Check class patterns
        if class_name:
            for context_type, patterns in self.CLASS_PATTERNS.items():
                for pattern in patterns:
                    if self._pattern_matcher.match_class_pattern(class_name, pattern):
                        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) -> str | None:
        """Extract Django app name from filename."""
        try:
            file_path = Path(filename)
            rel_path = self._path_analyzer.relative_to(file_path, self.project_root)

            if rel_path is None:
                return None

            # First part of path is usually the app name
            parts = self._path_analyzer.get_parts(rel_path)
            if parts:
                potential_app = parts[0]

                # Skip common non-app directories
                if potential_app not in [
                    "manage.py",
                    "static",
                    "media",
                    "templates",
                    "storm_logger",
                    "_core",
                ]:
                    return potential_app

        except Exception:
            return None
        else:
            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 = 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 = 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 (maintains backward compatibility)
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()


def create_context_detector(
    framework: str | None = None,
    frame_inspector: FrameInspector | None = None,
    filesystem_inspector: FileSystemInspector | None = None,
    path_analyzer: PathAnalyzer | None = None,
    pattern_matcher: PatternMatcher | None = None,
) -> ContextDetector:
    """
    Factory function to create a ContextDetector with custom dependencies.

    Args:
        framework: Optional framework name
        frame_inspector: Custom frame inspector
        filesystem_inspector: Custom filesystem inspector
        path_analyzer: Custom path analyzer
        pattern_matcher: Custom pattern matcher

    Returns:
        Configured ContextDetector instance
    """
    return ContextDetector(
        framework=framework,
        frame_inspector=frame_inspector,
        filesystem_inspector=filesystem_inspector,
        path_analyzer=path_analyzer,
        pattern_matcher=pattern_matcher,
    )


# 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}")
