import typing

import chitragupt.core.logger as logger


class GuptCache:
    """
    A cache utility class for storing and retrieving key-value pairs with logging.

    This class provides methods to create, set, get, delete, and inspect cache entries.
    All operations are logged using the provided logger.

    Attributes
    ----------
    internal_cache : dict[str, Any] or None
        The internal cache storage.
    logger : logger.GuptLogger or None
        The logger instance used for logging cache operations.

    Examples
    --------
    >>> from chitragupt.core.logger import GuptLogger, LogType
    >>> my_logger = GuptLogger()
    >>> cache = GuptCache()
    >>> cache.init(my_logger)
    >>> cache.create("user_data")
    >>> cache.set("user_data", {"name": "Alice"})
    >>> print(cache.get("user_data"))
    {'name': 'Alice'}
    """

    def __init__(self) -> None:
        """
        Initializes the GuptCache instance without an internal cache or logger.

        To enable logging and caching, call the `init` method with a valid logger.
        """
        self.internal_cache: typing.Optional[typing.Dict[str, typing.Any]] = (
            None
        )
        self.logger: typing.Optional[logger.GuptLogger] = None

    def init(self, internal_logger: logger.GuptLogger) -> None:
        """
        Initializes the internal cache and logger.

        Parameters
        ----------
        internal_logger : logger.GuptLogger
            An instance of GuptLogger to handle logging.

        Returns
        -------
        None

        Examples
        --------
        >>> my_logger = GuptLogger()
        >>> cache = GuptCache()
        >>> cache.init(my_logger)
        """
        self.internal_cache = {}
        self.logger = internal_logger
        self.logger.log(
            "Cache logger and cache initialized.", logger.LogType.DEBUG
        )

    def create(self, key: str) -> None:
        """
        Creates an empty dictionary for a specific key in the internal cache.

        Parameters
        ----------
        key : str
            The key to create in the internal cache.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.
        KeyError
            If the key already exists in the internal cache.

        Returns
        -------
        None

        Examples
        --------
        >>> cache.create("session")
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to create key but cache is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        if key in self.internal_cache:
            if self.logger:
                self.logger.log(
                    f"Attempted to create key '{key}' which already exists.",
                    logger.LogType.ERROR,
                )
            raise KeyError(f"Key '{key}' already exists in the internal_cache")
        self.internal_cache[key] = {}
        if self.logger:
            self.logger.log(
                f"Key '{key}' created in cache.", logger.LogType.INFO
            )

    def set(self, key: str, value: typing.Any) -> None:
        """
        Sets a value for a specific key in the internal cache.

        Parameters
        ----------
        key : str
            The key where the value should be stored.
        value : Any
            The value to store in the internal cache.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.

        Returns
        -------
        None

        Examples
        --------
        >>> cache.set("user", {"name": "Bob"})
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to set value but cache is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        if self.logger:
            self.logger.log(
                f"Setting value for key '{key}'.", logger.LogType.DEBUG
            )
        self.internal_cache[key] = value
        if self.logger:
            self.logger.log(f"Value set for key '{key}'.", logger.LogType.INFO)

    def get(self, key: str) -> typing.Union[typing.Any, None]:
        """
        Retrieves the value associated with a specific key in the internal cache.

        Parameters
        ----------
        key : str
            The key to look up in the internal cache.

        Returns
        -------
        Any or None
            The value if the key exists, otherwise None.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.

        Examples
        --------
        >>> value = cache.get("user")
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to get value but cache is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        if self.logger:
            self.logger.log(
                f"Retrieving value for key '{key}'.", logger.LogType.DEBUG
            )
        result = self.internal_cache.get(key)
        if self.logger:
            if result is None:
                self.logger.log(
                    f"Key '{key}' not found in cache.", logger.LogType.DEBUG
                )
            else:
                self.logger.log(
                    f"Key '{key}' found, returning value.",
                    logger.LogType.DEBUG,
                )
        return result

    def delete(self, key: str) -> None:
        """
        Deletes a key from the internal cache.

        Parameters
        ----------
        key : str
            The key to remove from the internal cache.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.
        KeyError
            If the key does not exist in the internal cache.

        Returns
        -------
        None

        Examples
        --------
        >>> cache.delete("session")
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to delete key but cache is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        if key not in self.internal_cache:
            if self.logger:
                self.logger.log(
                    f"Attempted to delete non-existent key '{key}'.",
                    logger.LogType.ERROR,
                )
            raise KeyError(f"Key '{key}' does not exist in the internal_cache")
        del self.internal_cache[key]
        if self.logger:
            self.logger.log(
                f"Key '{key}' deleted from cache.", logger.LogType.INFO
            )

    def clear(self) -> None:
        """
        Clears all entries in the internal cache.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.

        Returns
        -------
        None

        Examples
        --------
        >>> cache.clear()
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to clear cache but it is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        self.internal_cache.clear()
        if self.logger:
            self.logger.log("Cache cleared.", logger.LogType.INFO)

    def contains(self, key: str) -> bool:
        """
        Checks if a specific key exists in the internal cache.

        Parameters
        ----------
        key : str
            The key to check.

        Returns
        -------
        bool
            True if the key exists, False otherwise.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.

        Examples
        --------
        >>> exists = cache.contains("user")
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to check key existence but cache is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        result = key in self.internal_cache
        if self.logger:
            self.logger.log(
                f"Cache contains key '{key}': {result}.", logger.LogType.DEBUG
            )
        return result

    def size(self) -> int:
        """
        Gets the number of items in the internal cache.

        Returns
        -------
        int
            The number of items in the internal cache.

        Raises
        ------
        ValueError
            If the internal cache is not initialized.

        Examples
        --------
        >>> count = cache.size()
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to get cache size but cache is not initialized.",
                    logger.LogType.ERROR,
                )
            raise ValueError("Cache not initialized")
        size_value: int = len(self.internal_cache)
        if self.logger:
            self.logger.log(
                f"Cache size is {size_value}.", logger.LogType.DEBUG
            )
        return size_value

    def keys(self) -> typing.Optional[typing.List[str]]:
        """
        Retrieves all keys in the internal cache.

        Returns
        -------
        list of str or None
            A list of keys if the internal cache is initialized, otherwise None.

        Examples
        --------
        >>> key_list = cache.keys()
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to retrieve keys but cache is not initialized.",
                    logger.LogType.DEBUG,
                )
            return None
        keys_list: typing.List[str] = list(self.internal_cache.keys())
        if self.logger:
            self.logger.log(
                f"Retrieved keys: {keys_list}.", logger.LogType.DEBUG
            )
        return keys_list

    def values(self) -> typing.Optional[typing.List[typing.Any]]:
        """
        Retrieves all values in the internal cache.

        Returns
        -------
        list of Any or None
            A list of values if the internal cache is initialized, otherwise None.

        Examples
        --------
        >>> values_list = cache.values()
        """
        if self.internal_cache is None:
            if self.logger:
                self.logger.log(
                    "Attempted to retrieve values but cache is not initialized.",
                    logger.LogType.DEBUG,
                )
            return None
        values_list: typing.List[typing.Any] = list(
            self.internal_cache.values()
        )
        if self.logger:
            self.logger.log(
                "Retrieved values from cache.", logger.LogType.DEBUG
            )
        return values_list

    def is_initialized(self) -> bool:
        """
        Checks if the internal cache is initialized.

        Returns
        -------
        bool
            True if the internal cache is initialized, False otherwise.

        Examples
        --------
        >>> initialized = cache.is_initialized()
        """
        result: bool = self.internal_cache is not None
        if self.logger:
            self.logger.log(
                f"Cache initialization status: {result}.", logger.LogType.DEBUG
            )
        return result
