"""
Advanced caching decorators for API views and functions.
"""

import hashlib
import json
import logging
import time
from collections.abc import Callable
from functools import wraps
from typing import Any

from django.http import HttpRequest, HttpResponseBase
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.vary import vary_on_headers
from rest_framework.response import Response as DRFResponse

from apps.core.services.enhanced_cache_service import EnhancedCacheService

logger = logging.getLogger(__name__)


def cache_api_response(
    timeout: int = 1800,
    vary_on: list[str] | None = None,
    key_prefix: str = "api",
    cache_backend: str = "api",
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """
    Advanced decorator for caching API responses with flexible configuration

    Args:
        timeout: Cache timeout in seconds
        vary_on: List of request attributes to vary cache on
        key_prefix: Prefix for cache keys
        cache_backend: Cache backend to use
    """

    def decorator(view_func):
        @wraps(view_func)
        def wrapper(self, request, *args, **kwargs):
            # Only cache GET requests
            if request.method != "GET":
                return view_func(self, request, *args, **kwargs)

            # Generate cache key based on view, user, and parameters
            cache_key_parts = [
                key_prefix,
                view_func.__name__,
                (str(getattr(request.user, "id", "anonymous")) if request.user.is_authenticated else "anonymous"),
                str(hash(frozenset(request.GET.items()))),
            ]

            # Add URL parameters to cache key
            if args:
                cache_key_parts.append(str(hash(args)))
            if kwargs:
                cache_key_parts.append(str(hash(frozenset(kwargs.items()))))

            # Add custom vary_on fields
            if vary_on:
                for field in vary_on:
                    value = getattr(request, field, "")
                    cache_key_parts.append(str(value))

            cache_key = ":".join(cache_key_parts)

            # Try to get from cache
            cache_service = EnhancedCacheService()
            cached_response = getattr(cache_service, cache_backend + "_cache").get(cache_key)

            if cached_response:
                logger.debug(
                    f"Cache hit for API view {view_func.__name__}",
                    extra={
                        "cache_key": cache_key,
                        "view": view_func.__name__,
                        "cache_hit": True,
                    },
                )

                # If cached data is our dict format for DRF responses, reconstruct it
                if isinstance(cached_response, dict) and "__drf_data__" in cached_response:
                    return DRFResponse(data=cached_response["__drf_data__"], status=cached_response.get("status", 200))

                return cached_response

            # Execute view and cache result
            start_time = time.time()
            response = view_func(self, request, *args, **kwargs)
            execution_time = time.time() - start_time

            # Only cache successful responses
            if hasattr(response, "status_code") and response.status_code == 200:
                # For DRF responses, we must cache the data, not the response object
                # because the response object is not rendered yet and cannot be pickled
                if hasattr(response, "data"):
                    cache_payload = {"__drf_data__": response.data, "status": response.status_code}
                else:
                    cache_payload = response

                getattr(cache_service, cache_backend + "_cache").set(cache_key, cache_payload, timeout)
                logger.debug(
                    f"Cached API response for view {view_func.__name__}",
                    extra={
                        "cache_key": cache_key,
                        "view": view_func.__name__,
                        "execution_time": execution_time,
                        "cache_timeout": timeout,
                        "cache_miss": True,
                    },
                )
            else:
                logger.debug(
                    f"Not caching response for view {view_func.__name__} (status: {
                        getattr(response, 'status_code', 'unknown')
                    })"
                )

            return response

        return wrapper

    return decorator


def cache_queryset_result(
    cache_key_template: str, timeout: int = 3600, cache_backend: str = "default"
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """
    Decorator for caching expensive queryset operations

    Args:
        cache_key_template: Template for cache key (can use format placeholders)
        timeout: Cache timeout in seconds
        cache_backend: Cache backend to use
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Generate cache key from template and arguments
            try:
                cache_key = cache_key_template.format(*args, **kwargs)
            except (KeyError, IndexError):
                # Fallback to simpler key generation
                cache_key = f"{cache_key_template}:{hash(str(args) + str(kwargs))}"

            cache_service = EnhancedCacheService()
            cached_result = getattr(cache_service, cache_backend + "_cache").get(cache_key)

            if cached_result is not None:
                logger.debug(f"Cache hit for queryset operation: {func.__name__}")
                return cached_result

            # Execute function and cache result
            start_time = time.time()
            result = func(*args, **kwargs)
            execution_time = time.time() - start_time

            getattr(cache_service, cache_backend + "_cache").set(cache_key, result, timeout)
            logger.debug(
                f"Cached queryset result for {func.__name__}",
                extra={
                    "cache_key": cache_key,
                    "function": func.__name__,
                    "execution_time": execution_time,
                    "cache_timeout": timeout,
                },
            )

            return result

        return wrapper

    return decorator


def invalidate_cache_on_save(
    model_name: str, cache_patterns: list[str] | None = None
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """
    Decorator to invalidate cache when model instances are saved

    Args:
        model_name: Name of the model
        cache_patterns: List of cache key patterns to invalidate
    """

    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            result = func(self, *args, **kwargs)

            # Invalidate related cache entries
            cache_service = EnhancedCacheService()

            # Standard model cache invalidation
            instance_id = getattr(self, "id", None)
            cache_service.invalidate_model_cache(model_name, instance_id)

            # Custom pattern invalidation
            if cache_patterns:
                for pattern in cache_patterns:
                    if instance_id:
                        pattern = pattern.format(model=model_name, id=instance_id)
                    cache_service.invalidate_pattern(pattern)

            logger.info(
                f"Invalidated cache for {model_name} after save",
                extra={
                    "model": model_name,
                    "instance_id": instance_id,
                    "patterns": cache_patterns,
                },
            )

            return result

        return wrapper

    return decorator


class CachedAPIViewMixin(View):
    """Mixin to add caching capabilities to API views"""

    cache_timeout = 1800  # 30 minutes default
    cache_vary_on = ["version"]
    cache_key_prefix = "api"
    cache_backend = "api"

    @method_decorator(vary_on_headers("User-Agent", "Accept-Language"))
    def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponseBase:
        """Add caching to the dispatch method"""
        if request.method == "GET" and getattr(self, "enable_caching", True):
            return self._cached_dispatch(request, *args, **kwargs)
        return super().dispatch(request, *args, **kwargs)

    def _cached_dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponseBase:
        """Handle cached dispatch for GET requests"""
        cache_key = self._generate_cache_key(request, *args, **kwargs)

        cache_service = EnhancedCacheService()
        cached_response = getattr(cache_service, self.cache_backend + "_cache").get(cache_key)

        if cached_response:
            logger.debug(f"Cache hit for view {self.__class__.__name__}")
            return cached_response

        # Execute view
        response = super().dispatch(request, *args, **kwargs)

        # Cache successful responses
        if hasattr(response, "status_code") and response.status_code == 200:
            getattr(cache_service, self.cache_backend + "_cache").set(cache_key, response, self.cache_timeout)
            logger.debug(f"Cached response for view {self.__class__.__name__}")

        return response

    def _generate_cache_key(self, request: HttpRequest, *args: Any, **kwargs: Any) -> str:
        """Generate cache key for the request"""
        key_parts = [
            self.cache_key_prefix,
            self.__class__.__name__,
            request.method,
            (str(getattr(request.user, "id", "anonymous")) if request.user.is_authenticated else "anonymous"),
            str(hash(frozenset(request.GET.items()))),
        ]

        if args:
            key_parts.append(str(hash(args)))
        if kwargs:
            key_parts.append(str(hash(frozenset(kwargs.items()))))

        # Add vary_on fields
        for field in self.cache_vary_on:
            value = getattr(request, field, "")
            key_parts.append(str(value))

        return ":".join(key_parts)


def smart_cache(
    timeout: int = 3600,
    key_func: Callable[..., str] | None = None,
    invalidate_on: list[str] | None = None,
    cache_backend: str = "default",
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """
    Smart caching decorator that adapts to function arguments

    Args:
        timeout: Cache timeout in seconds
        key_func: Custom function to generate cache key
        invalidate_on: List of signals to invalidate cache on
        cache_backend: Cache backend to use
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Generate cache key
            if key_func:
                cache_key = key_func(*args, **kwargs)
            else:
                # Default key generation
                key_data = {
                    "func": f"{func.__module__}.{func.__name__}",
                    "args": str(args),
                    "kwargs": json.dumps(kwargs, sort_keys=True, default=str),
                }
                key_string = json.dumps(key_data, sort_keys=True)
                cache_key = f"smart_cache:{hashlib.md5(key_string.encode()).hexdigest()}"

            # Try to get from cache
            cache_service = EnhancedCacheService()
            cached_result = getattr(cache_service, cache_backend + "_cache").get(cache_key)

            if cached_result is not None:
                logger.debug(f"Smart cache hit for {func.__name__}")
                return cached_result

            # Execute function
            start_time = time.time()
            result = func(*args, **kwargs)
            execution_time = time.time() - start_time

            # Cache result
            getattr(cache_service, cache_backend + "_cache").set(cache_key, result, timeout)

            logger.debug(
                f"Smart cached result for {func.__name__}",
                extra={
                    "cache_key": cache_key,
                    "execution_time": execution_time,
                    "function": func.__name__,
                },
            )

            return result

        # Add cache invalidation if specified
        if invalidate_on:
            wrapper._cache_invalidate_on = invalidate_on
            wrapper._cache_backend = cache_backend

        return wrapper

    return decorator


def conditional_cache(
    condition_func: Callable[..., bool], **cache_kwargs: Any
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """
    Cache decorator that only caches when condition is met

    Args:
        condition_func: Function that returns True if caching should be applied
        **cache_kwargs: Arguments passed to smart_cache
    """

    def decorator(func):
        cached_func = smart_cache(**cache_kwargs)(func)

        @wraps(func)
        def wrapper(*args, **kwargs):
            if condition_func(*args, **kwargs):
                return cached_func(*args, **kwargs)
            else:
                return func(*args, **kwargs)

        return wrapper

    return decorator


# Utility functions for cache key generation
def generate_user_cache_key(user: Any, suffix: str = "") -> str:
    """Generate cache key based on user"""
    user_id = user.id if user.is_authenticated else "anonymous"
    return f"user:{user_id}:{suffix}" if suffix else f"user:{user_id}"


def generate_model_cache_key(model_instance: Any, suffix: str = "") -> str:
    """Generate cache key based on model instance"""
    model_name = model_instance._meta.model_name
    instance_id = model_instance.id
    return f"{model_name}:{instance_id}:{suffix}" if suffix else f"{model_name}:{instance_id}"


def generate_queryset_cache_key(queryset: Any, params: dict[str, Any] | None = None) -> str:
    """Generate cache key for queryset with parameters"""
    model_name = queryset.model._meta.model_name
    params_str = json.dumps(params or {}, sort_keys=True, default=str)
    params_hash = hashlib.md5(params_str.encode()).hexdigest()
    return f"queryset:{model_name}:{params_hash}"
