"""
Cache invalidation callbacks for FSM state transitions.

This module provides callback implementations that invalidate cache entries
when state transitions occur.
"""

import logging

from django.conf import settings

from ..callback_base import PostTransitionCallback, TransitionContext

logger = logging.getLogger(__name__)


class CacheInvalidationCallback(PostTransitionCallback):
    """
    Base cache invalidation callback for state transitions.

    Invalidates cache entries matching specified patterns when a state
    transition completes successfully.
    """

    name: str = "CacheInvalidationCallback"

    def __init__(
        self,
        patterns: list[str] | None = None,
        include_instance_patterns: bool = True,
        **kwargs,
    ):
        """
        Initialize the cache invalidation callback.

        Args:
            patterns: List of cache key patterns to invalidate.
            include_instance_patterns: Whether to auto-generate instance-specific patterns.
            **kwargs: Additional arguments passed to parent class.
        """
        super().__init__(**kwargs)
        self.patterns = patterns or []
        self.include_instance_patterns = include_instance_patterns

    def should_execute(self, context: TransitionContext) -> bool:
        """Check if cache invalidation is enabled."""
        callback_settings = getattr(settings, "STATE_MACHINE_CALLBACKS", {})
        if not callback_settings.get("cache_invalidation_enabled", True):
            logger.debug("Cache invalidation disabled via settings")
            return False
        return True

    def _get_cache_service(self):
        """Get the EnhancedCacheService instance."""
        try:
            from apps.core.services.enhanced_cache_service import EnhancedCacheService

            return EnhancedCacheService()
        except ImportError:
            logger.warning("EnhancedCacheService not available")
            return None

    def _get_instance_patterns(self, context: TransitionContext) -> list[str]:
        """Generate cache key patterns specific to the instance."""
        patterns = []
        model_name = context.model_name.lower()
        instance_id = context.instance.pk

        # Standard instance patterns
        patterns.append(f"*{model_name}:{instance_id}*")
        patterns.append(f"*{model_name}_{instance_id}*")
        patterns.append(f"*{model_name}*{instance_id}*")

        return patterns

    def _get_all_patterns(self, context: TransitionContext) -> set[str]:
        """Get all patterns to invalidate, including generated ones."""
        all_patterns = set(self.patterns)

        if self.include_instance_patterns:
            all_patterns.update(self._get_instance_patterns(context))

        # Substitute placeholders in patterns
        model_name = context.model_name.lower()
        instance_id = str(context.instance.pk)

        substituted = set()
        for pattern in all_patterns:
            substituted.add(pattern.replace("{id}", instance_id).replace("{model}", model_name))

        return substituted

    def execute(self, context: TransitionContext) -> bool:
        """Execute the cache invalidation."""
        cache_service = self._get_cache_service()
        if not cache_service:
            # Try using Django's default cache
            return self._fallback_invalidation(context)

        try:
            patterns = self._get_all_patterns(context)

            for pattern in patterns:
                try:
                    cache_service.invalidate_pattern(pattern)
                    logger.debug(f"Invalidated cache pattern: {pattern}")
                except Exception as e:
                    logger.warning(f"Failed to invalidate cache pattern {pattern}: {e}")

            logger.info(f"Cache invalidation completed for {context}: " f"{len(patterns)} patterns")
            return True

        except Exception as e:
            logger.exception(f"Failed to invalidate cache for {context}: {e}")
            return False

    def _fallback_invalidation(self, context: TransitionContext) -> bool:
        """Fallback cache invalidation using Django's cache framework."""
        try:

            patterns = self._get_all_patterns(context)

            # Django's default cache doesn't support pattern deletion
            # Log a warning and return True (don't fail the transition)
            logger.warning(
                f"EnhancedCacheService not available, skipping pattern " f"invalidation for {len(patterns)} patterns"
            )

            return True

        except Exception as e:
            logger.exception(f"Fallback cache invalidation failed: {e}")
            return False


class ModelCacheInvalidation(CacheInvalidationCallback):
    """
    Invalidates all cache keys for a specific model instance.

    Uses model-specific cache key patterns.
    """

    name: str = "ModelCacheInvalidation"

    # Default patterns by model type
    MODEL_PATTERNS = {
        "Park": ["*park:{id}*", "*parks*", "geo:*"],
        "Ride": ["*ride:{id}*", "*rides*", "*park:*", "geo:*"],
        "EditSubmission": ["*submission:{id}*", "*moderation*"],
        "PhotoSubmission": ["*photo:{id}*", "*moderation*"],
        "ModerationReport": ["*report:{id}*", "*moderation*"],
        "ModerationQueue": ["*queue*", "*moderation*"],
        "BulkOperation": ["*operation:{id}*", "*moderation*"],
    }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def _get_instance_patterns(self, context: TransitionContext) -> list[str]:
        """Get model-specific patterns."""
        base_patterns = super()._get_instance_patterns(context)

        # Add model-specific patterns
        model_name = context.model_name
        if model_name in self.MODEL_PATTERNS:
            model_patterns = self.MODEL_PATTERNS[model_name]
            # Substitute {id} placeholder
            instance_id = str(context.instance.pk)
            for pattern in model_patterns:
                base_patterns.append(pattern.replace("{id}", instance_id))

        return base_patterns


class RelatedModelCacheInvalidation(CacheInvalidationCallback):
    """
    Invalidates cache for related models when a transition occurs.

    Useful for maintaining cache consistency across model relationships.
    """

    name: str = "RelatedModelCacheInvalidation"

    def __init__(
        self,
        related_fields: list[str] | None = None,
        **kwargs,
    ):
        """
        Initialize related model cache invalidation.

        Args:
            related_fields: List of field names pointing to related models.
            **kwargs: Additional arguments.
        """
        super().__init__(**kwargs)
        self.related_fields = related_fields or []

    def _get_related_patterns(self, context: TransitionContext) -> list[str]:
        """Get cache patterns for related models."""
        patterns = []

        for field_name in self.related_fields:
            related_obj = getattr(context.instance, field_name, None)
            if related_obj is None:
                continue

            # Handle foreign key relationships
            if hasattr(related_obj, "pk"):
                related_model = type(related_obj).__name__.lower()
                related_id = related_obj.pk
                patterns.append(f"*{related_model}:{related_id}*")
                patterns.append(f"*{related_model}_{related_id}*")

            # Handle many-to-many relationships
            elif hasattr(related_obj, "all"):
                for obj in related_obj.all():
                    related_model = type(obj).__name__.lower()
                    related_id = obj.pk
                    patterns.append(f"*{related_model}:{related_id}*")

        return patterns

    def _get_all_patterns(self, context: TransitionContext) -> set[str]:
        """Get all patterns including related model patterns."""
        patterns = super()._get_all_patterns(context)
        patterns.update(self._get_related_patterns(context))
        return patterns


class PatternCacheInvalidation(CacheInvalidationCallback):
    """
    Invalidates cache keys matching specific patterns.

    Provides fine-grained control over which cache keys are invalidated.
    """

    name: str = "PatternCacheInvalidation"

    def __init__(
        self,
        patterns: list[str],
        include_instance_patterns: bool = False,
        **kwargs,
    ):
        """
        Initialize pattern-based cache invalidation.

        Args:
            patterns: List of exact patterns to invalidate.
            include_instance_patterns: Whether to include auto-generated patterns.
            **kwargs: Additional arguments.
        """
        super().__init__(
            patterns=patterns,
            include_instance_patterns=include_instance_patterns,
            **kwargs,
        )


class APICacheInvalidation(CacheInvalidationCallback):
    """
    Invalidates API response cache entries.

    Specialized for API endpoint caching.
    """

    name: str = "APICacheInvalidation"

    def __init__(
        self,
        api_prefixes: list[str] | None = None,
        include_geo_cache: bool = False,
        **kwargs,
    ):
        """
        Initialize API cache invalidation.

        Args:
            api_prefixes: List of API cache prefixes (e.g., ['api:parks', 'api:rides']).
            include_geo_cache: Whether to invalidate geo/map cache entries.
            **kwargs: Additional arguments.
        """
        super().__init__(**kwargs)
        self.api_prefixes = api_prefixes or ["api:*"]
        self.include_geo_cache = include_geo_cache

    def _get_all_patterns(self, context: TransitionContext) -> set[str]:
        """Get API-specific cache patterns."""
        patterns = set()

        # Add API patterns
        for prefix in self.api_prefixes:
            patterns.add(prefix)

        # Add geo cache if requested
        if self.include_geo_cache:
            patterns.add("geo:*")
            patterns.add("map:*")

        # Add model-specific API patterns
        model_name = context.model_name.lower()
        instance_id = str(context.instance.pk)
        patterns.add(f"api:{model_name}:{instance_id}*")
        patterns.add(f"api:{model_name}s*")

        return patterns


# Pre-configured cache invalidation callbacks for common models


class ParkCacheInvalidation(CacheInvalidationCallback):
    """Cache invalidation for Park model transitions."""

    name: str = "ParkCacheInvalidation"

    def __init__(self, **kwargs):
        super().__init__(
            patterns=[
                "*park:{id}*",
                "*parks*",
                "api:*",
                "geo:*",
            ],
            **kwargs,
        )


class RideCacheInvalidation(CacheInvalidationCallback):
    """Cache invalidation for Ride model transitions."""

    name: str = "RideCacheInvalidation"

    def __init__(self, **kwargs):
        super().__init__(
            patterns=[
                "*ride:{id}*",
                "*rides*",
                "api:*",
                "geo:*",
            ],
            **kwargs,
        )

    def _get_instance_patterns(self, context: TransitionContext) -> list[str]:
        """Include parent park cache patterns."""
        patterns = super()._get_instance_patterns(context)

        # Invalidate parent park's cache
        park = getattr(context.instance, "park", None)
        if park:
            park_id = park.pk if hasattr(park, "pk") else park
            patterns.append(f"*park:{park_id}*")
            patterns.append(f"*park_{park_id}*")

        return patterns


class ModerationCacheInvalidation(CacheInvalidationCallback):
    """Cache invalidation for moderation-related model transitions."""

    name: str = "ModerationCacheInvalidation"

    def __init__(self, **kwargs):
        super().__init__(
            patterns=[
                "*submission*",
                "*moderation*",
                "api:moderation*",
            ],
            **kwargs,
        )
