from abc import ABC
from enum import Enum
from typing import Any, Dict, Generic, TypeVar
import os

from bclearer_interop_services.file_system_service.objects.folders import \
    Folders
from bclearer_core.objects.registries.registries import Registries

TRegistry = \
    TypeVar('TRegistry', bound=Registries)


class Universes(ABC, Generic[TRegistry]):
    """
    Generic base class for Universe objects that manage collections of registries.
    
    A Universe is the top-level container in the hierarchy, managing multiple
    Registries (which in turn manage Registers). It also maintains a configuration
    registry for pipeline-level settings.
    
    Type Parameters:
        TRegistry: The type of registries this universe will contain.
        
    Attributes:
        _registries: Dictionary mapping enum keys to registry instances.
        _configuration_registry: Dictionary mapping enum keys to configuration values.
    """
    
    def __init__(
            self):
        super().__init__()

        self._registries: Dict[Enum, TRegistry] = \
            {}
        
        self._configuration_registry: Dict[Enum, Any] = \
            {}
        
    @property
    def registries(
            self) \
            -> Dict[Enum, TRegistry]:
        """
        Get all registries in this universe.
        
        :returns: Dictionary mapping enum keys to registry instances.
        """
        return \
            self._registries
    
    @property
    def configuration_registry(
            self) \
            -> Dict[Enum, Any]:
        """
        Get the configuration registry.
        
        :returns: Dictionary mapping enum keys to configuration values.
        """
        return \
            self._configuration_registry
    
    def get_registry(
            self,
            name: Enum) \
            -> TRegistry:
        """
        Retrieve a registry by its enum name.
        
        :param name: The enum key for the registry.
        :returns: The registry instance.
        :raises ValueError: If registry not found.
        """
        if name not in self._registries:
            raise \
                ValueError(
                    f"Registry '{name}' not found in {self.__class__.__name__}")
        
        return \
            self._registries[name]

    def set_registry(
            self,
            name: Enum,
            registry: TRegistry) \
            -> None:
        """
        Store a registry by its enum name.
        
        Also sets the registry as an attribute for direct access.
        
        :param name: The enum key for the registry.
        :param registry: The registry instance to store.
        """
        self._registries[name] = \
            registry
        
        # Also set as attribute for direct access (e.g., universe.sap_registry)
        setattr(
            self, 
            name.value.lower(), 
            registry)
    
    def has_registry(
            self,
            name: Enum) \
            -> bool:
        """
        Check if a registry exists by name.
        
        :param name: The enum key to check.
        :returns: True if registry exists, False otherwise.
        """
        return \
            name in self._registries
    
    def get_all_registry_names(
            self) \
            -> list[Enum]:
        """
        Get a list of all registry names in this universe.
        
        :returns: List of enum keys for all registries.
        """
        return \
            list(self._registries.keys())
    
    def get_configuration(
            self,
            name: Enum,
            default: Any = None) \
            -> Any:
        """
        Get a configuration value by name.
        
        :param name: The enum key for the configuration.
        :param default: Default value if configuration not found.
        :returns: The configuration value or default.
        """
        return \
            self._configuration_registry.get(
                name,
                default)
    
    def set_configuration(
            self,
            name: Enum,
            value: Any) \
            -> None:
        """
        Set a configuration value by name.
        
        :param name: The enum key for the configuration.
        :param value: The configuration value to store.
        """
        self._configuration_registry[name] = \
            value
    
    def has_configuration(
            self,
            name: Enum) \
            -> bool:
        """
        Check if a configuration exists by name.
        
        :param name: The enum key to check.
        :returns: True if configuration exists, False otherwise.
        """
        return \
            name in self._configuration_registry
    
    def export_to_disk(
            self,
            base_folder_path: str) \
            -> None:
        """
        Export all registries to disk.
        
        Default implementation creates a subfolder structure and delegates
        to each registry's export_to_disk method. Override this method if
        custom export logic is needed.
        
        :param base_folder_path: The base folder path for exports.
        """
        universe_name = \
            self.__class__.__name__.lower()
        
        base_folder = \
            Folders(
                absolute_path_string=base_folder_path)
        
        export_folder = \
            base_folder.absolute_path \
            / universe_name \
            / "registries"
        
        if not export_folder.exists():
            os.makedirs(
                export_folder,
                exist_ok=True)
        
        # Delegate to all registries
        for registry in self._registries.values():
            registry.export_to_disk(
                folder_path=str(export_folder))
    
    def log_universe_state(
            self) \
            -> None:
        """
        Log the state of all registries in this universe.
        
        Default implementation delegates to each registry's log_registry_state method.
        Override this method if custom logging behavior is needed.
        """
        from bclearer_core.configurations.datastructure.logging_inspection_level_b_enums import \
            LoggingInspectionLevelBEnums
        from bclearer_orchestration_services.reporting_service.reporters.inspection_message_logger import \
            log_inspection_message
        
        universe_name = \
            self.__class__.__name__
        
        log_inspection_message(
            logging_inspection_level_b_enum=LoggingInspectionLevelBEnums.INFO,
            message=f"Universe '{universe_name}' contains {len(self._registries)} registr(y/ies).")
        
        # Delegate to all registries
        for registry in self._registries.values():
            registry.log_registry_state()
