"""
Settings Service Layer

This module manages application settings persistence and MongoDB connection configuration.
Settings are stored in app_config.yaml and can be updated through the UI.

Key principles:
- Explicit user control: No automatic reconnections or background operations
- Safe persistence: Settings are validated before saving
- Clear error handling: All errors are surfaced to the UI
"""

import os
import yaml
from pathlib import Path
from typing import Dict, Any, Optional, Tuple, List
from pymongo import MongoClient


class SettingsService:
    """
    Service for managing application settings and MongoDB configuration.
    
    This service handles:
    - Loading and saving settings to app_config.yaml
    - Testing MongoDB connections
    - Validating configuration values
    """
    
    def __init__(self, config_path: str = "config/app_config.yaml"):
        """
        Initialize settings service.
        
        Args:
            config_path: Path to YAML configuration file
        """
        self.config_path = Path(config_path)
        self.config = self._load_config()
    
    def _load_config(self) -> Dict[str, Any]:
        """
        Load configuration from YAML file.
        
        Returns:
            Configuration dictionary
        """
        if not self.config_path.exists():
            raise FileNotFoundError(f"Configuration file not found: {self.config_path}")
        
        with open(self.config_path, 'r') as f:
            return yaml.safe_load(f)
    
    def get_mongodb_settings(self) -> Dict[str, Any]:
        """
        Get current MongoDB settings.
        
        Returns:
            Dictionary with MongoDB configuration:
            - uri: Connection URI (from environment or default)
            - database: Database name
            - connection_timeout: Connection timeout in ms
            - server_selection_timeout: Server selection timeout in ms
        """
        mongo_config = self.config.get('mongodb', {})
        
        # Get URI from environment variable
        uri_env = mongo_config.get('uri_env', 'MONGO_URI')
        uri = os.getenv(uri_env, 'mongodb://AdminUser:WnE0TjGIafVJutreZL7cTIoWNWU52YyxLVgwCUqIGCGg3YUT@18.184.249.241:27017/?authMechanism=DEFAULT')
        
        return {
            'uri': uri,
            'uri_env': uri_env,
            'database': mongo_config.get('database', 'IGF_WORK'),
            'connection_timeout': mongo_config.get('connection_timeout', 5000),
            'server_selection_timeout': mongo_config.get('server_selection_timeout', 5000)
        }
    
    def test_connection(
        self,
        uri: str,
        database: str,
        connection_timeout: int = 5000,
        server_selection_timeout: int = 5000
    ) -> Tuple[bool, Optional[str]]:
        """
        Test MongoDB connection with provided settings.
        
        Args:
            uri: MongoDB connection URI
            database: Database name
            connection_timeout: Connection timeout in milliseconds
            server_selection_timeout: Server selection timeout in milliseconds
        
        Returns:
            Tuple of (success: bool, error_message: Optional[str])
        """
        client = None
        try:
            # Create test client
            client = MongoClient(
                uri,
                serverSelectionTimeoutMS=server_selection_timeout,
                connectTimeoutMS=connection_timeout
            )
            
            # Test connection
            client.admin.command('ping')
            
            # Test database access
            db = client[database]
            db.list_collection_names()
            
            return True, None
            
        except Exception as e:
            return False, str(e)
            
        finally:
            if client is not None:
                client.close()
    
    def save_mongodb_settings(
        self,
        uri: str,
        database: str,
        connection_timeout: int = 5000,
        server_selection_timeout: int = 5000
    ) -> Tuple[bool, Optional[str]]:
        """
        Save MongoDB settings to configuration file and environment.
        
        This method:
        1. Validates the settings
        2. Updates app_config.yaml
        3. Updates the environment variable for the current session
        
        Args:
            uri: MongoDB connection URI
            database: Database name
            connection_timeout: Connection timeout in milliseconds
            server_selection_timeout: Server selection timeout in milliseconds
        
        Returns:
            Tuple of (success: bool, error_message: Optional[str])
        """
        try:
            # Validate inputs
            if not uri or not uri.strip():
                return False, "MongoDB URI cannot be empty"
            
            if not database or not database.strip():
                return False, "Database name cannot be empty"
            
            if connection_timeout < 1000:
                return False, "Connection timeout must be at least 1000ms"
            
            if server_selection_timeout < 1000:
                return False, "Server selection timeout must be at least 1000ms"
            
            # Validate URI format (basic check)
            uri_stripped = uri.strip()
            if not uri_stripped.startswith('mongodb://') and not uri_stripped.startswith('mongodb+srv://'):
                return False, "MongoDB URI must start with 'mongodb://' or 'mongodb+srv://'"
            
            # Update configuration
            self.config['mongodb']['database'] = database.strip()
            self.config['mongodb']['connection_timeout'] = int(connection_timeout)
            self.config['mongodb']['server_selection_timeout'] = int(server_selection_timeout)
            
            # Save to file with proper error handling
            try:
                with open(self.config_path, 'w', encoding='utf-8') as f:
                    yaml.dump(self.config, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
            except PermissionError:
                return False, f"Permission denied: Cannot write to {self.config_path}. Check file permissions."
            except Exception as file_error:
                return False, f"Failed to write config file: {str(file_error)}"
            
            # Update environment variable for current session
            uri_env = self.config['mongodb'].get('uri_env', 'MONGO_URI')
            os.environ[uri_env] = uri_stripped
            
            return True, None
            
        except Exception as e:
            return False, f"Failed to save settings: {str(e)}"
    
    def get_database_name(self) -> str:
        """
        Get the configured database name.
        
        Returns:
            Database name
        """
        return self.config.get('mongodb', {}).get('database', 'IGF_WORK')
    
    def get_connection_uri(self) -> str:
        """
        Get the MongoDB connection URI from environment.
        
        Returns:
            Connection URI
        """
        uri_env = self.config.get('mongodb', {}).get('uri_env', 'MONGO_URI')
        return os.getenv(uri_env, 'mongodb://localhost:27017')
    
    def reload_config(self) -> None:
        """
        Reload configuration from file.
        
        This is useful after settings have been saved to ensure
        the service has the latest configuration.
        """
        self.config = self._load_config()
    
    def get_all_collections(self) -> Dict[str, Any]:
        """
        Get all collection configurations.
        
        Returns:
            Dictionary of collection configurations
        """
        return self.config.get('collections', {})
    
    def get_collection_config(self, collection_name: str) -> Dict[str, Any]:
        """
        Get configuration for a specific collection.
        
        Args:
            collection_name: Name of the collection
            
        Returns:
            Collection configuration dictionary
        """
        collections = self.config.get('collections', {})
        return collections.get(collection_name, {})
    
    def update_collection_config(
        self,
        collection_name: str,
        business_keys: List[str],
        alternative_keys: List[str],
        matching_strategy: str,
        enabled: bool = True
    ) -> Tuple[bool, Optional[str]]:
        """
        Update collection configuration.
        
        Args:
            collection_name: Name of the collection
            business_keys: List of business key field names
            alternative_keys: List of alternative key field names
            matching_strategy: Matching strategy (primary_only, primary_or_alternative, alternative_only)
            enabled: Whether collection is enabled
            
        Returns:
            Tuple of (success: bool, error_message: Optional[str])
        """
        try:
            if 'collections' not in self.config:
                self.config['collections'] = {}
            
            if collection_name not in self.config['collections']:
                self.config['collections'][collection_name] = {}
            
            # Update configuration
            self.config['collections'][collection_name]['enabled'] = enabled
            self.config['collections'][collection_name]['business_keys'] = business_keys
            self.config['collections'][collection_name]['alternative_keys'] = alternative_keys
            self.config['collections'][collection_name]['matching_strategy'] = matching_strategy
            
            # Save to file
            try:
                with open(self.config_path, 'w', encoding='utf-8') as f:
                    yaml.dump(self.config, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
            except PermissionError:
                return False, f"Permission denied: Cannot write to {self.config_path}"
            except Exception as file_error:
                return False, f"Failed to write config file: {str(file_error)}"
            
            return True, None
            
        except Exception as e:
            return False, f"Failed to update collection config: {str(e)}"
    
    def get_record_limit(self) -> int:
        """
        Get the record limit for data loading.
        
        Returns:
            Record limit (default 5000)
        """
        limit = self.config.get('ui', {}).get('default_record_limit', 5000)
        # Ensure we always return an integer
        try:
            return int(limit)
        except (ValueError, TypeError):
            return 5000
    
    def update_record_limit(self, limit: int) -> Tuple[bool, Optional[str]]:
        """
        Update the record limit for data loading.
        
        Args:
            limit: New record limit
            
        Returns:
            Tuple of (success: bool, error_message: Optional[str])
        """
        try:
            if limit < 100:
                return False, "Record limit must be at least 100"
            
            if limit > 100000:
                return False, "Record limit cannot exceed 100,000"
            
            if 'ui' not in self.config:
                self.config['ui'] = {}
            
            self.config['ui']['default_record_limit'] = limit
            
            # Save to file
            try:
                with open(self.config_path, 'w', encoding='utf-8') as f:
                    yaml.dump(self.config, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
            except PermissionError:
                return False, f"Permission denied: Cannot write to {self.config_path}"
            except Exception as file_error:
                return False, f"Failed to write config file: {str(file_error)}"
            
            return True, None
            
        except Exception as e:
            return False, f"Failed to update record limit: {str(e)}"
    
    def get_foreign_keys(self, collection_name: str) -> List[Dict[str, str]]:
        """
        Get foreign key relationships for a collection.
        
        Args:
            collection_name: Name of the collection
            
        Returns:
            List of foreign key definitions, each with:
            - field: Field name in this collection
            - references_collection: Target collection name
            - references_field: Field name in target collection
        """
        collections = self.config.get('collections', {})
        collection_config = collections.get(collection_name, {})
        return collection_config.get('foreign_keys', [])
    
    def update_foreign_keys(
        self,
        collection_name: str,
        foreign_keys: List[Dict[str, str]]
    ) -> Tuple[bool, Optional[str]]:
        """
        Update foreign key relationships for a collection.
        
        Args:
            collection_name: Name of the collection
            foreign_keys: List of foreign key definitions
            
        Returns:
            Tuple of (success: bool, error_message: Optional[str])
        """
        try:
            if 'collections' not in self.config:
                self.config['collections'] = {}
            
            if collection_name not in self.config['collections']:
                self.config['collections'][collection_name] = {}
            
            # Validate foreign keys
            for fk in foreign_keys:
                if 'field' not in fk or not fk['field']:
                    return False, "Foreign key must have a 'field' specified"
                if 'references_collection' not in fk or not fk['references_collection']:
                    return False, "Foreign key must have a 'references_collection' specified"
                if 'references_field' not in fk or not fk['references_field']:
                    return False, "Foreign key must have a 'references_field' specified"
            
            # Update configuration
            self.config['collections'][collection_name]['foreign_keys'] = foreign_keys
            
            # Save to file
            try:
                with open(self.config_path, 'w', encoding='utf-8') as f:
                    yaml.dump(self.config, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
            except PermissionError:
                return False, f"Permission denied: Cannot write to {self.config_path}"
            except Exception as file_error:
                return False, f"Failed to write config file: {str(file_error)}"
            
            return True, None
            
        except Exception as e:
            return False, f"Failed to update foreign keys: {str(e)}"
