from flask_caching import Cache
from flask import current_app, g
from functools import wraps
from typing import Any, Optional, Callable, Union
import hashlib
import json
import sys
import pickle
from app.utils.helpers import get_logger

logger = get_logger("cache_service")


class CacheService:
    """Simple and robust caching service for Flask application"""

    cache = None

    @classmethod
    def init_app(cls, app):
        """Initialize caching with Flask app"""
        try:
            logger.debug(f"CACHE_TYPE {app.config.get('CACHE_TYPE')}")
            cache_config = {
                "CACHE_TYPE": app.config.get("CACHE_TYPE", "simple"),
                "CACHE_DEFAULT_TIMEOUT": app.config.get("CACHE_DEFAULT_TIMEOUT", 300),
                "CACHE_KEY_PREFIX": app.config.get("CACHE_KEY_PREFIX", "qpgs_api"),
            }

            if cache_config["CACHE_TYPE"] == "filesystem":
                cache_config.update(
                    {
                        "CACHE_DIR": app.config.get("CACHE_DIR", None),
                    }
                )

            # Add Redis configuration if using Redis
            if cache_config["CACHE_TYPE"] == "redis":
                cache_config.update(
                    {
                        "CACHE_REDIS_HOST": app.config.get(
                            "CACHE_REDIS_HOST", "localhost"
                        ),
                        "CACHE_REDIS_PORT": app.config.get("CACHE_REDIS_PORT", 6379),
                        "CACHE_REDIS_DB": app.config.get("CACHE_REDIS_DB", 0),
                        "CACHE_REDIS_PASSWORD": app.config.get("CACHE_REDIS_PASSWORD"),
                    }
                )

            cls.cache = Cache(app, config=cache_config)
            logger.info(f"Cache initialized with type: {cache_config['CACHE_TYPE']}")

        except Exception as e:
            logger.error(f"Failed to initialize cache: {str(e)}")
            # Fallback to simple cache
            cls.cache = Cache(app, config={"CACHE_TYPE": "simple"})
            logger.warning("Falling back to simple in-memory cache")

    @classmethod
    def get_cache_key(cls, prefix: str, *args, **kwargs) -> str:
        """Generate consistent cache key from parameters"""
        try:
            # Create a string representation of all parameters
            key_data = {
                "args": args,
                "kwargs": sorted(kwargs.items()) if kwargs else {},
            }

            # Create hash of the data for consistency
            key_string = json.dumps(key_data, sort_keys=True, default=str)
            key_hash = hashlib.md5(key_string.encode()).hexdigest()[:12]

            return f"{prefix}:{key_hash}"

        except Exception as e:
            logger.warning(f"Error generating cache key: {str(e)}")
            # Fallback to simple string concatenation
            return f"{prefix}:{hash(str(args) + str(kwargs))}"

    @classmethod
    def get(cls, key: str, default: Any = None) -> Any:
        """Get value from cache with error handling"""
        try:
            if cls.cache is None:
                return default
            return cls.cache.get(key) or default
        except Exception as e:
            logger.warning(f"Cache get error for key {key}: {str(e)}")
            return default

    @classmethod
    def set(cls, key: str, value: Any, timeout: Optional[int] = None) -> bool:
        """Set value in cache with error handling"""
        try:
            if cls.cache is None:
                return False
            return cls.cache.set(key, value, timeout=timeout)
        except Exception as e:
            logger.warning(f"Cache set error for key {key}: {str(e)}")
            return False

    @classmethod
    def delete(cls, key: str) -> bool:
        """Delete value from cache"""
        try:
            if cls.cache is None:
                return False
            return cls.cache.delete(key)
        except Exception as e:
            logger.warning(f"Cache delete error for key {key}: {str(e)}")
            return False

    @classmethod
    def clear(cls) -> bool:
        """Clear all cache"""
        try:
            if cls.cache is None:
                return False
            return cls.cache.clear()
        except Exception as e:
            logger.warning(f"Cache clear error: {str(e)}")
            return False

    @classmethod
    def get_stats(cls) -> dict:
        """Get cache statistics"""
        try:
            if cls.cache is None:
                return {"status": "disabled", "type": "none"}

            cache_type = current_app.config.get("CACHE_TYPE", "unknown")
            return {
                "status": "active",
                "type": cache_type,
                "backend": str(type(cls.cache.cache)),
            }
        except Exception as e:
            logger.warning(f"Error getting cache stats: {str(e)}")
            return {"status": "error", "error": str(e)}


def cached_query(
    cache_key_prefix: str, timeout: Optional[int] = None, condition: Callable = None
):
    """
    Decorator to cache function results

    Args:
        cache_key_prefix: Prefix for cache key
        timeout: Cache timeout in seconds (None = use default)
        condition: Function to determine if result should be cached
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Skip caching if cache is disabled or condition fails
            if CacheService.cache is None:
                return func(*args, **kwargs)

            if condition and not condition(*args, **kwargs):
                return func(*args, **kwargs)

            # Generate cache key
            cache_key = CacheService.get_cache_key(cache_key_prefix, *args, **kwargs)

            # Try to get from cache
            cached_result = CacheService.get(cache_key)
            if cached_result is not None:
                logger.debug(f"Cache hit for key: {cache_key}")
                return cached_result

            # Execute function and cache result
            try:
                result = func(*args, **kwargs)

                # Cache the result if it's not None/empty
                if result is not None:
                    CacheService.set(cache_key, result, timeout)
                    logger.debug(f"Cache set for key: {cache_key}")

                return result

            except Exception as e:
                logger.error(
                    f"Error executing cached function {func.__name__}: {str(e)}"
                )
                raise

        return wrapper

    return decorator


def cache_response(cache_key_prefix: str, timeout: Optional[int] = None):
    """
    Decorator specifically for caching API responses
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if CacheService.cache is None:
                return func(*args, **kwargs)

            # Include request parameters in cache key
            from flask import request

            request_params = {
                "args": dict(request.args),
                "json": request.get_json() if request.is_json else None,
            }

            cache_key = CacheService.get_cache_key(
                cache_key_prefix, func.__name__, request_params
            )
            logger.debug(f"Generated cache key: {cache_key}")

            # Try cache first
            cached_response = CacheService.get(cache_key)
            logger.debug(f"Cache response: {cached_response}")
            if cached_response is not None:
                logger.debug(f"API cache hit for: {cache_key}")
                return cached_response

            # Execute function and cache response
            try:
                response = func(*args, **kwargs)

                # Check if response is a Flask Response object or a dict/tuple
                should_cache = False
                if hasattr(response, "status_code"):
                    # Flask Response object
                    should_cache = response.status_code == 200
                    logger.debug(f"Flask Response status: {response.status_code}")
                elif isinstance(response, (dict, list)):
                    # Direct data return (successful)
                    should_cache = True
                    logger.debug(f"Direct data response received")
                elif isinstance(response, tuple) and len(response) >= 2:
                    # Tuple format (data, status_code)
                    should_cache = response[1] == 200
                    logger.debug(f"Tuple response status: {response[1]}")

                # Cache successful responses only
                if should_cache:
                    CacheService.set(cache_key, response, timeout)
                    logger.debug(f"API response cached: {cache_key}")

                return response

            except Exception as e:
                logger.error(f"Error in cached API function {func.__name__}: {str(e)}")
                raise

        return wrapper

    return decorator


def invalidate_cache_pattern(pattern: str):
    """Invalidate cache keys matching a pattern"""
    try:
        if CacheService.cache is None:
            return False

        # This is a simple implementation - for Redis you might use SCAN
        # For now, we'll just clear all cache
        logger.info(f"Invalidating cache pattern: {pattern}")
        return CacheService.clear()

    except Exception as e:
        logger.error(f"Error invalidating cache pattern {pattern}: {str(e)}")
        return False
