"""Persistent counter utility using shelve-backed storage.

This module provides `PersistentCounter`, a small convenience wrapper
over Python's `shelve` module to persist simple key-count mappings
across Python sessions. It's intended as a lightweight helper; for
high-throughput use cases prefer an external key-value store.
"""

import importlib.util
import shelve
from typing import Any

# Prefer explicit availability check instead of broad try/except
if importlib.util.find_spec("ggblab.utils_julia") is not None:
    from .utils_julia import called_from_julia  # type: ignore
else:

    def called_from_julia(*args: Any, **kwargs: Any) -> bool:  # fallback
        return False


class PersistentCounter:
    """Persistent counter using shelve database.

    Manages persistent key-value storage with automatic counting across sessions.
    Provides a dict-like interface with iteration, containment checks, and indexing.

    Attributes:
        cache_path (str): Path to shelve database
        enabled (bool): Enable/disable persistence
        _db: shelve.DbfilenameShelf instance

    Examples:
        >>> counter = PersistentCounter('my_cache.db')
        >>> counter.increment(['item1', 'item2'])
        >>> 'item1' in counter
        True
        >>> counter['item1']
        1
        >>> for key in counter:
        ...     print(f"{key}: {counter[key]}")
    """

    def __init__(self, cache_path="persistent_counter.db", enabled=True):
        """Initialize the persistent counter.

        Args:
            cache_path (str): Path to shelve database. Default: 'persistent_counter.db'
            enabled (bool): Enable persistence. Default: True
        """
        self.cache_path = cache_path
        # Allow explicit environment override to disable persistence (useful
        # in embedded contexts where heuristics fail).
        import os

        if os.environ.get("GGBLAB_DISABLE_PERSISTENT_COUNTER") in ("1", "true", "True"):
            print(
                "Info: PersistentCounter disabled via GGBLAB_DISABLE_PERSISTENT_COUNTER env"
            )
            enabled = False

        # If running under Julia/PythonCall, disable shelve-backed persistence
        if enabled and called_from_julia():
            # Avoid cross-process shelve locks when invoked from Julia embedding
            print("Info: PersistentCounter disabled when called from Julia/PythonCall")
            enabled = False
        self.enabled = enabled
        # Delay opening the shelve DB until first use to avoid opening
        # it on import (which can lead to cross-thread close errors).
        # The DB will be opened lazily by `_ensure_open()` in accessor
        # methods.
        self._db = None
            # Note: For large-scale or cross-process counters consider using an
            # external key-value store in your deployment. This utility is a
            # lightweight shelve-backed convenience included in the core package.

    def _open(self):
        """Open the shelve database."""
        try:
            self._db = shelve.open(self.cache_path)
        except Exception as e:
            print(f"Warning: Could not open database at {self.cache_path}: {e}")
            self._db = None
            # Note: If the shelve backend is incompatible on your platform you may
            # see warnings; consider removing or recreating the cache file.

    def _ensure_open(self):
        """Ensure the shelve DB is opened (lazy open)."""
        if not self.enabled:
            return
        if self._db is None:
            self._open()

    def increment(self, keys):
        """Increment counts for given keys.

        Args:
            keys (set or iterable): Keys to increment
        """
        if not self.enabled:
            return
        self._ensure_open()
        if self._db is None:
            return

        for key in keys:
            if key:
                try:
                    count = self._db.get(key, 0)
                    self._db[key] = count + 1
                    self._db.sync()
                except Exception as e:
                    print(f"Warning: Could not increment key '{key}': {e}")
                # Note: This operation writes to a local shelve DB and may be
                # comparatively slow; for high-throughput scenarios use a dedicated
                # external service.

    def get_all(self):
        """Retrieve all stored key-value pairs.

        Returns:
            dict: Keys mapped to their counts
        """
        if not self.enabled:
            return {}
        self._ensure_open()
        if self._db is None:
            return {}

        try:
            return dict(self._db)
        except Exception as e:
            print(f"Warning: Could not retrieve stored data: {e}")
            return {}
            # Note: Return value is a snapshot of the shelve contents.

    def clear(self):
        """Clear all stored data."""
        if not self.enabled:
            return
        self._ensure_open()
        if self._db is None:
            return

        try:
            self._db.clear()
            self._db.sync()
        except Exception as e:
            print(f"Warning: Could not clear data: {e}")
            # Note: Clearing is irreversible for the current cache file.

    def close(self):
        """Close the database.

        Swallow `sqlite3.ProgrammingError` that arises when a sqlite-backed
        shelve is closed from a different thread than it was opened in. The
        error is non-actionable at this point; we prefer to avoid noisy
        exception tracebacks during interpreter shutdown or thread teardown.
        """
        if self._db is not None:
            try:
                self._db.close()
                self._db = None
            except Exception as e:
                try:
                    import sqlite3

                    if isinstance(e, sqlite3.ProgrammingError):
                        # Ignore cross-thread close error
                        return
                except Exception:
                    pass
                print(f"Warning: Could not close database: {e}")
            # Note: Always call `close()` before process exit to ensure data is flushed.

    def __contains__(self, key):
        """Check if a key exists in the counter.

        Args:
            key: Key to check

        Returns:
            bool: True if key exists, False otherwise
        """
        if not self.enabled:
            return False
        self._ensure_open()
        if self._db is None:
            return False
        try:
            return key in self._db
        except Exception:
            return False

    def __iter__(self):
        """Iterate over keys in the counter.

        Yields:
            Keys in the database
        """
        if not self.enabled:
            return iter([])
        self._ensure_open()
        if self._db is None:
            return iter([])
        try:
            return iter(self._db)
        except Exception:
            return iter([])

    def __len__(self):
        """Return the number of keys in the counter.

        Returns:
            int: Number of stored keys
        """
        if not self.enabled:
            return 0
        self._ensure_open()
        if self._db is None:
            return 0
        try:
            return len(self._db)
        except Exception:
            return 0

    def __getitem__(self, key):
        """Get the count for a key.

        Args:
            key: Key to retrieve

        Returns:
            int: Count for the key

        Raises:
            KeyError: If key does not exist
        """
        if not self.enabled:
            raise KeyError(key)
        self._ensure_open()
        if self._db is None:
            raise KeyError(key)
        try:
            return self._db[key]
        except KeyError:
            raise
        except Exception as e:
            raise KeyError(f"Error retrieving key '{key}': {e}")

    def __setitem__(self, key, value):
        """Set the count for a key.

        Args:
            key: Key to set
            value: Count value to set
        """
        if not self.enabled:
            return
        self._ensure_open()
        if self._db is None:
            return
        try:
            self._db[key] = value
            self._db.sync()
        except Exception as e:
            print(f"Warning: Could not set key '{key}': {e}")

    def __del__(self):
        # Ensure DB closed on object finalization; swallow sqlite3 thread
        # errors similarly to `close()`.
        try:
            self.close()
        except Exception:
            pass
