"""
Cache Integration Mixin for ModuleWrapper

Provides seamless integration between ModuleWrapper and ComponentCache,
enabling fast component retrieval without path-based reconstruction.

Usage:
    # The mixin is automatically included in ModuleWrapper
    wrapper = get_card_framework_wrapper()

    # Cache a pattern with resolved classes
    entry = wrapper.cache_pattern(
        key="my_card",
        component_paths=["Section", "DecoratedText"],
        instance_params={"text": "Hello"},
    )

    # Retrieve cached components
    Section = wrapper.get_cached_class("Section")
    entry = wrapper.get_cached_entry("my_card")
"""

import logging
from typing import Any, Dict, List, Optional, Type

from adapters.module_wrapper.strict import warn_strict
from adapters.module_wrapper.types import (
    CacheKey,
    ComponentPath,
    ComponentPaths,
    DSLNotation,
    Payload,
    SymbolMapping,
    WrapperGetter,
)

logger = logging.getLogger(__name__)


class CacheMixin:
    """
    Mixin that adds ComponentCache integration to ModuleWrapper.

    Expects the following attributes on self:
    - module_name: str
    - components: Dict[str, ModuleComponent]
    - get_component_by_path: Callable
    - symbol_mapping: Dict[str, str]
    - build_dsl_from_paths: Callable
    """

    # --- Mixin dependency contract ---
    _MIXIN_PROVIDES = frozenset(
        {
            "get_cached_class",
            "get_cached_classes",
            "get_cached_entry",
            "cache_pattern",
            "cache_from_qdrant_pattern",
            "invalidate_cache",
            "cache_stats",
            "_get_component_cache",
        }
    )
    _MIXIN_REQUIRES = frozenset(
        {
            "module_name",
            "components",
            "get_component_by_path",
            "symbol_mapping",
            "build_dsl_from_paths",
        }
    )
    _MIXIN_INIT_ORDER = 50

    _component_cache = None

    def _get_component_cache(self):
        """Get or create the ComponentCache instance."""
        if self._component_cache is None:
            from adapters.module_wrapper.component_cache import ComponentCache

            # Create cache with self as the wrapper getter
            self._component_cache = ComponentCache(
                memory_limit=100,
                cache_dir=".component_cache",
                wrapper_getter=lambda: self,
                auto_hydrate=True,
            )

            # Warm cache with commonly used components
            self._warm_component_cache()

        return self._component_cache

    def _warm_component_cache(self) -> int:
        """Pre-populate cache with frequently used components."""
        if not hasattr(self, "components"):
            return 0

        from adapters.module_wrapper.component_cache import CacheEntry

        cache = self._component_cache
        warmed = 0

        # Priority components (most commonly used in cards)
        priority_components = [
            "Section",
            "DecoratedText",
            "TextParagraph",
            "ButtonList",
            "Button",
            "Image",
            "Icon",
            "Card",
            "CardHeader",
            "OnClick",
            "OpenLink",
            "Color",
            "Divider",
            "Grid",
            "GridItem",
            "Columns",
            "Column",
            "Carousel",
            "CarouselCard",
        ]

        for name in priority_components:
            if name not in self.components:
                continue

            comp = self.components[name]
            path = comp.full_path or f"{self.module_name}.{name}"
            key = f"component:{name}"

            # Get actual class
            cls = self.get_component_by_path(path)
            if not cls:
                continue

            entry = CacheEntry(
                key=key,
                component_paths=[path],
                instance_params={},
                dsl_notation=self.symbol_mapping.get(name, ""),
            )
            entry.component_classes = {name: cls}
            entry._is_hydrated = True

            cache._l1.put(key, entry)
            warmed += 1

        logger.debug(f"Warmed component cache with {warmed} priority components")
        return warmed

    def cache_pattern(
        self,
        key: CacheKey,
        component_paths: ComponentPaths,
        instance_params: Optional[Payload] = None,
        resolve_classes: bool = True,
        dsl_notation: Optional[DSLNotation] = None,
        structure_description: Optional[str] = None,
    ) -> "CacheEntry":
        """
        Cache a pattern with its component classes.

        This method resolves component paths to actual classes and caches
        them for fast retrieval later.

        Args:
            key: Unique cache key (e.g., card_id, pattern hash)
            component_paths: List of component paths or names
            instance_params: Parameters used to instantiate the components
            resolve_classes: Whether to resolve paths to classes now
            dsl_notation: Optional DSL notation (auto-generated if not provided)
            structure_description: Optional human description of the structure

        Returns:
            CacheEntry with component_classes populated

        Example:
            entry = wrapper.cache_pattern(
                key="status_card_v1",
                component_paths=["Section", "DecoratedText", "ButtonList"],
                instance_params={"text": "Status: Online", "buttons": [...]},
                dsl_notation="§[δ, Ƀ]",
            )

            # Later retrieval
            Section = wrapper.get_cached_class("Section")
        """
        from adapters.module_wrapper.component_cache import CacheEntry

        cache = self._get_component_cache()

        # Build DSL notation if not provided
        if dsl_notation is None:
            try:
                dsl_notation = self.build_dsl_from_paths(component_paths)
            except Exception:
                pass

        # Resolve component classes
        classes = {}
        if resolve_classes:
            for path in component_paths:
                name = path.split(".")[-1] if "." in path else path

                # Resolve the full path
                full_path = self._resolve_component_path(path)
                if not full_path:
                    full_path = self._resolve_component_path(name)

                cls = None
                if full_path:
                    cls = self.get_component_by_path(full_path)

                if cls:
                    classes[name] = cls

        entry = CacheEntry(
            key=key,
            component_paths=component_paths,
            instance_params=instance_params or {},
            dsl_notation=dsl_notation,
            structure_description=structure_description,
        )
        entry.component_classes = classes
        entry._is_hydrated = bool(classes)

        cache._l1.put(key, entry)
        return entry

    def get_cached_entry(
        self,
        key: CacheKey,
        component_paths: Optional[ComponentPaths] = None,
    ) -> Optional["CacheEntry"]:
        """
        Get a cached pattern entry.

        Checks L1 (memory) → L2 (pickle) → L3 (reconstruction).

        Args:
            key: Cache key
            component_paths: Optional paths for L3 reconstruction

        Returns:
            CacheEntry with component_classes, or None
        """
        cache = self._get_component_cache()
        return cache.get(key, component_paths)

    def _resolve_component_path(self, component_name: str) -> Optional[ComponentPath]:
        """
        Resolve a component name to its full importable path.

        Args:
            component_name: Simple name like "Button" or full path

        Returns:
            Full importable path like "card_framework.v2.widgets.button.Button"
        """
        # If already a full path, return it
        if "." in component_name and component_name.count(".") >= 2:
            return component_name

        # Collect all matching paths, then prefer v2 over v1
        matching_paths = []

        # Search in components dict by name suffix
        for comp_key, comp in self.components.items():
            # Check if key ends with the component name
            if comp_key.endswith(f".{component_name}") or comp_key == component_name:
                full_path = comp.full_path
                if full_path:
                    matching_paths.append(full_path)

            # Also check comp.name
            elif hasattr(comp, "name") and comp.name == component_name:
                if comp.full_path:
                    matching_paths.append(comp.full_path)

        # Prefer v2 paths over v1 paths (e.g., card_framework.v2.message.Message over card_framework.Message)
        if matching_paths:
            v2_paths = [p for p in matching_paths if ".v2." in p]
            if v2_paths:
                return v2_paths[0]
            return matching_paths[0]

        # Try common patterns for card_framework
        common_patterns = [
            f"card_framework.v2.widgets.{component_name.lower()}.{component_name}",
            f"card_framework.v2.{component_name.lower()}.{component_name}",
            f"card_framework.v2.widgets.{component_name}",
            f"card_framework.v2.{component_name}",
        ]

        for pattern in common_patterns:
            cls = self.get_component_by_path(pattern)
            if cls:
                return pattern

        return None

    def get_cached_class(self, component_name: str) -> Optional[Type]:
        """
        Get a cached component class by name.

        Fast path for retrieving individual component classes.

        Args:
            component_name: Component name (e.g., "Button", "Section")

        Returns:
            Component class or None
        """
        cache = self._get_component_cache()
        key = f"component:{component_name}"

        entry = cache.get(key)
        if entry and entry.component_classes:
            return entry.component_classes.get(component_name)

        # Not in cache - resolve path and get class
        path = self._resolve_component_path(component_name)
        if not path:
            warn_strict(
                f"get_cached_class('{component_name}'): path resolution failed. "
                f"Component will be missing from the card."
            )
            logger.debug(f"Could not resolve path for: {component_name}")
            return None

        cls = self.get_component_by_path(path)
        if not cls:
            warn_strict(
                f"get_cached_class('{component_name}'): class lookup failed for path '{path}'. "
                f"Component will be missing from the card."
            )
            logger.debug(f"get_component_by_path returned None for: {path}")
            return None

        # Cache for future use
        from adapters.module_wrapper.component_cache import CacheEntry

        entry = CacheEntry(
            key=key,
            component_paths=[path],
            instance_params={},
            dsl_notation=self.symbol_mapping.get(component_name, ""),
        )
        entry.component_classes = {component_name: cls}
        entry._is_hydrated = True
        cache._l1.put(key, entry)

        return cls

    def get_cached_classes(
        self,
        component_names: List[str],
    ) -> Dict[str, Type]:
        """
        Get multiple cached component classes by name.

        Args:
            component_names: List of component names

        Returns:
            Dict mapping names to classes (only includes found classes)
        """
        result = {}
        for name in component_names:
            cls = self.get_cached_class(name)
            if cls:
                result[name] = cls
        return result

    def cache_from_qdrant_pattern(
        self,
        pattern: Dict[str, Any],
        key: Optional[str] = None,
    ) -> "CacheEntry":
        """
        Cache a pattern retrieved from Qdrant.

        Convenience method that extracts fields from a Qdrant pattern payload.

        Args:
            pattern: Pattern dict from Qdrant scroll/query
            key: Optional cache key (defaults to card_id or hash)

        Returns:
            CacheEntry with hydrated component classes
        """
        import hashlib

        # Determine key
        if not key:
            key = pattern.get("card_id") or pattern.get("id")
            if not key:
                desc = pattern.get("card_description", "")
                key = f"pattern:{hashlib.sha256(desc.encode()).hexdigest()[:12]}"

        # Extract paths (handle both field names)
        component_paths = pattern.get("component_paths") or pattern.get(
            "parent_paths", []
        )

        return self.cache_pattern(
            key=key,
            component_paths=component_paths,
            instance_params=pattern.get("instance_params", {}),
        )

    def invalidate_cache(self, key: Optional[str] = None) -> bool:
        """
        Invalidate cache entries.

        Args:
            key: Specific key to invalidate, or None for all

        Returns:
            True if something was invalidated
        """
        cache = self._get_component_cache()

        if key:
            return cache.remove(key)
        else:
            cleared = cache.clear()
            return cleared["l1"] > 0 or cleared["l2"] > 0

    @property
    def cache_stats(self) -> Dict[str, Any]:
        """Get cache statistics."""
        cache = self._get_component_cache()
        return cache.stats


# Import CacheEntry for type hints
try:
    from adapters.module_wrapper.component_cache import CacheEntry
except ImportError:
    CacheEntry = None
