"""
Django Signals Module for European Channel Zones Creation

This module contains Django signals that automatically create default European
channel zones after database migrations. It ensures that all necessary
broadcasting regions (France, Spain, Netherlands, Germany, Portugal) are
available immediately after deployment or migration runs.

Author: Development Team
Created: 2025
Purpose: Automated setup of European broadcasting zones for channel management
"""

# Standard library imports
import logging

# Django imports
from django.db.models.signals import post_migrate  # Signal fired after migrations complete
from django.dispatch import receiver  # Decorator to connect signal handlers
from django.apps import apps  # Django app registry for model access
from django.conf import settings  # Access to Django settings

# Initialize logger for this module
# This logger will track zone creation activities and any errors that occur
logger = logging.getLogger(__name__)

 
def create_default_channel_zones(sender, **kwargs):
    """
    Django signal handler to create default European channel zones after migrations.
    
    This function is automatically triggered after Django runs any migration.
    It ensures that the 5 required European broadcasting zones are always
    available in the database without manual intervention.
    
    Signal: post_migrate
    Trigger: After any Django migration completes
    Scope: Only runs for the specific app containing ChannelZone model
    
    Args:
        sender: The AppConfig instance of the app that just ran migrations
        **kwargs: Additional signal arguments (app_config, verbosity, etc.)
        
    Returns:
        None: This function performs database operations and logging only
        
    Zones Created:
        - France (FR) - Europe/Paris timezone
        - Spain (ES) - Europe/Madrid timezone  
        - Netherlands (NL) - Europe/Amsterdam timezone
        - Germany (DE) - Europe/Berlin timezone
        - Portugal (PT) - Europe/Lisbon timezone
        
    Example:
        This function runs automatically, but the equivalent manual operation would be:
        >>> ChannelZone.objects.get_or_create(
        ...     code='FR',
        ...     defaults={
        ...         'name': 'France',
        ...         'description': 'France - République française',
        ...         'timezone': 'Europe/Paris',
        ...         'is_active': True
        ...     }
        ... )
    """
    
    # SECURITY CHECK: Only execute for the specific app containing ChannelZone
    # This prevents the signal from running on every app's migrations
    # Check for the actual app name 'apps.channels'
    if sender.name != 'apps.channels':
        # Exit early if this migration is for a different app
        # This optimization prevents unnecessary processing
        return
    
    # TESTING ENVIRONMENT CHECK: Skip execution during automated tests
    # Testing environments often set TESTING=True in settings
    # This prevents test pollution and speeds up test execution
    if hasattr(settings, 'TESTING') and settings.TESTING:
        # Log the skip action for debugging purposes
        # This helps developers understand why zones weren't created during tests
        logger.info("Skipping zone creation during tests")
        # Exit early to avoid database operations in test environment
        return
    
    # ADDITIONAL SAFETY CHECK: Verify app_config matches our target app
    # This provides an extra layer of protection against running on wrong migrations
    # kwargs.get() safely retrieves the app_config without raising KeyError
    if kwargs.get('app_config') and kwargs.get('app_config').name != 'apps.channels':
        # This check handles edge cases where sender.name might not be sufficient
        # Exit early if the app_config doesn't match our expected app
        return
    
    # DATABASE OPERATIONS: Wrapped in try-catch for error handling
    try:
        # DYNAMIC MODEL IMPORT: Get ChannelZone model through Django's app registry
        # Using apps.get_model() instead of direct import prevents circular imports
        # and ensures the model is available even during migrations
        ChannelZone = apps.get_model('channels', 'ChannelZone')
        
        # ZONE CONFIGURATION: Define the 5 European broadcasting regions
        # Each zone contains all necessary information for broadcasting operations
        # Using a list of dictionaries for easy iteration and maintenance
        default_zones = [
            {
                # FRANCE: Primary French broadcasting region
                'name': 'France',  # Human-readable name for UI display
                'code': 'FR',  # ISO country code for unique identification
                'description': 'France - République française',  # Bilingual description
                'timezone': 'Europe/Paris',  # Official timezone for scheduling
                'is_active': True  # Enable broadcasting by default
            },
            {
                # SPAIN: Primary Spanish broadcasting region
                'name': 'Spain',  # Human-readable name for UI display
                'code': 'ES',  # ISO country code for unique identification
                'description': 'Spain - Reino de España',  # Bilingual description
                'timezone': 'Europe/Madrid',  # Official timezone for scheduling
                'is_active': True  # Enable broadcasting by default
            },
            {
                # NETHERLANDS: Primary Dutch broadcasting region
                'name': 'Netherlands',  # Human-readable name for UI display
                'code': 'NL',  # ISO country code for unique identification
                'description': 'Netherlands - Nederland',  # Bilingual description
                'timezone': 'Europe/Amsterdam',  # Official timezone for scheduling
                'is_active': True  # Enable broadcasting by default
            },
            {
                # GERMANY: Primary German broadcasting region
                'name': 'Germany',  # Human-readable name for UI display
                'code': 'DE',  # ISO country code for unique identification
                'description': 'Germany - Bundesrepublik Deutschland',  # Bilingual description
                'timezone': 'Europe/Berlin',  # Official timezone for scheduling
                'is_active': True  # Enable broadcasting by default
            },
            {
                # PORTUGAL: Primary Portuguese broadcasting region
                'name': 'Portugal',  # Human-readable name for UI display
                'code': 'PT',  # ISO country code for unique identification
                'description': 'Portugal - República Portuguesa',  # Bilingual description
                'timezone': 'Europe/Lisbon',  # Official timezone for scheduling
                'is_active': True  # Enable broadcasting by default
            }
        ]
        
        # OPERATION COUNTERS: Track the number of created and updated zones
        # These counters help with logging and monitoring deployment success
        created_count = 0  # Number of new zones created
        updated_count = 0  # Number of existing zones updated
        
        # ZONE PROCESSING LOOP: Iterate through each zone configuration
        # Process each zone individually to handle partial failures gracefully
        for zone_data in default_zones:
            # INDIVIDUAL ZONE PROCESSING: Wrapped in try-catch for error isolation
            # If one zone fails, others can still be processed successfully
            try:
                # DATABASE OPERATION: Create zone if it doesn't exist, get if it does
                # get_or_create() is atomic and prevents race conditions
                # Uses 'code' as the unique identifier for lookups
                zone, created = ChannelZone.objects.get_or_create(
                    code=zone_data['code'],  # Unique lookup field (FR, ES, NL, DE, PT)
                    defaults=zone_data  # Data to use if creating new record
                )
                
                # CREATION HANDLING: Process newly created zones
                if created:
                    # INCREMENT COUNTER: Track successful creation
                    created_count += 1
                    # LOG SUCCESS: Record successful zone creation for monitoring
                    logger.info(f"Created zone: {zone.name} ({zone.code})")
                else:
                    # UPDATE HANDLING: Check if existing zone needs updates
                    # This ensures zone data stays current with code changes
                    
                    # UPDATE FLAG: Track whether any field was modified
                    updated = False
                    
                    # FIELD COMPARISON LOOP: Check each field for changes
                    # Skip 'code' field since it's the lookup key
                    for key, value in zone_data.items():
                        # SKIP LOOKUP FIELD: Don't update the field we used for lookup
                        if key != 'code':
                            # FIELD COMPARISON: Check if current value differs from desired
                            if getattr(zone, key) != value:
                                # UPDATE FIELD: Set new value on the model instance
                                setattr(zone, key, value)
                                # FLAG UPDATE: Mark that changes were made
                                updated = True
                    
                    # SAVE UPDATES: Persist changes to database if any were made
                    if updated:
                        # DATABASE SAVE: Commit the changes to the database
                        zone.save()
                        # INCREMENT COUNTER: Track successful update
                        updated_count += 1
                        # LOG UPDATE: Record successful zone update for monitoring
                        logger.info(f"Updated zone: {zone.name} ({zone.code})")
                    else:
                        # NO CHANGES: Log that zone was already up to date
                        # Uses debug level since this is expected in most runs
                        logger.debug(f"Zone unchanged: {zone.name} ({zone.code})")
                        
            # INDIVIDUAL ZONE ERROR HANDLING: Handle failures for specific zones
            except Exception as zone_error:
                # LOG ERROR: Record the specific zone that failed
                # Include both zone code and error message for debugging
                logger.error(f"Error processing zone {zone_data['code']}: {zone_error}")
                # CONTINUE PROCESSING: Don't let one failure stop others
                continue
        
        # COMPLETION LOGGING: Report the overall results of the operation
        # Only log if something actually happened (creation or updates)
        if created_count > 0 or updated_count > 0:
            # SUCCESS LOG: Report successful operations for monitoring
            logger.info(f"European zones setup complete - Created: {created_count}, Updated: {updated_count}")
        else:
            # NO-CHANGE LOG: Report that everything was already up to date
            # Uses debug level since this is the normal state after first run
            logger.debug("All European zones already exist and are up to date")
            
    # GLOBAL ERROR HANDLING: Catch any unexpected errors in the entire process
    except Exception as e:
        # CRITICAL ERROR LOG: Report complete failure of zone setup
        # This indicates a serious problem that needs immediate attention
        logger.error(f"Error setting up European channel zones: {e}")


# ADDITIONAL DOCUMENTATION AND USAGE EXAMPLES:

"""
Usage Examples:
==============

1. Automatic Usage (Recommended):
   The signal runs automatically after any migration:
   $ python manage.py migrate
   
2. Manual Trigger (for testing):
   You can trigger this manually in Django shell:
   >>> from channels.signals import create_default_european_zones
   >>> from channels.apps import YourAppConfig
   >>> app_config = YourAppConfig('channels', 'channels')
   >>> create_default_european_zones(sender=app_config)

3. Checking Results:
   Verify zones were created:
   >>> from channels.models import ChannelZone
   >>> zones = ChannelZone.objects.filter(code__in=['FR', 'ES', 'NL', 'DE', 'PT'])
   >>> print(f"European zones count: {zones.count()}")

Integration Notes:
=================

1. App Configuration:
   Ensure your app's apps.py imports this signals module:
   
   class YourAppConfig(AppConfig):
       name = 'channels'
       
       def ready(self):
           import channels.signals

2. Logging Configuration:
   Add to your settings.py for proper logging:
   
   LOGGING = {
       'loggers': {
           'channels.signals': {
               'handlers': ['console'],
               'level': 'INFO',
           },
       },
   }

3. Model Requirements:
   Your ChannelZone model must have these fields:
   - name (CharField)
   - code (CharField, unique=True)
   - description (TextField)
   - timezone (CharField)
   - is_active (BooleanField)

Error Handling:
==============

The function handles several types of errors gracefully:

1. App Mismatch: Silently skips if wrong app
2. Testing Environment: Logs skip and exits
3. Model Import Errors: Logs critical error
4. Individual Zone Errors: Logs error but continues with other zones
5. Database Errors: Logs critical error with details

Performance Considerations:
==========================

1. Execution Frequency: Runs after every migration (by design)
2. Database Queries: 5 get_or_create operations (minimal impact)
3. Early Returns: Multiple exit points prevent unnecessary processing
4. Atomic Operations: Each zone creation is atomic

Security Considerations:
=======================

1. App Isolation: Only runs for the intended app
2. Test Isolation: Skips during testing to prevent pollution
3. Error Isolation: Individual zone failures don't affect others
4. Input Validation: Uses Django's model validation automatically

Maintenance Notes:
=================

To modify zones:
1. Update the default_zones list in this file
2. Run migrations - existing zones will be updated automatically
3. New zones will be created, existing ones updated
4. Removed zones must be handled manually (safety feature)

To add new zones:
1. Add new dictionary to default_zones list
2. Include all required fields (name, code, description, timezone, is_active)
3. Use proper ISO country codes for the 'code' field
4. Use valid timezone identifiers from the IANA Time Zone Database
"""