import time
from threading import RLock
from collections.abc import MutableMapping
from typing import Any

from sibi_flux.logger import Logger


class FsRegistry:
    """
    Thread-safe registry and cache for filesystem instances.
    Provides TTL-based invalidation and integrates fsspec cache clearing.
    """

    def __init__(
        self,
        logger: "Logger | None" = None,
        ttl_seconds: int = 3600,
        debug: bool = False,
    ):
        self._storage_registry: MutableMapping[str, Any] = {}
        self._fs_instance_cache: MutableMapping[str, tuple] = (
            {}
        )  # {name: (fs, timestamp)}
        self._lock = RLock()
        self.ttl_seconds = ttl_seconds

        self.logger = logger or Logger.default_logger(logger_name="FsRegistry")
        self.logger.set_level(Logger.DEBUG if debug else Logger.INFO)

    # ----------------------------------------------------------------------
    # Registration
    # ----------------------------------------------------------------------
    def register(self, name: str, manager: Any):
        """
        Register a storage manager that provides get_fs_instance().
        """
        if not hasattr(manager, "get_fs_instance"):
            raise TypeError("Manager must have a 'get_fs_instance' method.")
        with self._lock:
            self._storage_registry[name] = manager
            self.logger.debug(f"Registered storage '{name}'")

    # ----------------------------------------------------------------------
    # Retrieval
    # ----------------------------------------------------------------------
    def get_fs_instance(self, name: str = "source", reload: bool = False):
        """
        Retrieve a filesystem instance by name.
        - Uses cache unless TTL expired or reload=True.
        - Automatically clears fsspec cache to avoid FileExpired errors.
        """
        now = time.time()
        with self._lock:
            cached = self._fs_instance_cache.get(name)

            # TTL check
            if cached and not reload and (now - cached[1]) < self.ttl_seconds:
                fs = cached[0]
                return fs

            manager = self._storage_registry.get(name)
            if not manager:
                raise ValueError(
                    f"Storage '{name}' not registered. "
                    f"Available: {list(self._storage_registry.keys())}"
                )

            fs = manager.get_fs_instance()

            # Invalidate fsspec internal cache
            if hasattr(fs, "invalidate_cache"):
                try:
                    fs.invalidate_cache()
                    self.logger.debug(f"Cleared fsspec cache for '{name}'")
                except Exception as e:
                    self.logger.warning(f"Failed to invalidate cache for '{name}': {e}")

            self._fs_instance_cache[name] = (fs, now)
            self.logger.debug(f"Refreshed fs instance for '{name}'")
            return fs

    # ----------------------------------------------------------------------
    # Cache control
    # ----------------------------------------------------------------------
    def invalidate_fs(self, name: str):
        """
        Invalidate and remove cached fs instance for a given name.
        """
        with self._lock:
            if name in self._fs_instance_cache:
                del self._fs_instance_cache[name]
                self.logger.debug(f"Invalidated fs cache for '{name}'")
            else:
                self.logger.debug(f"No cached fs found for '{name}'")

    def clear_fs_cache(self):
        """
        Clear all cached filesystem instances.
        """
        with self._lock:
            self._fs_instance_cache.clear()
            self.logger.debug("Cleared all filesystem caches")

    def unregister_fs(self, name: str):
        """
        Unregister a storage configuration and clear its cached instance.
        """
        with self._lock:
            self._storage_registry.pop(name, None)
            self._fs_instance_cache.pop(name, None)
            self.logger.debug(f"Unregistered storage '{name}' and cleared cache")

    def flatten(self, cache_dict: MutableMapping[str, Any]) -> None:
        """
        Populate the provided dictionary with flattened paths from all registered managers.
        Enables concise imports like 'from my_app.conf.storage import mobile_storage'.
        """
        with self._lock:
            for name, manager in self._storage_registry.items():
                if not hasattr(manager, "_depot_config"):
                    continue

                # Access depot config
                depots = getattr(manager, "_depot_config", {})
                for depot, subdirs in depots.items():
                    # Alias the depot (e.g. logistics_storage)
                    cache_dict[depot] = manager.join_paths(manager.storage_path, depot)
                    
                    # Alias subdirectories (e.g. products -> products_storage)
                    for subdir in subdirs:
                        alias = f"{subdir}_storage"
                        # Use accessor if available, or construct path
                        if hasattr(manager, "depot_names"):
                            try:
                                accessor = getattr(getattr(manager.depot_names, depot), subdir)
                                cache_dict[alias] = accessor
                            except AttributeError:
                                cache_dict[alias] = manager.join_paths(manager.storage_path, depot, subdir)
                        else:
                            cache_dict[alias] = manager.join_paths(manager.storage_path, depot, subdir)
            
            self.logger.debug(f"Flattened registry into cache. Keys: {list(cache_dict.keys())}")
