"""Azure App Configuration Handler Module.

This module provides a thread-safe singleton implementation for managing Azure App Configuration
settings. It offers lazy loading of configuration values with caching support.

The module exposes a Configurations class that acts as a facade for Azure App Configuration,
providing synchronous access to configuration settings with automatic environment-based filtering.

Example:
    Basic usage:
        config = Configurations.create()
        database_url = config.DATABASE_URL
        api_key = config.API_KEY
"""

import os
import logging
from typing import Optional, Dict
from threading import Lock
from azure.appconfiguration import AzureAppConfigurationClient
from azure.core.exceptions import ResourceNotFoundError

class Configurations:
    """Thread-safe singleton facade for Azure App Configuration.
    
    This class provides a synchronous interface to Azure App Configuration service
    with lazy loading, caching, and thread-safe singleton pattern implementation.
    
    The class automatically loads configuration settings based on the APPLICATION_ENVIRONMENT
    environment variable and caches them for subsequent access.
    
    Attributes:
        environment (str): The current application environment (e.g., 'dev', 'prod').
        connection_string (Optional[str]): Azure App Configuration connection string.
        
    Note:
        This class implements the singleton pattern and should be instantiated
        using the create() class method rather than direct instantiation.
        
    Example:
        config = Configurations.create()
        db_connection = config.DATABASE_CONNECTION_STRING
    """
    
    _instance: Optional["Configurations"] = None
    _lock = Lock()

    def __init__(self):
        """Initialize the Configurations instance.
        
        Sets up the environment, connection string, and internal state for configuration
        management. The actual configuration loading is deferred until first access.
        
        Raises:
            ValueError: If APP_CONFIGURATION_CONNECTION_STRING environment variable is not set.
            
        Note:
            This method should not be called directly. Use create() class method instead.
        """
        self.environment = os.getenv("APPLICATION_ENVIRONMENT", "dev")
        self.connection_string = os.getenv("APP_CONFIGURATION_CONNECTION_STRING")
        
        if not self.connection_string:
            raise ValueError("APP_CONFIGURATION_CONNECTION_STRING is not set.")
        
        self._configs: Dict[str, str] = {}
        self._loaded = False

    @classmethod
    def create(cls) -> "Configurations":
        """Create or return the singleton instance of Configurations.
        
        Thread-safe factory method that ensures only one instance of Configurations
        exists throughout the application lifetime. Uses double-checked locking
        pattern for optimal performance.
        
        Returns:
            Configurations: The singleton instance of the Configurations class.
            
        Example:
            config = Configurations.create()
            api_endpoint = config.API_ENDPOINT
        """
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = cls()
        return cls._instance

    def _load_configs(self) -> None:
        """Load configuration settings from Azure App Configuration service.
        
        Synchronously loads all configuration settings filtered by the current environment
        label. Settings are cached in memory and also set as instance attributes for
        convenient dot-notation access.
        
        This method uses double-checked locking to ensure configurations are loaded
        only once in a thread-safe manner.
        
        Raises:
            Exception: Any exception that occurs during Azure App Configuration client
                      initialization or configuration retrieval. The original exception
                      is logged and re-raised.
                      
        Note:
            This method is called automatically on first configuration access.
            It should not be called directly by client code.
        """
        if not self._loaded:
            with self._lock:
                if not self._loaded:
                    try:
                        client = AzureAppConfigurationClient.from_connection_string(
                            self.connection_string
                        )
                        settings = client.list_configuration_settings(
                            label_filter=self.environment
                        )
                        
                        for item in settings:
                            self._configs[item.key] = item.value
                            setattr(self, item.key, item.value)
                            
                        self._loaded = True
                        logging.info(f"Loaded Azure App Config successfully for environment: {self.environment}")
                        
                    except Exception as e:
                        logging.error(f"Error loading Azure App Config: {e}")
                        raise

    def __getattr__(self, name: str) -> str:
        """Provide lazy-loaded access to configuration values via attribute access.
        
        This method is called when an attribute is not found through normal lookup.
        It triggers configuration loading if not already loaded and returns the
        requested configuration value.
        
        Args:
            name (str): The name of the configuration key to retrieve.
            
        Returns:
            str: The configuration value associated with the given key.
            
        Raises:
            AttributeError: If the requested configuration key is not found in
                          the loaded configurations.
                          
        Example:
            config = Configurations.create()
            database_url = config.DATABASE_URL  # This calls __getattr__("DATABASE_URL")
        """
        if not self._loaded:
            self._load_configs()
            
        if name in self._configs:
            return self._configs[name]
            
        raise AttributeError(f"Configuration '{name}' not found")