"""
This module defines the `CacheMiddleware` class, which provides simple in-memory caching
for the Haru web framework. Caching helps improve performance by storing and reusing responses
for specified routes, reducing the need to reprocess identical requests within a given time period.

Usage example:

.. code-block:: python

    app.add_middleware(CacheMiddleware(cache_time=60))  # Cache responses for 60 seconds

Note:
    This is a basic in-memory cache and is not intended for production use. For production environments,
    consider using a distributed cache such as Redis or Memcached.

Parameters:
    cache_time (int): The duration (in seconds) for which responses should be cached.
    cache_key_func (Callable[[Request], str]): A function to generate cache keys based on the request.
"""

from typing import Dict, Callable, Any
from haru.middleware import Middleware
from haru.request import Request
from haru.response import Response
import time
import hashlib

__all__ = ['CacheMiddleware']


class CacheMiddleware(Middleware):
    """
    Cache Middleware

    This middleware caches responses for specified routes to improve performance.
    It stores the response in memory for a specified duration, allowing faster responses
    for subsequent requests to the same route.

    :param cache_time: The duration in seconds for which the response should be cached.
    :type cache_time: int
    :param cache_key_func: A function that generates a cache key based on the request object.
                           If not provided, a default function will generate the key using the request path and query parameters.
    :type cache_key_func: Callable[[Request], str]
    """

    def __init__(self, cache_time: int = 60, cache_key_func: Callable[[Request], str] = None):
        """
        Initialize the `CacheMiddleware` with a cache duration and an optional cache key generator function.

        :param cache_time: The duration in seconds for which the response should be cached.
        :type cache_time: int
        :param cache_key_func: A function that generates cache keys based on the request. If not provided, a default key generator is used.
        :type cache_key_func: Callable[[Request], str]
        """
        super().__init__()
        self.cache_time = cache_time
        self.cache_key_func = cache_key_func or self.default_cache_key_func
        self.cache: Dict[str, Any] = {}

    def default_cache_key_func(self, request: Request) -> str:
        """
        Generate a default cache key based on the request path and query parameters.

        :param request: The current HTTP request object.
        :type request: Request
        :return: A hashed cache key.
        :rtype: str
        """
        key = request.path + '?' + request.query_string
        return hashlib.sha256(key.encode('utf-8')).hexdigest()

    def before_request(self, request: Request) -> None:
        """
        Check if a cached response exists for the current request.
        If a valid cached response is found, it is attached to the request object.

        :param request: The current HTTP request object.
        :type request: Request
        """
        cache_key = self.cache_key_func(request)
        cached = self.cache.get(cache_key)
        if cached and time.time() < cached['expires_at']:
            request.cached_response = cached['response']

    def after_request(self, request: Request, response: Response) -> Response:
        """
        Cache the response after the request has been processed, if no cached response was found.

        :param request: The current HTTP request object.
        :type request: Request
        :param response: The HTTP response object that was generated by the route handler.
        :type response: Response
        :return: The original or cached response.
        :rtype: Response
        """
        if hasattr(request, 'cached_response'):
            return request.cached_response
        else:
            cache_key = self.cache_key_func(request)
            self.cache[cache_key] = {
                'response': response,
                'expires_at': time.time() + self.cache_time
            }
            return response
