import os  
import pytz
import json 
import time
import requests
import datetime  

from django.views import View  
from django.db import connection
from django.utils import timezone
from django.db import transaction
from django.contrib import messages
from django.urls import reverse_lazy, reverse
from django.db.models import Q, Count, Max, Min
from django.http import HttpResponse, JsonResponse  
from django.core.exceptions import PermissionDenied 
from django.utils.decorators import method_decorator 
from django.views.decorators.csrf import csrf_protect
from django.views.generic import ListView, UpdateView 
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.shortcuts import get_object_or_404, render, redirect
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
 
from rest_framework.views import APIView   

from apps.common.utils import get_client_ip


from apps.channels.models import Channel, ChannelZone 
from apps.playlists.models import Playlists, Windows, Avails, AdspotsInAvail
from apps.playlists.utils import PlaylistXMLGenerator


class PlaylistCreateView(LoginRequiredMixin, View):
    """
        Django class-based view for comprehensive playlist creation and management operations.
        
        This view provides a complete interface for playlist creation, supporting both immediate
        playlist activation and draft playlist functionality. It handles the full lifecycle
        of playlist creation from initial form display through validation, processing, and
        final activation or storage.
        
        The view integrates with Django's authentication system to ensure only authenticated
        users can create playlists, and implements proper data isolation so users can only
        work with their own channels and zones.
        
        Inheritance:
            LoginRequiredMixin: Ensures only authenticated users can access this view
                            Automatically redirects unauthenticated users to login page
            View: Django's base class-based view providing HTTP method dispatch
        
        Supported HTTP Methods:
            GET: Renders the playlist creation form with user's available channels
            POST: Processes form submission for playlist creation (apply or draft)
        
        Features:
            - User authentication and authorization
            - Channel and zone validation
            - Date parsing and validation
            - Dual submission modes (immediate apply vs draft)
            - Comprehensive error handling with user feedback
            - Data integrity validation
            - Secure data isolation per user
        
        URL Pattern:
            Typically mapped to: /playlists/create/ or similar
            
        Template:
            Uses: playlists/new.html
            
        Context Data:
            - channels: QuerySet of user's available channels
            
        Form Processing Flow:
            1. User accesses GET endpoint → Display creation form
            2. User fills form and clicks button → POST to same endpoint
            3. System validates all input data → Extract common data
            4. Route to appropriate handler → Process apply or draft
            5. Redirect to success page → Display confirmation message
            
        Security Considerations:
            - LoginRequiredMixin prevents unauthorized access
            - Channel filtering by user ownership
            - CSRF protection through Django middleware
            - Input validation and sanitization
            - Error message sanitization to prevent XSS
            
        Error Handling:
            - Invalid channel/zone combinations
            - Malformed date inputs
            - Missing required fields
            - Database connectivity issues
            - User permission violations
            
        Dependencies:
            - Django authentication system
            - Channels model with user relationship
            - ChannelsZone model with channel relationship
            - Django messaging framework
            - Template rendering system
            
        Example Usage:
            # URL configuration
            path('create/', PlaylistCreateView.as_view(), name='new_playlist')
            
            # Template usage
            <form method="post">
                {% csrf_token %}
                <select name="channel_id">
                    {% for channel in channels %}
                        <option value="{{ channel.id_channel }}">{{ channel.name }}</option>
                    {% endfor %}
                </select>
                <input type="text" name="zonename" required>
                <input type="number" name="numofwin" required>
                <input type="date" name="day" required>
                <button type="submit" name="apply_btn">Submit Playlist</button>
                <button type="submit" name="draft_btn">Save as Draft</button>
            </form>
            
        Related Models:
            - Channels: Stores channel information with user relationships
            - ChannelsZone: Defines zones within channels
            - User: Django's authentication user model
            - Playlist: The target model for created playlists (implied)
            
        Performance Considerations:
            - Database queries are filtered and optimized
            - Single query for channels per GET request
            - Lazy evaluation of QuerySets
            - Proper indexing assumed on foreign key relationships
            
        Extensibility:
            - Additional validation can be added to _extract_common_data
            - New submission types can be added alongside apply/draft
            - Template can be customized for different UI frameworks
            - Additional context data can be passed to template
            
        Version History:
        - Initial implementation: Basic playlist creation
        - Added draft functionality: Save incomplete playlists
        - Enhanced validation: Comprehensive input checking
        - Improved error handling: User-friendly error messages
    """ 
    
    def get(self, request):
        """
            Handle GET requests for playlist creation page rendering.
            
            This method processes GET requests to display the playlist creation form.
            It retrieves all channels associated with the authenticated user and prepares
            the context data needed for rendering the playlist creation interface.
            
            The method ensures users can only see and work with channels they own,
            maintaining proper data isolation and security. The rendered page will
            contain form elements populated with the user's available channels.
            
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                    - User authentication information
                                    - Session data
                                    - GET parameters (if any)
                                    - HTTP headers and metadata
            
            Returns:
                HttpResponse: Rendered HTML response containing:
                    - Playlist creation form
                    - User's available channels in dropdown/selection elements
                    - Associated JavaScript and CSS for form functionality
                    - CSRF tokens for secure form submission
                    
            Raises:
                AttributeError: If request.user is not available (authentication middleware issue)
                TemplateDoesNotExist: If 'playlists/new.html' template is missing
                
            Template Context:
                channels (QuerySet): Filtered list of Channel objects owned by current user
                
            Example:
                GET /playlist/create/
                
                Response: HTML page with form containing:
                - Channel selection dropdown
                - Zone selection (populated via AJAX based on channel)
                - Date picker for playlist scheduling
                - Number of windows input
                - Submit buttons (Apply/Draft)
                
            Security:
            - Channels are filtered by user ownership (id_user=user)
            - Only authenticated users can access this view
            - CSRF protection enabled through Django middleware
        """
        
        # =============================================================================
        # USER AUTHENTICATION SECTION
        # Extract authenticated user for data filtering and security
        # =============================================================================
        # Retrieve the authenticated user from the request object
        # This user object contains authentication status, permissions, and user data
        # Used for filtering channels and maintaining data security boundaries
        user = request.user
        
        # =============================================================================
        # DATABASE QUERY SECTION
        # Retrieve user-specific channels for playlist creation options
        # =============================================================================
        # Query database to get all channels owned by the current user
        # This ensures data isolation - users can only create playlists for their own channels
        # The filter prevents unauthorized access to other users' channels
        channels = Channel.objects.filter(id_user=user)
        
        # =============================================================================
        # CONTEXT PREPARATION SECTION  
        # Prepare template context data for rendering the playlist creation form
        # =============================================================================
        # Create context dictionary containing data needed by the template
        # This data will be available in the template for rendering form elements
        context = { 
            'channels': channels    # QuerySet: User's channels for dropdown population
        }
        
        # =============================================================================
        # TEMPLATE RENDERING SECTION
        # Render and return the playlist creation page with user data
        # =============================================================================
        # Render the playlist creation template with the prepared context
        # The template will use the channels data to populate form selection elements
        # Returns complete HTML response ready for browser display
        return render(request, "playlists/new.html", context)
    
    def post(self, request):
        """
            Handle POST requests for playlist creation and management operations.
            
            This method serves as the main entry point for processing playlist form submissions.
            It determines the user's intended action (apply or draft) based on which submit button
            was clicked, then delegates to the appropriate handler method for processing.
            
            The method supports two primary workflows:
            1. Apply Playlist: Immediately processes and activates the playlist
            2. Draft Playlist: Saves the playlist as a draft for later modification
            
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                    - POST data with form fields
                                    - User authentication information
                                    - Session data
                                    Expected POST fields:
                                    - 'apply_btn': Present if "Submit Playlist" button clicked
                                    - 'draft_btn': Present if "Save as Draft" button clicked
                                    - Additional playlist data fields
            
            Returns:
                HttpResponseRedirect: Redirect response to appropriate page:
                    - On apply action: Redirects based on _handle_apply_playlist result
                    - On draft action: Redirects based on _handle_draft_playlist result  
                    - On invalid submission: Redirects to playlist list with error message
                    
            Raises:
                AttributeError: If request.user is not available (should not occur in normal flow)
                
            Example:
                POST /playlist/create/
                Form data: {
                    'apply_btn': 'Submit Playlist',
                    'channel_id': '123',
                    'zonename': 'zone1',
                    'numofwin': '5',
                    'day': '07/30/2025'
                }
                
            Flow:
            1. Extract user from request
            2. Determine which button was clicked
            3. Route to appropriate handler method
            4. Return redirect response
        """
        
        # =============================================================================
        # USER AUTHENTICATION SECTION
        # Extract and prepare user information for playlist operations
        # =============================================================================
        # Get the authenticated user from the request object
        # This user will be associated with the created/modified playlist
        user = request.user
        
        # =============================================================================
        # BUTTON ACTION DETECTION SECTION
        # Determine which submit button was clicked to route request appropriately
        # =============================================================================
        # Check if "Submit Playlist" button was clicked
        # apply_btn will contain the button's value if it was clicked, None otherwise
        apply_btn = request.POST.get("apply_btn")
        
        # Check if "Save as Draft" button was clicked  
        # draft_btn will contain the button's value if it was clicked, None otherwise
        draft_btn = request.POST.get("draft_btn")
        
        # =============================================================================
        # REQUEST ROUTING SECTION
        # Route the request to appropriate handler based on user action
        # =============================================================================
        # Handle "Submit Playlist" action - immediate processing and activation
        if apply_btn:
            # Delegate to apply handler which validates data and creates active playlist
            # This method handles all validation, database operations, and user feedback
            return self._handle_apply_playlist(request, user)
            
        # Handle "Save as Draft" action - save for later modification
        elif draft_btn:
            # Delegate to draft handler which saves playlist without activation
            # This allows users to work on playlists incrementally
            return self._handle_draft_playlist(request, user)
        
        # =============================================================================
        # ERROR HANDLING SECTION
        # Handle invalid submissions where no recognized button was clicked
        # =============================================================================
        # Handle case where neither button was clicked or form was submitted incorrectly
        # This could happen due to:
        # - JavaScript errors preventing proper form submission
        # - Manual form manipulation
        # - Browser compatibility issues
        # - Networking issues during form submission
        messages.error(
            request, 
            "Invalid submission. Please use either 'Save as Draft' or 'Submit Playlist' button."
        )
        
        # Redirect user to playlist list page to recover from the error
        # This provides a safe fallback and allows user to try again
        return redirect("playlists:list_playlist")
    
    def _extract_common_data(self, request):
        """
            Extract and validate common data from POST request for playlist operations.
            
            This method processes form data submitted via POST request and validates each field
            to ensure data integrity before proceeding with playlist operations. It handles
            channel selection, zone configuration, window count, and date formatting with
            comprehensive error handling and user feedback.
            
            Args:
                request (HttpRequest): Django HTTP request object containing POST data
                                    Expected POST fields:
                                    - 'channel_id': Unique identifier for the channel
                                    - 'zonename': Zone identifier within the channel
                                    - 'numofwin': Number of windows (must be numeric)
                                    - 'day': Date in MM/DD/YYYY format
                
            Returns:
                dict or HttpResponseRedirect: On success, returns dictionary with validated data:
                    {
                        'channel_id': str - The channel identifier
                        'channel': Channels - Channel model instance
                        'channel_zone': ChannelsZone - Channel zone model instance  
                        'number_of_windows': str - Number of windows value
                        'daydate': datetime - Parsed datetime object
                    }
                    On error, returns redirect to 'playlists:new_playlist' with error message
                    
            Raises:
                Channels.DoesNotExist: When specified channel doesn't exist in database
                ChannelsZone.DoesNotExist: When specified zone doesn't exist for the channel
                ValueError: When date format is invalid or number_of_windows is not numeric
                Exception: For any other unexpected errors during processing
                
            Example:
            >>> data = self._extract_common_data(request)
            >>> if isinstance(data, dict):
            ...     # Process successful data extraction
            ...     channel = data['channel']
            ...     date = data['daydate']
            >>> else:
            ...     # Handle redirect response
            ...     return data
        """
        
        # =============================================================================
        # CHANNEL VALIDATION SECTION
        # Validate and retrieve channel information from the database
        # =============================================================================
        try:
            # Extract channel_id from POST data - this identifies which channel to work with
            channel_id = request.POST.get('channel_id') 
            # Validate that channel_id was provided in the request
            if not channel_id:
                # Display user-friendly error message using Django's messaging framework
                messages.error(request, 'Channel ID is required.')
                # Redirect user back to playlist creation form to correct the issue
                return redirect('playlists:new_playlist') 
            # Query database to retrieve the Channel object using the provided ID
            # This verifies the channel exists and fetches related channel data
            channel = Channel.objects.get(id_channel=channel_id) 
        except Channel.DoesNotExist:
            # Handle case where channel_id doesn't match any existing channel
            messages.error(request, 'Selected channel does not exist.')
            return redirect('playlists:new_playlist')
        except Exception as e:
            # Catch any unexpected database or system errors during channel retrieval
            messages.error(request, f'Error retrieving channel: {str(e)}')
            return redirect('playlists:new_playlist')
        
        # =============================================================================
        # ZONE VALIDATION SECTION  
        # Validate and retrieve channel zone configuration
        # =============================================================================
        try:
            # Extract and clean zone name from POST data
            # strip() removes leading/trailing whitespace to prevent validation issues
            zonename = request.POST.get('zonename', '').strip()
            
            # Ensure zone name was provided and not empty after stripping whitespace
            if not zonename:
                messages.error(request, 'Channel Zone name is required.')
                return redirect('playlists:new_playlist')
                
            # Query database for the specific zone within the selected channel
            # This ensures the zone exists and belongs to the correct channel
            channel_zone = ChannelZone.objects.get(
                id_channel=channel_id,      # Filter by the channel we validated above
                id_zone_channel=zonename    # Filter by the zone name provided
            ) 
        except ChannelZone.DoesNotExist:
            # Handle case where zone doesn't exist for the specified channel
            messages.error(request, 'Selected zone does not exist for this channel.')
            return redirect('playlists:new_playlist')
        except Exception as e:
            # Catch any unexpected database or system errors during zone retrieval
            messages.error(request, f'Error retrieving channel zone: {str(e)}')
            return redirect('playlists:new_playlist')
        
        # =============================================================================
        # WINDOW COUNT VALIDATION SECTION
        # Validate the number of windows parameter for playlist configuration
        # =============================================================================
        try:
            # Extract number of windows from POST data
            number_of_windows = request.POST.get('numofwin') 
            # Validate that the field was provided
            if not number_of_windows:
                messages.error(request, 'Number of windows is required.')
                return redirect('playlists:new_playlist') 
            # Validate that the value is a valid integer by attempting conversion
            # This prevents non-numeric values from causing issues downstream
            int(number_of_windows) 
        except ValueError:
            # Handle case where number_of_windows contains non-numeric characters
            messages.error(request, 'Number of windows must be a valid number.')
            return redirect('playlists:new_playlist')
        except Exception as e:
            # Catch any unexpected errors during number validation
            messages.error(request, f'Error processing number of windows: {str(e)}')
            return redirect('playlists:new_playlist')
        
        # =============================================================================
        # DATE VALIDATION AND PARSING SECTION
        # Parse and validate the date input for playlist scheduling
        # =============================================================================
        try:
            # Extract date string from POST data
            daydate = request.POST.get('day')
            
            # Ensure date was provided in the request
            if not daydate:
                messages.error(request, 'Date is required.')
                return redirect('playlists:new_playlist')
                
            # Parse date string into datetime object using expected MM/DD/YYYY format
            # This validates the date format and creates a proper datetime object for database storage
            daydate = datetime.datetime.strptime(daydate, '%m/%d/%Y') 
        except ValueError:
            # Handle case where date string doesn't match expected MM/DD/YYYY format
            messages.error(request, 'Invalid date format. Please use MM/DD/YYYY format.')
            return redirect('playlists:new_playlist')
        except Exception as e:
            # Catch any unexpected errors during date parsing
            messages.error(request, f'Error processing date: {str(e)}')
            return redirect('playlists:new_playlist')

        # =============================================================================
        # SUCCESS RETURN SECTION
        # Return validated data dictionary for use by calling method
        # =============================================================================
        # All validations passed - return dictionary containing all validated data
        # This data can be safely used for database operations and business logic
        return {
            'channel_id': channel_id,              # String: Channel identifier
            'channel': channel,                    # Model: Channel database object
            'channel_zone': channel_zone,          # Model: ChannelZone database object
            'number_of_windows': number_of_windows,# String: Validated numeric value
            'daydate': daydate                     # datetime: Parsed date object
        }

    def _handle_apply_playlist(self, request, user):
        """
            Handle immediate playlist application and activation (non-draft processing).
            
            This method processes a complete playlist creation workflow including validation,
            database record creation, structural processing, activity logging, XML generation,
            and optional FTP deployment. It creates an active playlist that is immediately
            ready for broadcast scheduling.
            
            The method performs the complete playlist lifecycle in a single transaction-like
            operation, ensuring data consistency and providing comprehensive error handling
            with detailed logging for troubleshooting and auditing purposes.
            
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                    - POST data with validated form fields
                                    - User session information
                                    - CSRF tokens and security headers
                                    - Optional draft_version for playlist updates
                user (User): Django User model instance representing the authenticated user
                            Used for permissions, logging, and audit trail creation
            
            Returns:
                HttpResponseRedirect: Redirect to playlist detail page showing:
                    - Created playlist information
                    - Generated schedule details
                    - XML output preview
                    - Success confirmation message
                    URL pattern: "playlists:playlist_detail" with playlist ID parameter
                    
            Raises:
                ValidationError: If _extract_common_data validation fails
                IntegrityError: If playlist creation violates database constraints
                AttributeError: If required model relationships are missing
                Exception: For XML generation or FTP upload failures
                
            Database Operations:
                - Creates new Playlists record with version 1
                - Processes playlist structure (windows, avails, ads)
                - Logs activity record for audit trail
                - Maintains referential integrity across related models
                
            Processing Flow:
                1. Start performance timer for monitoring
                2. Extract and validate common form data
                3. Create main playlist database record
                4. Process detailed playlist structure
                5. Log successful creation activity
                6. Generate XML schedule document
                7. Optional FTP upload (currently disabled)
                8. Redirect to playlist detail view
                
            Performance Monitoring:
                - Tracks total processing time in milliseconds
                - Logs duration for performance analysis
                - Enables identification of bottlenecks
                
            Security Features:
                - User authentication required (enforced at view level)
                - Data validation through _extract_common_data
                - Audit logging for all playlist operations
                - CSRF protection for form submissions
                
            Example Usage:
                # Called from POST method when apply_btn is clicked
                return self._handle_apply_playlist(request, user)
                
                # Results in playlist creation and redirect to:
                # /playlists/detail/123/ (where 123 is the new playlist ID)
        """
        
        # =============================================================================
        # PERFORMANCE MONITORING SECTION
        # Initialize timing for operation performance tracking and analysis
        # =============================================================================
        # Record start time for performance monitoring and optimization analysis
        # This timestamp will be used to calculate total processing duration
        start_time = time.time()
        
        # =============================================================================
        # DATA EXTRACTION AND VALIDATION SECTION
        # Retrieve and validate all required form data for playlist creation
        # =============================================================================
        # Extract and validate common form data including channel, zone, date, and windows
        # This method handles all validation, error messages, and potential redirects
        # Returns validated data dictionary or redirect response on validation failure
        data = self._extract_common_data(request)
        
        # Extract draft version if this is an update to an existing draft playlist
        # Used for version control and maintaining draft history
        draft_version = request.POST.get('draft_version')
        
        # =============================================================================
        # MAIN PLAYLIST RECORD CREATION SECTION
        # Create the primary playlist database record with validated data
        # =============================================================================
        # Create the main playlist record in the database with all required fields
        # This establishes the playlist as an active (non-draft) broadcast schedule
        playlist = Playlists.objects.create(
            channel=data['channel'],                    # Channel model instance from validation
            version=1,                                  # Initial version for new playlist
            broadcast_date=data['daydate'],             # Target broadcast date from form
            # Set start time to 00:01 UTC for beginning of broadcast day
            start_date=data['daydate'].replace(
                hour=0,                                 # Start at midnight hour
                minute=1,                               # Add 1 minute buffer
                second=0,                               # Reset seconds to zero
                microsecond=0,                          # Reset microseconds to zero
                tzinfo=datetime.timezone.utc            # Set timezone to UTC for consistency
            ),
            # Set end time to 23:59 UTC for end of broadcast day
            end_date=data['daydate'].replace(
                hour=23,                                # End at 23:59 hour
                minute=59,                              # Set to 59 minutes
                second=0,                               # Reset seconds to zero
                microsecond=0,                          # Reset microseconds to zero
                tzinfo=datetime.timezone.utc            # Set timezone to UTC for consistency
            ), 
            zone_channel=data['channel_zone'],          # Channel zone model instance
            is_draft=False,                             # Mark as active (not draft) playlist
            draft_version=0                             # Set draft version to 0 for active playlist
        )
        
        # =============================================================================
        # PLAYLIST STRUCTURE PROCESSING SECTION
        # Process detailed playlist components including windows, avails, and advertisements
        # =============================================================================
        # Process the detailed structure of the playlist including time windows,
        # advertisement slots, and content scheduling based on the number of windows
        self._process_playlist_structure(
            request,                                    # HTTP request for additional form data
            playlist,                                   # Created playlist instance for association
            data['daydate'],                            # Broadcast date for scheduling calculations
            data['number_of_windows']                   # Number of windows for structure generation
        ) 

        # =============================================================================
        # ACTIVITY LOGGING SECTION
        # Record successful playlist creation for audit trail and monitoring
        # =============================================================================
        # Log the successful playlist creation activity for audit purposes
        # This creates a permanent record of the operation for compliance and debugging
        self._log_activity(
            playlist=playlist,                          # Playlist instance for activity association
            request=getattr(self, 'request', None),    # Request object for user/session tracking
            is_successful=True,                         # Mark operation as successful
            # Calculate processing duration in milliseconds for performance analysis
            duration_ms=int((time.time() - start_time) * 1000)
        )

        # =============================================================================
        # XML GENERATION SECTION
        # Generate XML schedule document for broadcast system integration
        # =============================================================================
        # Initialize the XML generator for creating broadcast-compatible schedule files
        generator = PlaylistXMLGenerator()
        
        # Generate XML representation of the playlist for broadcast system consumption
        # This creates industry-standard XML format for integration with broadcast equipment
        xml_playlist_res = generator.generate_schedule(playlist=playlist)

        # Output XML result for debugging and verification purposes
        # In production, this might be logged instead of printed to console
        print(xml_playlist_res)
        
        # =============================================================================
        # FTP DEPLOYMENT SECTION (CURRENTLY DISABLED)
        # Optional automated deployment to broadcast servers via FTP
        # =============================================================================
        # FTP upload functionality preserved for future activation
        # When enabled, this would automatically deploy the playlist to broadcast servers
        # Currently commented out for manual deployment workflow
        # self._handle_ftp_upload(xml_playlist_res, playlist)
        
        # =============================================================================
        # SUCCESS REDIRECT SECTION
        # Redirect user to playlist detail page showing the created playlist
        # =============================================================================
        # Redirect to the playlist detail view showing the successfully created playlist
        # This provides immediate feedback and allows user to review the generated schedule
        return redirect("playlists:playlist_detail", id_playlist=playlist.id)

    def _handle_draft_playlist(self, request, user):
        """
            Handle draft playlist creation and management for incremental playlist development.
            
            This method processes playlist creation in draft mode, allowing users to save
            incomplete or work-in-progress playlists for later modification and eventual
            activation. Draft playlists are not immediately broadcast-ready and require
            additional processing before they can be applied to live broadcast schedules.
            
            The draft functionality enables users to:
                - Save incomplete playlist configurations
                - Iteratively develop complex playlists over multiple sessions
                - Collaborate on playlist creation with approval workflows
                - Test playlist configurations before activation
                - Maintain version history of playlist development
            
            Currently, the core implementation is commented out pending infrastructure
            setup for draft storage and management. The method maintains the interface
            contract while preserving the implementation logic for future activation.
            
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                - POST data with form fields (same as apply playlist)
                                - User session and authentication information
                                - CSRF tokens for secure form submission
                                - Optional existing draft ID for updates
                user (User): Django User model instance representing the authenticated user
                            Used for draft ownership, permissions, and audit trail creation
            
            Returns:
                HttpResponseRedirect: Redirect to draft playlist management page showing:
                    - Saved draft playlist information
                    - Options for further editing
                    - Convert to active playlist option
                    - Draft version history
                    URL pattern: "playlists:draft_playlist" with playlist ID parameter
                    
            Raises:
                ValidationError: If _extract_common_data validation fails
                IntegrityError: If draft playlist creation violates database constraints
                AttributeError: If required model relationships are missing
                NotImplementedError: Currently raised due to commented implementation
                
            Database Operations (When Implemented):
                - Creates new Playlists record with is_draft=True
                - Sets draft version for tracking incremental changes
                - Processes playlist structure in draft mode
                - Maintains audit trail for draft operations
                
            Draft vs Apply Differences:
                - is_draft flag set to True
                - draft_version incremented for tracking
                - No XML generation for broadcast systems
                - No FTP upload to production servers
                - Relaxed validation for incomplete data
                - Additional save/resume functionality
                
            Processing Flow (When Active):
                1. Extract and validate form data (same as apply)
                2. Create draft playlist database record
                3. Process playlist structure in draft mode
                4. Skip XML generation and FTP deployment
                5. Redirect to draft management interface
                
            Implementation Status:
                CURRENT: Core logic commented out pending infrastructure setup
                PLANNED: Full draft functionality with version control
                PRESERVED: Implementation logic maintained for easy activation
                
            Future Enhancements:
                - Draft-specific validation rules (more permissive)
                - Version comparison and diff functionality
                - Draft sharing and collaboration features
                - Automatic draft cleanup policies
                - Draft to production promotion workflows
                
            Security Considerations:
                - Draft ownership restricted to creating user
                - Same authentication requirements as apply playlist
                - Draft data isolated from production playlists
                - Audit logging for draft operations
                
            Example Usage:
                # Called from POST method when draft_btn is clicked
                return self._handle_draft_playlist(request, user)
                
                # Results in draft creation and redirect to:
                # /playlists/draft/123/ (where 123 is the draft playlist ID)
                
            Related Functionality:
                - _extract_common_data: Common validation logic
                - _process_playlist_structure: Structure generation
                - _handle_draft_xml_generation: Draft-specific XML processing
                - Draft management views: Edit, delete, promote drafts
        """
        
        # =============================================================================
        # DATA EXTRACTION AND VALIDATION SECTION
        # Retrieve and validate form data using shared validation logic
        # =============================================================================
        # Extract and validate common form data using the same validation logic as apply playlist
        # This ensures consistency between draft and active playlist data requirements
        # Returns validated data dictionary or handles redirect on validation failure
        data = self._extract_common_data(request)
        
        # =============================================================================
        # DRAFT PLAYLIST CREATION SECTION (CURRENTLY DISABLED)
        # Core draft playlist creation logic preserved for future implementation
        # =============================================================================
        # Create draft playlist record with is_draft=True for incremental development
        # This section is commented out pending infrastructure setup for draft management
        # Implementation preserved to maintain development continuity
        # playlist = Playlists(
        #     channel_id=data['channel_id'],                    # Channel identifier from validated data
        #     version=1,                                        # Initial version for new draft
        #     broadcast_date=str(data['daydate']),              # Target broadcast date as string
        #     start_date=f"{data['daydate']}T00:01:00+00:00",   # ISO format start time with timezone
        #     end_date=f"{data['daydate']}T23:59:00+00:00",     # ISO format end time with timezone
        #     zone_channel_id=data['channel_zone'].id_zone_channel,  # Zone identifier from relationship
        #     is_draft=True,                                    # Mark as draft (not active) playlist
        #     draft_version=1                                   # Initial draft version for tracking
        # )
        # Save the draft playlist to database for persistent storage
        # playlist.save()
        
        # =============================================================================
        # DRAFT STRUCTURE PROCESSING SECTION (CURRENTLY DISABLED)
        # Process playlist components in draft mode with relaxed validation
        # =============================================================================
        # Process the detailed structure of the draft playlist including windows and slots
        # Draft mode may allow incomplete or placeholder data for iterative development
        # traffic_counter = self._process_playlist_structure(
        #     request,                                          # HTTP request for additional form data
        #     playlist,                                         # Draft playlist instance for association
        #     data['daydate'],                                  # Broadcast date for scheduling
        #     data['number_of_windows']                         # Number of windows for structure
        # )
        
        # =============================================================================
        # DRAFT-SPECIFIC PROCESSING SECTION (FUTURE ENHANCEMENT)
        # Handle draft-specific operations like XML preview generation
        # =============================================================================
        # Generate draft-specific XML or preview content for user review
        # This would create non-production XML for validation and preview purposes
        # Commented out pending implementation of draft-specific XML handling
        # self._handle_draft_xml_generation(data, playlist)
        
        # =============================================================================
        # REDIRECT SECTION
        # Redirect to draft management interface for further editing
        # =============================================================================
        # Redirect to the draft playlist management page
        # Note: This will currently fail due to undefined playlist variable
        # This demonstrates the interface contract while implementation is pending
        return redirect("playlists:draft_playlist", id_playlist=playlist.id)

    def _process_playlist_structure(self, request, playlist, daydate, number_of_windows):
        """
            Process the complex nested structure of playlist components including windows, avails, and advertisements.
            
            This method orchestrates the creation of the complete playlist hierarchy by processing
            time windows and their associated advertisement availability slots. It handles the
            iterative creation of Windows records and delegates the processing of nested avails
            and advertisements to specialized handler methods.
            
            The method implements a hierarchical data processing pattern where:
                1. Playlist contains multiple Windows
                2. Each Window contains multiple Avails (advertisement slots)
                3. Each Avail contains multiple Ads (specific advertisements)
                
            The processing maintains referential integrity across all levels and provides
            comprehensive validation to ensure data consistency and broadcast schedule accuracy.
            
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                    - POST data with nested form arrays for windows/avails
                                    - Form fields indexed by window number (e.g., windowstart[0])
                                    - User session and authentication information
                playlist (Playlists): Playlist model instance to associate windows with
                                    Must be a saved instance with valid primary key
                daydate (datetime): Formatted datetime object representing the broadcast date
                                Used for scheduling calculations and time validation
                number_of_windows (str): String representation of total windows to process
                                        Must be convertible to positive integer
            
            Returns:
                int: Cumulative traffic counter value representing total processed advertisements
                    Used for billing, reporting, and capacity planning purposes
                    Incremented by each processed avail and advertisement
                    
            Raises:
                ValueError: If number_of_windows cannot be converted to integer
                AttributeError: If playlist instance is invalid or unsaved
                IntegrityError: If window creation violates database constraints
                KeyError: If expected form fields are missing from request.POST
                
            Database Operations:
                - Creates Windows records linked to the playlist
                - Calculates and stores window duration based on start/end times
                - Delegates avail and advertisement creation to _process_avails
                - Maintains foreign key relationships across the hierarchy
                
            Form Data Structure Expected:
                - windowstart[0], windowstart[1], ... : Start times for each window
                - windowend[0], windowend[1], ... : End times for each window  
                - numofavails[0], numofavails[1], ... : Avail counts per window
                - Additional nested data processed by _process_avails method
                
            Processing Flow:
                1. Initialize traffic counter for advertisement tracking
                2. Validate number_of_windows parameter
                3. Iterate through each window index
                4. Extract window-specific form data
                5. Calculate window duration from start/end times
                6. Create and save Windows database record
                7. Process nested avails for current window
                8. Accumulate traffic counter from avail processing
                9. Return final traffic count
                
            Performance Considerations:
                - Processes windows sequentially to maintain order
                - Single database query per window creation
                - Bulk processing delegated to _process_avails method
                - Early termination for invalid window configurations
                
            Data Validation:
                - Validates number_of_windows is positive integer
                - Skips windows with missing or invalid avail counts
                - Ensures window start/end times are present
                - Validates window duration calculations
                
            Error Handling Strategy:
                - Graceful handling of missing form fields
                - Continues processing valid windows despite individual failures
                - Returns partial results when possible
                - Delegates detailed error handling to specialized methods
                
            Integration Points:
                - _calculate_window_duration: Computes time spans
                - _process_avails: Handles nested avail/ad processing
                - Windows model: Database representation of time windows
                - Form validation: Client-side and server-side validation
                
            Example Usage:
                traffic_count = self._process_playlist_structure(
                    request=request,
                    playlist=playlist_instance,
                    daydate=datetime(2025, 7, 30),
                    number_of_windows='3'
                )
                
            Business Logic:
                - Each window represents a scheduled time slot for advertisements
                - Windows must have valid start/end times for broadcast scheduling
                - Traffic counter enables revenue tracking and capacity management
                - Window duration affects advertisement slot availability
        """
        
        # =============================================================================
        # TRAFFIC COUNTER INITIALIZATION SECTION
        # Initialize advertisement tracking counter for billing and reporting
        # =============================================================================
        # Initialize traffic counter to track total advertisements processed across all windows
        # This counter accumulates throughout the processing and is used for:
        # - Revenue calculation and billing purposes
        # - Capacity planning and load analysis
        # - Performance monitoring and optimization
        traffic = 0
        
        # =============================================================================
        # INPUT VALIDATION SECTION
        # Validate number_of_windows parameter and handle edge cases
        # =============================================================================
        # Validate that number_of_windows exists and represents a positive integer
        # Early return prevents unnecessary processing for invalid configurations
        if not number_of_windows or int(number_of_windows) <= 0:
            # Return zero traffic count for invalid window configurations
            # This handles cases where no windows are specified or invalid counts provided
            return traffic

        # =============================================================================
        # WINDOW PROCESSING LOOP SECTION
        # Iterate through each window index to process individual time slots
        # =============================================================================
        # Process each window sequentially to maintain proper ordering and relationships
        # Window indices start at 0 and continue for the specified number of windows
        for i in range(int(number_of_windows)):
            
            # =============================================================================
            # WINDOW FORM DATA EXTRACTION SECTION
            # Extract window-specific data from indexed form fields
            # =============================================================================
            # Extract number of avails for current window from indexed form field
            # Form field format: numofavails[0], numofavails[1], etc.
            numofavails = request.POST.get(f'numofavails[{i}]')
            
            # Skip processing this window if no avails are specified
            # This allows for sparse window configurations where some windows may be empty
            if not numofavails:
                continue
                
            # Extract window start time from indexed form field
            # Form field format: windowstart[0], windowstart[1], etc.
            # Expected format: HH:MM or similar time representation
            window_start = request.POST.get(f'windowstart[{i}]')    
            
            # Extract window end time from indexed form field
            # Form field format: windowend[0], windowend[1], etc.
            # Expected format: HH:MM or similar time representation
            window_end = request.POST.get(f'windowend[{i}]')   
            
            # =============================================================================
            # WINDOW DURATION CALCULATION SECTION
            # Calculate the duration of the current window for scheduling purposes
            # =============================================================================
            # Calculate window duration using specialized method for time arithmetic
            # This handles time zone conversions, day boundaries, and validation
            window_duration = self._calculate_window_duration(window_start, window_end) 
            
            # =============================================================================
            # WINDOW DATABASE RECORD CREATION SECTION
            # Create and persist the Windows model instance
            # =============================================================================
            # Create Windows model instance with calculated and extracted data
            # This establishes the database relationship between playlist and window
            window = Windows(
                playlist=playlist,                      # Foreign key relationship to parent playlist
                window_start=window_start,              # Start time for this broadcast window
                window_end=window_end,                  # End time for this broadcast window
                window_duration=window_duration         # Calculated duration for scheduling
            )
            
            # Persist the window record to database with immediate commit
            # This ensures the window has a primary key for subsequent avail relationships
            window.save()
            
            # =============================================================================
            # NESTED AVAIL PROCESSING SECTION
            # Process advertisement availability slots within the current window
            # =============================================================================
            # Process all avails (advertisement slots) for this window
            # This method handles the nested structure of avails and individual ads
            # Returns updated traffic counter including advertisements from this window
            traffic = self._process_avails(
                request,                                # HTTP request containing nested form data
                window,                                 # Current window instance for relationship
                daydate,                                # Broadcast date for scheduling context
                i,                                      # Current window index for form field access
                int(numofavails),                       # Number of avails to process for this window
                traffic                                 # Current traffic counter to accumulate
            )
        
        # =============================================================================
        # RETURN SECTION
        # Return final accumulated traffic counter for reporting and analysis
        # =============================================================================
        # Return the final traffic counter representing total advertisements processed
        # This value is used by calling methods for billing, reporting, and validation
        return traffic

    def _process_avails(self, request, window, daydate, window_index, numofavails, traffic):
        """
            Process advertisement availability slots (avails) within a specific broadcast window.
            
            This method handles the second level of the playlist hierarchy, processing avails
            which represent discrete advertisement opportunity slots within a broadcast window.
            Each avail defines a specific time slot where advertisements can be scheduled,
            and contains one or more individual advertisement records.
            
            The method iterates through all avails for a given window, creates the corresponding
            database records, and delegates the processing of nested advertisements to the
            specialized _process_ads method. It maintains the hierarchical relationship
            between windows and avails while accumulating traffic statistics.
            
            Avails serve as the fundamental scheduling unit for advertisement placement,
            providing the granular time slots that advertisers can purchase for their
            campaigns. Each avail is uniquely identified within its parent window and
            maintains precise timing information for broadcast coordination.
            
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                    - Nested POST data with double-indexed form arrays
                                    - Form fields indexed by window and avail position
                                    - Advertisement data for each avail slot
                                    - User session and authentication information
                window (Windows): Window model instance that will contain the avails
                                Must be a saved instance with valid primary key
                                Represents the parent time window for these avails
                daydate (datetime): Formatted datetime object for the broadcast date
                                Used for scheduling context and time calculations
                                Provides date reference for avail timing
                window_index (int): Zero-based index of the current window being processed
                                Used to access correct form fields in nested arrays
                                Corresponds to window position in the playlist
                numofavails (int): Total number of avails to process for this window
                                Must be a positive integer value
                                Determines the iteration range for avail processing
                traffic (int): Current cumulative traffic counter from previous processing
                            Represents total advertisement count processed so far
                            Will be incremented by advertisements in processed avails
            
            Returns:
                int: Updated traffic counter including advertisements from all processed avails
                    Accumulates advertisement counts from nested _process_ads calls
                    Used for billing calculations and capacity reporting
                    Maintains running total across all playlist components
                    
            Raises:
                ValueError: If numofavails is not a valid positive integer
                AttributeError: If window instance is invalid or unsaved
                IntegrityError: If avail creation violates database constraints
                KeyError: If expected nested form fields are missing
                
            Database Operations:
                - Creates Avails records linked to the parent window
                - Sets avail position within window (availinwindow field)
                - Maintains foreign key relationship to Windows model
                - Delegates advertisement creation to _process_ads method
                
            Form Data Structure Expected:
                - availstart[window_index][avail_index]: Start time for each avail
                - numofads[window_index][avail_index]: Advertisement count per avail
                - Additional nested advertisement data processed by _process_ads
                
            Processing Flow:
                1. Iterate through each avail index from 0 to numofavails-1
                2. Extract avail start time from double-indexed form field
                3. Validate avail data and skip if invalid
                4. Extract advertisement count for current avail
                5. Create and save Avails database record
                6. Process nested advertisements for current avail
                7. Accumulate traffic counter from advertisement processing
                8. Continue to next avail or return final traffic count
                
            Data Validation:
                - Validates avail start time is present in form data
                - Skips avails with missing or invalid start times
                - Handles optional advertisement count gracefully
                - Ensures avail position numbering starts from 1
                
            Performance Considerations:
                - Sequential processing maintains avail order within window
                - Single database query per avail creation
                - Efficient nested form field access using computed keys
                - Early continuation for invalid avail configurations
                
            Business Logic:
                - Avails represent purchasable advertisement time slots
                - Each avail has a unique position within its parent window
                - Avail timing must align with window boundaries
                - Traffic counter enables revenue and capacity tracking
                
            Integration Points:
                - _process_ads: Handles nested advertisement processing
                - Avails model: Database representation of advertisement slots
                - Windows model: Parent relationship for hierarchical structure
                - Form validation: Client-side and server-side data validation
                
            Example Usage:
                updated_traffic = self._process_avails(
                    request=request,
                    window=window_instance,
                    daydate=datetime(2025, 7, 30),
                    window_index=0,
                    numofavails=5,
                    traffic=current_traffic_count
                )
                
            Avail Positioning:
                - availinwindow field uses 1-based indexing for business logic
                - Loop uses 0-based indexing for programming convenience
                - Position mapping: loop index j → avail position (j + 1)
                - Maintains consistent ordering for broadcast scheduling
        """
        
        # =============================================================================
        # AVAIL ITERATION SECTION
        # Process each avail sequentially to maintain proper ordering
        # =============================================================================
        # Iterate through each avail index to process individual advertisement slots
        # Range starts at 0 and continues for the specified number of avails
        # Sequential processing ensures proper avail ordering within the window
        for j in range(numofavails):
            
            # =============================================================================
            # AVAIL FORM DATA EXTRACTION SECTION
            # Extract avail-specific data from double-indexed form fields
            # =============================================================================
            # Construct form field key for avail start time using double indexing
            # Format: availstart[window_index][avail_index] for nested form arrays
            avail_start_key = f'availstart[{window_index}][{j}]'
            
            # Extract avail start time from the computed form field key
            # This represents the precise timing when this avail slot begins
            av_start = request.POST.get(avail_start_key)
            
            # =============================================================================
            # AVAIL VALIDATION SECTION
            # Validate required avail data and handle missing information
            # =============================================================================
            # Skip processing this avail if start time is missing or invalid
            # This allows for sparse avail configurations and handles form submission errors
            if not av_start:
                continue
            
            # Extract number of advertisements for this avail using double indexing
            # Format: numofads[window_index][avail_index] for nested advertisement count
            number_of_ads = request.POST.get(f'numofads[{window_index}][{j}]')
            
            # =============================================================================
            # AVAIL DATABASE RECORD CREATION SECTION
            # Create and persist the Avails model instance
            # =============================================================================
            # Create Avails model instance with extracted and calculated data
            # This establishes the database relationship between window and avail
            avail = Avails(
                window=window,                          # Foreign key relationship to parent window
                avail_start=av_start,                   # Start time for this advertisement slot
                # Convert 0-based loop index to 1-based business position
                # Business logic requires avail positions to start from 1
                availinwindow=str(j + 1)               # Position of avail within the window (1-based)
            )
            
            # Persist the avail record to database with immediate commit
            # This ensures the avail has a primary key for subsequent advertisement relationships
            avail.save()
            
            # =============================================================================
            # NESTED ADVERTISEMENT PROCESSING SECTION
            # Process individual advertisements within the current avail
            # =============================================================================
            # Process advertisements for this avail if any are specified
            # Only proceed with advertisement processing if count is provided
            if number_of_ads:
                # Process all advertisements for this avail slot
                # This method handles the deepest level of the playlist hierarchy
                # Returns updated traffic counter including ads from this avail
                traffic = self._process_ads(
                    request,                            # HTTP request containing nested advertisement data
                    avail,                              # Current avail instance for relationship
                    window_index,                       # Window index for form field access
                    j,                                  # Current avail index for form field access
                    int(number_of_ads),                # Number of advertisements to process
                    traffic                             # Current traffic counter to accumulate
                )
        
        # =============================================================================
        # RETURN SECTION
        # Return accumulated traffic counter for upstream processing
        # =============================================================================
        # Return the updated traffic counter including all advertisements from processed avails
        # This value continues accumulating as it moves up the processing hierarchy
        return traffic

    def _process_ads(self, request, avail, window_index, avail_index, number_of_ads, traffic):
        """
            Process individual advertisement records within a specific advertisement availability slot.
            
            This method handles the deepest level of the playlist hierarchy, processing individual
            advertisements that will be scheduled within specific avail slots. Each advertisement
            represents a concrete commercial spot with specific content, duration, and positioning
            requirements for broadcast scheduling.
            
            The method creates AdspotsInAvail records that link specific advertisement content
            to precise time slots within the broadcast schedule. It maintains the complete
            hierarchical relationship from playlist → window → avail → advertisement and
            provides the granular scheduling data required for broadcast automation systems.
            
            This is the final stage of playlist structure processing, where abstract time
            slots are populated with actual advertisement content. The traffic counter is
            incremented for each successfully processed advertisement, providing accurate
            counts for billing, reporting, and compliance purposes.
                
            Args:
                request (HttpRequest): Django HTTP request object containing:
                                    - Triple-indexed POST data with nested form arrays
                                    - Advertisement IDs for each position within avails
                                    - Form fields indexed by window, avail, and ad position
                                    - User session and authentication information
                avail (Avails): Avail model instance that will contain the advertisements
                            Must be a saved instance with valid primary key
                            Represents the parent advertisement slot for these ads
                window_index (int): Zero-based index of the parent window being processed
                                Used to access correct form fields in nested arrays
                                Corresponds to window position in the playlist
                avail_index (int): Zero-based index of the parent avail being processed
                                Used to access correct form fields in nested arrays
                                Corresponds to avail position within the window
                number_of_ads (int): Total number of advertisements to process for this avail
                                    Must be a positive integer value
                                    Determines the iteration range for advertisement processing
                traffic (int): Current cumulative traffic counter from previous processing
                            Represents total advertisement count processed so far
                            Will be incremented by each advertisement processed here
            
            Returns:
                int: Updated traffic counter including all advertisements processed in this avail
                    Incremented by one for each successfully processed advertisement
                    Used for billing calculations, reporting, and compliance tracking
                    Maintains accurate count across entire playlist hierarchy
                    
            Raises:
                ValueError: If number_of_ads is not a valid positive integer
                AttributeError: If avail instance is invalid or unsaved
                IntegrityError: If advertisement creation violates database constraints
                KeyError: If expected triple-indexed form fields are missing
                
            Database Operations:
                - Creates AdspotsInAvail records linking advertisements to avail slots
                - Sets advertisement position within avail (positioninavail field)
                - Assigns unique traffic ID for each advertisement record
                - Maintains foreign key relationship to Avails model
                
            Form Data Structure Expected:
                - ad[window_index][avail_index][ad_index]: Advertisement ID for each position
                - Triple indexing enables precise advertisement placement
                - Form structure mirrors the complete playlist hierarchy
                
            Processing Flow:
                1. Iterate through each advertisement index from 0 to number_of_ads-1
                2. Extract advertisement ID from triple-indexed form field
                3. Validate advertisement data and skip if invalid
                4. Increment traffic counter for billing and reporting
                5. Create and save AdspotsInAvail database record
                6. Continue to next advertisement or return final traffic count
                
            Traffic Counter Logic:
                - Incremented once per successfully processed advertisement
                - Provides accurate count for revenue calculation
                - Enables compliance reporting and audit trails
                - Supports capacity planning and inventory management
                
            Advertisement Positioning:
                - positioninavail field uses 1-based indexing for business logic
                - Loop uses 0-based indexing for programming convenience
                - Position mapping: loop index k → advertisement position (k + 1)
                - Maintains precise ordering for broadcast playback sequence
                
            Data Validation:
                - Validates advertisement ID is present in form data
                - Skips advertisements with missing or invalid IDs
                - Handles sparse advertisement configurations gracefully
                - Ensures traffic counter accuracy despite validation failures
                
            Performance Considerations:
                - Sequential processing maintains advertisement order within avail
                - Single database query per advertisement creation
                - Efficient triple-indexed form field access using computed keys
                - Early continuation for invalid advertisement configurations
                
            Business Logic:
                - Each advertisement represents billable inventory
                - Advertisement positioning affects broadcast scheduling
                - Traffic IDs enable precise advertisement tracking
                - Position within avail determines playback sequence
                
            Integration Points:
                - AdspotsInAvail model: Database representation of scheduled advertisements
                - Avails model: Parent relationship for hierarchical structure
                - Advertisement inventory system: Source of advertisement content
                - Billing system: Uses traffic counter for revenue calculation
                
            Example Usage:
                final_traffic = self._process_ads(
                    request=request,
                    avail=avail_instance,
                    window_index=0,
                    avail_index=2,
                    number_of_ads=3,
                    traffic=current_traffic_count
                )
                
            Broadcast Integration:
                - Created records drive automated broadcast systems
                - Traffic IDs enable real-time playback tracking
                - Position data ensures correct advertisement sequencing
                - Timing data coordinates with broadcast schedules
        """
        
        # =============================================================================
        # ADVERTISEMENT ITERATION SECTION
        # Process each advertisement sequentially to maintain proper positioning
        # =============================================================================
        # Iterate through each advertisement index to process individual commercial spots
        # Range starts at 0 and continues for the specified number of advertisements
        # Sequential processing ensures proper advertisement ordering within the avail
        for k in range(number_of_ads):
            
            # =============================================================================
            # ADVERTISEMENT FORM DATA EXTRACTION SECTION
            # Extract advertisement-specific data from triple-indexed form fields
            # =============================================================================
            # Construct form field key for advertisement ID using triple indexing
            # Format: ad[window_index][avail_index][ad_index] for nested form arrays
            # This enables precise identification of each advertisement's position
            ad_key = f'ad[{window_index}][{avail_index}][{k}]'
            
            # Extract advertisement spot ID from the computed form field key
            # This ID references the specific advertisement content to be scheduled
            adspot = request.POST.get(ad_key)
            
            # =============================================================================
            # ADVERTISEMENT VALIDATION SECTION
            # Validate required advertisement data and handle missing information
            # =============================================================================
            # Skip processing this advertisement if ID is missing or invalid
            # This allows for sparse advertisement configurations and handles form errors
            if not adspot:
                continue
            
            # =============================================================================
            # TRAFFIC COUNTER INCREMENT SECTION
            # Increment traffic counter for billing and reporting purposes
            # =============================================================================
            # Increment traffic counter for each successfully processed advertisement
            # This provides accurate count for billing, compliance, and reporting
            # Each advertisement represents billable inventory in the broadcast schedule
            traffic += 1
            
            # =============================================================================
            # ADVERTISEMENT DATABASE RECORD CREATION SECTION
            # Create and persist the AdspotsInAvail model instance
            # =============================================================================
            # Create AdspotsInAvail model instance linking advertisement to avail slot
            # This establishes the final level of the playlist hierarchy relationship
            ads_in_avail = AdspotsInAvail(
                avail=avail,                            # Foreign key relationship to parent avail
                adspot_id=adspot,                       # Advertisement content identifier
                # Convert 0-based loop index to 1-based business position
                # Business logic requires advertisement positions to start from 1
                positioninavail=str(k + 1),            # Position of ad within the avail (1-based)
                trafficid=traffic                       # Unique traffic identifier for this ad
            )
            
            # Persist the advertisement record to database with immediate commit
            # This completes the playlist hierarchy and enables broadcast scheduling
            ads_in_avail.save()
        
        # =============================================================================
        # RETURN SECTION
        # Return final traffic counter for upstream processing and reporting
        # =============================================================================
        # Return the updated traffic counter including all advertisements from this avail
        # This value represents the final count for this branch of the playlist hierarchy
        return traffic

    def _calculate_window_duration(self, window_start, window_end):
        """
            Calculate the duration of a broadcast window for scheduling and billing purposes.
            
            This method computes the time span between window start and end times, handling
            time format parsing and duration calculation for broadcast scheduling systems.
            The calculated duration is used for advertisement slot allocation, billing
            calculations, and broadcast automation timing requirements.
            
            The method currently contains placeholder implementation with the core logic
            commented out pending finalization of duration format requirements and
            edge case handling strategies. The preserved implementation shows the
            intended calculation approach using datetime arithmetic.
            
            Key considerations for duration calculation:
                - Handles standard HH:MM time format input
                - Manages day boundary crossings (e.g., 23:30 to 01:30)
                - Provides duration in format suitable for broadcast systems
                - Validates input times and handles parsing errors
                - Supports both same-day and cross-midnight windows
                
            Args:
                window_start (str): Window start time in HH:MM format
                                Examples: "09:30", "14:15", "23:45"
                                Must be valid 24-hour time format
                                Used as the beginning of the broadcast window
                window_end (str): Window end time in HH:MM format
                                Examples: "10:00", "14:45", "00:15"
                                Must be valid 24-hour time format
                                Used as the conclusion of the broadcast window
            
            Returns:
                str: Formatted duration string suitable for broadcast systems
                    Format depends on business requirements (pending implementation)
                    Examples might include: "00:30:00", "1.5 hours", "90 minutes"
                    Currently returns None due to commented implementation
                    
            Raises:
                ValueError: If time strings cannot be parsed in HH:MM format
                TypeError: If input parameters are not strings
                AttributeError: If datetime operations fail due to invalid data
                
            Implementation Status:
                CURRENT: Core logic commented out pending format specification
                PLANNED: Full duration calculation with multiple output formats
                PRESERVED: Implementation approach maintained for easy activation
                
            Time Handling Considerations:
                - Same-day windows: start < end (e.g., 09:00 to 17:00)
                - Cross-midnight windows: start > end (e.g., 23:00 to 01:00)
                - Zero-duration windows: start == end (special case handling)
                - Invalid ranges: end before start on same day
                
            Business Logic Applications:
                - Advertisement slot duration for billing
                - Broadcast schedule timing validation
                - Capacity planning and inventory management
                - Automated playlist generation timing
                - Compliance reporting for broadcast regulations
                
            Example Usage:
                duration = self._calculate_window_duration("09:30", "10:15")
                # Expected result: "00:45:00" or equivalent format
                
                duration = self._calculate_window_duration("23:30", "01:15")
                # Expected result: "01:45:00" (cross-midnight calculation)
                
            Future Enhancements:
                - Multiple output format support (HH:MM:SS, minutes, decimal hours)
                - Timezone handling for multi-region broadcasts
                - Business day vs calendar day duration options
                - Duration validation against minimum/maximum window constraints
                - Integration with broadcast system time standards
                
            Integration Points:
                - Windows model: Duration field for database storage
                - Broadcast scheduling: Timing coordination
                - Billing system: Duration-based pricing calculations
                - Playlist validation: Window timing consistency checks
        """
        
        # =============================================================================
        # DATETIME PARSING SECTION (CURRENTLY COMMENTED)
        # Parse time strings and create datetime objects for calculation
        # =============================================================================
        # Parse window start time and combine with today's date for calculation
        # This approach normalizes both times to the same date for duration arithmetic
        window_start = datetime.datetime.combine(
            datetime.date.today(),                    # Use today's date as baseline
            datetime.datetime.strptime(window_start, "%H:%M").time()  # Parse HH:MM format
        )
        
        # Parse window end time and combine with today's date for calculation
        # Same normalization approach ensures consistent duration calculation
        window_end = datetime.datetime.combine(
            datetime.date.today(),                      # Use today's date as baseline
            datetime.datetime.strptime(window_end, "%H:%M").time()    # Parse HH:MM format
        )
        
        # =============================================================================
        # DURATION CALCULATION SECTION (CURRENTLY COMMENTED)
        # Perform datetime arithmetic to calculate window duration
        # =============================================================================
        # Calculate duration using datetime subtraction
        # This produces a timedelta object representing the time span
        duration = window_end - window_start
        
        # =============================================================================
        # RETURN SECTION (CURRENTLY COMMENTED)
        # Format and return the calculated duration
        # =============================================================================
        # Return the calculated duration in the required format
        # Format specification pending business requirements finalization
        return duration

    def _handle_ftp_upload(self, xml_playlist_res, playlist):
        """
            Handle automated FTP upload of generated XML playlist files to broadcast servers.
            
            This method manages the deployment of generated playlist XML files to remote
            broadcast automation systems via FTP protocol. It provides the critical link
            between playlist creation and broadcast system integration, ensuring that
            created playlists are automatically deployed to the appropriate servers
            for immediate broadcast availability.
            
            The method implements a complete FTP deployment workflow including configuration
            retrieval, connection establishment, file transfer, and error handling. It
            supports multiple broadcast environments and provides robust error recovery
            for network connectivity issues.
            
            Currently, the implementation is disabled pending infrastructure configuration
            and security review. The preserved logic demonstrates the intended workflow
            and can be activated when deployment requirements are finalized.
            
            Args:
                xml_playlist_res (dict): XML generation result containing:
                                        - 'status' (bool): Success status of XML generation
                                        - 'file' (str): Path to generated XML file
                                        - 'filename' (str): Name of the XML file
                                        - 'errors' (list): Any generation errors
                                        - 'metadata' (dict): Additional file information
                playlist (Playlists): Playlist model instance for configuration context
                                    Used to determine target FTP server and deployment path
                                    Contains channel and zone information for routing
            
            Returns:
                None: Method performs side effects (file upload) without return value
                    Success/failure communicated through logging and exception handling
                    
            Raises:
                ConnectionError: If FTP server connection cannot be established
                AuthenticationError: If FTP credentials are invalid or expired
                FileNotFoundError: If generated XML file is missing or inaccessible
                PermissionError: If insufficient permissions for file upload
                TimeoutError: If FTP transfer exceeds configured timeout limits
                
            Implementation Status:
                CURRENT: Implementation disabled with pass statement
                PLANNED: Full FTP deployment with error handling and logging
                PRESERVED: Core logic maintained for easy activation
                SECURITY: Pending security review for credential management
                
            FTP Deployment Workflow (When Active):
                1. Validate XML generation was successful
                2. Retrieve FTP configuration for target playlist
                3. Initialize FTP connector with server credentials
                4. Establish secure connection to broadcast server
                5. Upload XML file to designated remote path
                6. Verify successful transfer and file integrity
                7. Log deployment status for audit trail
                8. Clean up temporary files and connections
                
            Configuration Requirements:
                - FTP server hostnames and ports
                - Authentication credentials (username/password or key-based)
                - Remote directory paths for different channels/zones
                - Transfer mode settings (binary/ASCII)
                - Timeout and retry configurations
                - SSL/TLS encryption settings for secure transfers
                
            Security Considerations:
                - Credential storage and encryption
                - Secure FTP (SFTP) or FTP over SSL/TLS
                - Network access control and firewall rules
                - File permission validation on remote servers
                - Audit logging for compliance and monitoring
                
            Error Handling Strategy:
                - Retry logic for temporary network failures
                - Fallback servers for high availability
                - Graceful degradation when FTP is unavailable
                - Detailed error logging for troubleshooting
                - User notification for critical deployment failures
                
            Performance Optimization:
                - Connection pooling for multiple uploads
                - Compression for large XML files
                - Parallel uploads to multiple servers
                - Transfer progress monitoring and reporting
                - Bandwidth throttling for network management
                
            Integration Points:
                - _get_ftp_config: Retrieves server-specific configuration
                - FTPConnector: Handles low-level FTP operations
                - Logging system: Records deployment activities
                - Monitoring system: Tracks deployment success rates
                - Alert system: Notifies on deployment failures
                
            Example Usage (When Implemented):
                xml_result = generator.generate_schedule(playlist)
                self._handle_ftp_upload(xml_result, playlist)
                
                # Results in XML file deployed to:
                # ftp://broadcast-server/playlists/channel_123/playlist_456.xml
                
            Business Impact:
                - Enables automated broadcast deployment
                - Reduces manual intervention and errors
                - Supports real-time playlist updates
                - Ensures consistent deployment processes
                - Facilitates compliance with broadcast schedules
                
            Monitoring and Analytics:
                - Upload success/failure rates
                - Transfer duration and performance metrics
                - Server availability and connectivity status
                - File integrity verification results
                - Deployment frequency and patterns
        """
        
        # =============================================================================
        # IMPLEMENTATION PLACEHOLDER SECTION
        # Temporary pass statement while implementation is disabled
        # =============================================================================
        # Placeholder implementation while FTP deployment is disabled
        # This maintains the method interface while infrastructure is being configured
        pass
        
        # =============================================================================
        # XML VALIDATION AND FTP DEPLOYMENT SECTION (CURRENTLY DISABLED)
        # Core FTP upload logic preserved for future activation
        # =============================================================================
        # Validate that XML generation was successful before attempting upload
        # Only proceed with FTP deployment if the XML file was generated without errors
        # if xml_playlist_res["status"]:
        #     
        #     # Retrieve FTP configuration specific to this playlist's target environment
        #     # Configuration includes server details, credentials, and deployment paths
        #     ftp_config = self._get_ftp_config(playlist)
        #     
        #     # Initialize FTP connector with server-specific configuration
        #     # This establishes the connection parameters and authentication details
        #     ftp = FTPConnector(**ftp_config)
        #     
        #     # Upload the generated XML file to the configured remote path
        #     # This deploys the playlist to the broadcast automation system
        #     ftp.upload_file(xml_playlist_res["file"], ftp_config['remote_path'])

    def _log_activity(self, playlist, request=None, is_successful=True, error_message=None, duration_ms=None):
        """
        Log comprehensive playlist creation activity for audit trail, compliance, and monitoring.
        
        This method creates detailed audit records for all playlist operations, providing
        complete traceability for compliance, debugging, and business intelligence purposes.
        It captures user actions, system performance, and contextual metadata to enable
        comprehensive analysis of playlist creation patterns and system behavior.
        
        The logging system integrates with the centralized activity tracking framework
        to maintain consistent audit trails across all system operations. This supports
        regulatory compliance, security monitoring, and operational analytics for
        broadcast scheduling systems.
        
        The method handles both successful and failed operations, capturing appropriate
        context and error information to support troubleshooting and system optimization.
        It also records performance metrics to enable capacity planning and performance
        monitoring across the playlist creation workflow.
        
        Args:
            playlist (Playlists): The Playlist model instance that was created or attempted
                                Contains all playlist metadata and relationships
                                Used to extract contextual information for logging
            request (HttpRequest, optional): Django request object containing:
                                            - User authentication information
                                            - Client IP address and user agent
                                            - Session data and request metadata
                                            - HTTP headers for security analysis
            is_successful (bool, optional): Success status of the playlist operation
                                        Defaults to True for successful operations
                                        Used to categorize and filter activity logs
            error_message (str, optional): Detailed error message for failed operations
                                        Provides debugging context for failures
                                        Stored in activity metadata for analysis
            duration_ms (int, optional): Processing duration in milliseconds
                                        Used for performance monitoring and optimization
                                        Enables identification of slow operations
        
        Returns:
            None: Method performs logging side effects without return value
                Success/failure communicated through logging system
                
        Raises:
            AttributeError: If playlist instance lacks required attributes
            ImportError: If Activity or ActivityCategory models are unavailable
            DatabaseError: If activity logging fails due to database issues
            
        Database Operations:
            - Retrieves or handles missing ActivityCategory for 'PLAYLIST'
            - Creates comprehensive Activity record with full metadata
            - Stores user information, timing data, and system context
            - Maintains audit trail for compliance and security monitoring
            
        Metadata Structure:
            - playlist_id: Unique identifier for created playlist
            - channel_id: Channel identifier for scheduling context
            - channel_name: Human-readable channel name
            - broadcast_date: Scheduled broadcast date in ISO format
            - start_date: Window start time in ISO format
            - end_date: Window end time in ISO format
            - zone_channel: Geographic or programming zone identifier
            - version: Playlist version for change tracking
            - is_draft: Draft status for workflow management
            - draft_version: Draft iteration for version control
            
        Security and Compliance:
            - Records user identity and authentication status
            - Captures client IP address for security monitoring
            - Logs user agent for client analysis and security
            - Maintains tamper-evident audit trails
            - Supports regulatory compliance requirements
            
        Performance Monitoring:
            - Records operation duration for optimization
            - Enables identification of performance bottlenecks
            - Supports capacity planning and resource allocation
            - Provides data for system performance analysis
            
        Example Usage:
            # Successful playlist creation
            self._log_activity(
                playlist=created_playlist,
                request=request,
                is_successful=True,
                duration_ms=1250
            )
            
            # Failed playlist creation
            self._log_activity(
                playlist=partial_playlist,
                request=request,
                is_successful=False,
                error_message="Invalid channel configuration",
                duration_ms=450
            )
        """
        
        # =============================================================================
        # ACTIVITY FRAMEWORK IMPORT SECTION
        # Import activity tracking models for audit logging
        # =============================================================================
        # Import Activity and ActivityCategory models for comprehensive audit logging
        # Local import prevents circular dependencies and ensures module availability
        from apps.activities.models import Activity, ActivityCategory
        
        # =============================================================================
        # ACTIVITY CATEGORY RESOLUTION SECTION
        # Retrieve or handle missing activity category for playlist operations
        # =============================================================================
        # Attempt to retrieve the PLAYLIST activity category for proper categorization
        # This enables filtering and analysis of playlist-specific activities
        try:
            # Get the predefined PLAYLIST category for activity classification
            category = ActivityCategory.objects.get(code='PLAYLIST')
        except ActivityCategory.DoesNotExist:
            # Handle case where PLAYLIST category hasn't been created yet
            # Set to None to allow activity creation without category restriction
            category = None
        
        # =============================================================================
        # ACTIVITY DESCRIPTION GENERATION SECTION
        # Build human-readable description of the playlist operation
        # =============================================================================
        # Build descriptive text for the activity log entry
        # Includes channel information for context and scheduling reference
        description = f"Created playlist for {playlist.channel.channel_name if playlist.channel else 'Unknown Channel'}"
        
        # Append broadcast date information if available for scheduling context
        if playlist.broadcast_date:
            description += f" scheduled for {playlist.broadcast_date.strftime('%Y-%m-%d')}"
        
        # =============================================================================
        # COMPREHENSIVE METADATA COLLECTION SECTION
        # Gather detailed playlist information for audit and analysis purposes
        # =============================================================================
        # Build comprehensive metadata dictionary with all relevant playlist information
        # This provides complete context for audit trails and business intelligence
        metadata = {
            'playlist_id': str(playlist.id),                                                    # Unique playlist identifier
            'channel_id': playlist.channel.id_channel if playlist.channel else None,          # Channel reference
            'channel_name': playlist.channel.channel_name if playlist.channel else None,      # Human-readable channel name
            # Format dates in ISO format for consistent parsing and analysis
            'broadcast_date': playlist.broadcast_date.isoformat() if playlist.broadcast_date else None,   # Scheduled broadcast date
            'start_date': playlist.start_date.isoformat() if playlist.start_date else None,               # Playlist start time
            'end_date': playlist.end_date.isoformat() if playlist.end_date else None,                     # Playlist end time
            'zone_channel': playlist.zone_channel.region if playlist.zone_channel else None,             # Geographic/programming zone
            'version': playlist.version,                                                        # Playlist version number
            'is_draft': playlist.is_draft,                                                     # Draft status flag
            'draft_version': playlist.draft_version,                                           # Draft iteration number
        }
        
        # =============================================================================
        # ACTIVITY LOGGING EXECUTION SECTION
        # Create comprehensive activity record with all collected information
        # =============================================================================
        # Create comprehensive activity log entry using the centralized logging system
        # This maintains consistent audit trails across all system operations
        Activity.log_activity(
            user=request.user,                                                  # Authenticated user performing the action
            action="CREATE",                                                    # Standardized action type for filtering
            # Build detailed description including user context for audit purposes
            description=f"User {request.user.email if request.user else 'System'} performed create_playlist",
            request=request,                                                    # Full request context for security analysis
            ip_address=get_client_ip(request),                                 # Client IP for security monitoring
            user_agent=request.META.get("HTTP_USER_AGENT", ""),               # Client browser/app for analysis
            metadata=metadata                                                   # Complete playlist context and metadata
        )  

    def _handle_draft_xml_generation(self, data, playlist):
        """
        Generate XML preview files for draft playlists without production deployment.
        
        This method creates XML representations of draft playlists for preview,
        validation, and testing purposes without deploying to production broadcast
        systems. It enables users to review the generated schedule format and
        validate playlist structure before committing to live broadcast deployment.
        
        Draft XML generation serves multiple purposes:
        - Preview generation for user review and approval workflows
        - Format validation before production deployment
        - Testing and development of playlist structures
        - Compliance verification without affecting live broadcasts
        - Template generation for repeated playlist patterns
        
        The method currently contains placeholder implementation with core logic
        commented out pending infrastructure setup for draft XML processing and
        storage systems. The preserved logic demonstrates the intended workflow
        for generating and managing draft XML files.
        
        Args:
            data (dict): Validated playlist data dictionary containing:
                        - 'daydate': Formatted broadcast date for scheduling
                        - 'channel_id': Channel identifier for routing
                        - 'channel_zone': Zone configuration for regional settings
                        - Additional scheduling and configuration parameters
            playlist (Playlists): Draft playlist model instance containing:
                                - Playlist structure and timing information
                                - Channel and zone relationships
                                - Draft-specific metadata and version information
        
        Returns:
            None: Method performs XML generation side effects without return value
                Generated files managed through file system or storage service
                Success/failure communicated through logging and exception handling
                
        Raises:
            AttributeError: If required data fields are missing or invalid
            FileNotFoundError: If XML generation templates are unavailable
            PermissionError: If insufficient permissions for file creation
            ValidationError: If playlist data fails XML schema validation
            
        Implementation Status:
            CURRENT: Core logic commented out pending infrastructure setup
            PLANNED: Full draft XML generation with preview capabilities
            PRESERVED: Implementation approach maintained for easy activation
            INTEGRATION: Pending storage and preview system configuration
            
        Draft XML Workflow (When Active):
            1. Extract playlist scheduling parameters from data dictionary
            2. Initialize XML generator with draft-specific configuration
            3. Generate XML content from playlist structure and timing
            4. Validate generated XML against broadcast system schemas
            5. Store XML file in draft storage location for preview
            6. Create preview URLs or file references for user access
            7. Log generation activity for audit and monitoring
            
        XML Generation Parameters:
            - daydate: Target broadcast date for scheduling context
            - channel_id: Channel identifier for content routing
            - zone_channel_id: Zone identifier for regional configuration
            - version: Draft version identifier for tracking
            
        Storage Considerations:
            - Draft XML files stored separately from production files
            - Temporary storage with configurable retention policies
            - Access control for draft file security and privacy
            - Version tracking for iterative draft development
            - Cleanup policies for abandoned drafts
            
        Preview Integration:
            - Web interface for XML preview and validation
            - Download capabilities for offline review
            - Diff comparison with previous draft versions
            - Integration with approval and collaboration workflows
            - Format validation and compliance checking
            
        Business Applications:
            - Client approval processes for broadcast schedules
            - Compliance review before production deployment
            - Testing new playlist configurations safely
            - Training and education on playlist structures
            - Template development for repeated patterns
            
        Example Usage (When Implemented):
            validated_data = self._extract_common_data(request)
            self._handle_draft_xml_generation(validated_data, draft_playlist)
            
            # Results in draft XML file created at:
            # /drafts/xml/channel_123/draft_playlist_456_v1.xml
            
        Integration Points:
            - GenerateXMLfromDatabase: Core XML generation functionality
            - Draft storage system: File management and retention
            - Preview interface: User access to generated XML
            - Validation system: Schema and compliance checking
            - Logging system: Activity tracking and monitoring
        """
        
        # =============================================================================
        # IMPLEMENTATION PLACEHOLDER SECTION
        # Temporary pass statement while draft XML generation is disabled
        # =============================================================================
        # Placeholder implementation while draft XML generation infrastructure is configured
        # This maintains the method interface while storage and preview systems are developed
        pass
        
        # =============================================================================
        # DRAFT XML GENERATION SECTION (CURRENTLY DISABLED)
        # Core draft XML generation logic preserved for future activation
        # =============================================================================
        # Generate XML filename and content using draft-specific parameters
        # This creates preview XML without affecting production broadcast systems
        # xmlfilename = GenerateXMLfromDatabase(
        #     data['daydate'],                              # Target broadcast date for scheduling
        #     data['channel_id'],                           # Channel identifier for content routing
        #     data['channel_zone'].id_zone_channel,        # Zone identifier for regional settings
        #     '1'                                           # Draft version identifier for tracking
        # )
        # 
        # Additional draft-specific processing and storage logic would be implemented here
        # This might include preview URL generation, validation, and user notification



class PlaylistDetailView(LoginRequiredMixin, View):
    """
    Detailed view for individual playlist with related data.
    """
    
    def get(self, request, id_playlist):
        """
        Display detailed playlist information.
        
        Args:
            request: Django HTTP request object
            id_playlist: Playlist ID
            
        Returns:
            HttpResponse: Rendered template
        """
        try:
            # playlist = Playlists.objects.select_related(
            #     'channel',
            #     'zone_channel'
            # ).prefetch_related(
            #     'windows_set__avails_set__adspotsinAvail_set__id_adspot'
            # ).get(
            #     id=id_playlist,
            #     channel__id_user=request.user
            # )
            playlist = Playlists.objects.select_related(
                'channel',
                'zone_channel'
            ).get(
                id=id_playlist,
                channel__id_user=request.user
            )
            
            context = {
                'playlist': playlist,
                'windows': self._get_playlist_windows(playlist),
                'stats': self._get_detailed_stats(playlist),
                'timeline': self._get_playlist_timeline(playlist),
            }
            
            return render(request, 'playlists/detail.html', context)
            
        except Playlists.DoesNotExist:
            return render(request, 'playlists/not_found.html', status=404)
    
    def _get_playlist_windows(self, playlist):
        """
        Get organized window data for the playlist.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            list: Organized window data
        """
        windows_data = []
        
        for window in playlist.windows.all().order_by('window_start'):
            avails_data = []
            
            for avail in window.avails.all().order_by('availinwindow'):
                ads_data = []
                
                for ad in avail.adspots_in_avail.all().order_by('positioninavail'):
                    ads_data.append({
                        'adspot': ad.adspot,
                        'position': ad.positioninavail,
                        'traffic_id': ad.trafficid,
                    })
                
                avails_data.append({
                    'avail': avail,
                    'ads': ads_data,
                })
            
            windows_data.append({
                'window': window,
                'avails': avails_data,
            })
        
        return windows_data
    
    def _get_detailed_stats(self, playlist):
        """
        Get detailed statistics for the playlist.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            dict: Detailed statistics
        """
        windows = playlist.windows.all()
        total_avails = sum(window.avails.count() for window in windows)
        total_ads = sum(
            avail.adspots_in_avail.count() 
            for window in windows 
            for avail in window.avails.all()
        )
        
        return {
            'total_windows': windows.count(),
            'total_avails': total_avails,
            'total_ads': total_ads,
            'avg_ads_per_avail': round(total_ads / total_avails, 2) if total_avails > 0 else 0,
            'duration_info': self._calculate_total_duration(windows),
        }
    
    def _calculate_total_duration(self, windows):
        """
        Calculate total duration information.
        
        Args:
            windows: Window queryset
            
        Returns:
            dict: Duration information
        """
        total_seconds = 0
        
        for window in windows:
            # window.window_duration is a DurationField (timedelta object)
            if window.window_duration:
                total_seconds += window.window_duration.total_seconds()
        
        total_minutes = total_seconds / 60
        
        return {
            'total_minutes': round(total_minutes, 2),
            'total_hours': round(total_minutes / 60, 2),
            'total_seconds': round(total_seconds, 2),  # Added for completeness
        }

    def _get_playlist_timeline(self, playlist):
        """
        Get timeline information for the playlist.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            list: Timeline events
        """
        from datetime import datetime, date, time
        from django.utils import timezone
        
        timeline = [
            {
                'event': 'Playlist Created',
                'datetime': playlist.created_at,
                'type': 'creation',
                'description': f'Playlist version {playlist.version} created'
            }
        ]
        
        # Add window-based events
        for window in playlist.windows.all().order_by('window_start'):
            # Convert time to datetime for proper comparison
            if isinstance(window.window_start, time):
                # Combine with playlist date or today's date
                playlist_date = getattr(playlist, 'date', date.today())
                window_datetime = datetime.combine(playlist_date, window.window_start)
                
                # Make timezone-aware to match playlist.created_at
                if timezone.is_naive(window_datetime):
                    window_datetime = timezone.make_aware(window_datetime)
            else:
                # If it's already a datetime, ensure it's timezone-aware
                window_datetime = window.window_start
                if timezone.is_naive(window_datetime):
                    window_datetime = timezone.make_aware(window_datetime)
                    
            timeline.append({
                'event': 'Window Scheduled',
                'datetime': window_datetime,
                'type': 'window',
                'description': f'Window with {window.avails.count()} avails'
            })
        
        return sorted(timeline, key=lambda x: x['datetime'])


class PlaylistListView(LoginRequiredMixin, View):
    """
    Class-based view for listing and filtering playlists using base View class.
    Supports search, filtering, sorting, and pagination with full control.
    """
    
    def get(self, request):
        """
        Handle GET requests for playlist listing.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            HttpResponse or JsonResponse for AJAX requests
        """
        # Check for AJAX requests
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return self._handle_ajax_request(request)
        
        # Get filtered queryset
        queryset = self._get_filtered_queryset(request)
        
        # Apply pagination
        paginator = Paginator(queryset, self._get_items_per_page(request))
        page_number = request.GET.get('page', 1)
        
        try:
            page_obj = paginator.get_page(page_number)
        except PageNotAnInteger:
            page_obj = paginator.get_page(1)
        except EmptyPage:
            page_obj = paginator.get_page(paginator.num_pages)
        
        # Build context
        context = self._build_context(request, page_obj, paginator)
        
        return render(request, 'playlists/list.html', context)
    
    def post(self, request):
        """
        Handle POST requests for bulk operations.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse with operation results
        """
        action = request.POST.get('action')
        playlist_ids = request.POST.getlist('playlist_ids')
        
        if not playlist_ids:
            return JsonResponse({
                'success': False,
                'message': 'No playlists selected.'
            })
        
        if action == 'bulk_delete':
            return self._handle_bulk_delete(request, playlist_ids)
        elif action == 'bulk_export':
            return self._handle_bulk_export(request, playlist_ids)
        elif action == 'bulk_status_change':
            return self._handle_bulk_status_change(request, playlist_ids)
        
        return JsonResponse({
            'success': False,
            'message': 'Invalid action.'
        })
    
    def _get_filtered_queryset(self, request):
        """
        Get filtered and sorted queryset based on request parameters.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            QuerySet: Filtered playlist queryset
        """
        user = request.user
        
        # Base queryset with optimizations
        queryset = Playlists.objects.select_related(
            'channel',
            'zone_channel'
        ).prefetch_related(
            'windows',
            'windows__avails',
            'windows__avails__adspots_in_avail'
        ).annotate(
            window_count=Count('windows', distinct=True),
            total_avails=Count('windows__avails', distinct=True),
            total_ads=Count('windows__avails__adspots_in_avail', distinct=True)
        )
        # .filter(
        #     channel__id_user=user   
        # )
        
        # Apply filters
        queryset = self._apply_search_filter(request, queryset)
        queryset = self._apply_channel_filter(request, queryset)
        queryset = self._apply_date_filter(request, queryset)
        queryset = self._apply_status_filter(request, queryset)
        queryset = self._apply_zone_filter(request, queryset)
        
        # Apply sorting
        queryset = self._apply_sorting(request, queryset)
        
        return queryset
    
    def _apply_search_filter(self, request, queryset):
        """
        Apply search filter based on search query.
        
        Args:
            request: Django HTTP request object
            queryset: Base queryset
            
        Returns:
            QuerySet: Filtered queryset
        """
        search_query = request.GET.get('search', '').strip()
        
        if search_query:
            queryset = queryset.filter(
                Q(channel__channel_name__icontains=search_query) |
                Q(zone_channel__networkname__icontains=search_query) |
                Q(zone_channel__region__icontains=search_query) |
                Q(broadcast_date__icontains=search_query) |
                Q(version__icontains=search_query)
            )
        
        return queryset
    
    def _apply_channel_filter(self, request, queryset):
        """
        Apply channel filter.
        
        Args:
            request: Django HTTP request object
            queryset: Base queryset
            
        Returns:
            QuerySet: Filtered queryset
        """
        channel_id = request.GET.get('channel')
        
        if channel_id and channel_id != 'all':
            try:
                channel_id = int(channel_id)
                queryset = queryset.filter(channel__id_channel=channel_id)
            except (ValueError, TypeError):
                pass
        
        return queryset
    
    def _apply_date_filter(self, request, queryset):
        """
        Apply date range filter with predefined and custom ranges.
        
        Args:
            request: Django HTTP request object
            queryset: Base queryset
            
        Returns:
            QuerySet: Filtered queryset
        """
        date_from = request.GET.get('date_from')
        date_to = request.GET.get('date_to')
        date_range = request.GET.get('date_range')
        
        # Handle predefined date ranges
        if date_range:
            today = timezone.now().date()
            
            if date_range == 'today':
                queryset = queryset.filter(broadcast_date=today)
            elif date_range == 'yesterday':
                yesterday = today - datetime.timedelta(days=1)
                queryset = queryset.filter(broadcast_date=yesterday)
            elif date_range == 'this_week':
                week_start = today - datetime.timedelta(days=today.weekday())
                queryset = queryset.filter(broadcast_date__gte=week_start)
            elif date_range == 'last_week':
                week_start = today - datetime.timedelta(days=today.weekday() + 7)
                week_end = week_start + datetime.timedelta(days=6)
                queryset = queryset.filter(
                    broadcast_date__gte=week_start,
                    broadcast_date__lte=week_end
                )
            elif date_range == 'this_month':
                month_start = today.replace(day=1)
                queryset = queryset.filter(broadcast_date__gte=month_start)
            elif date_range == 'last_month':
                if today.month == 1:
                    last_month = today.replace(year=today.year - 1, month=12, day=1)
                else:
                    last_month = today.replace(month=today.month - 1, day=1)
                
                # Calculate last day of previous month
                if last_month.month == 12:
                    month_end = last_month.replace(year=last_month.year + 1, month=1, day=1) - datetime.timedelta(days=1)
                else:
                    month_end = last_month.replace(month=last_month.month + 1, day=1) - datetime.timedelta(days=1)
                
                queryset = queryset.filter(
                    broadcast_date__gte=last_month,
                    broadcast_date__lte=month_end
                )
        
        # Handle custom date range
        if date_from:
            try:
                date_from_parsed = datetime.datetime.strptime(date_from, '%Y-%m-%d').date()
                queryset = queryset.filter(broadcast_date__gte=date_from_parsed)
            except ValueError:
                pass
        
        if date_to:
            try:
                date_to_parsed = datetime.datetime.strptime(date_to, '%Y-%m-%d').date()
                queryset = queryset.filter(broadcast_date__lte=date_to_parsed)
            except ValueError:
                pass
        
        return queryset
    
    def _apply_status_filter(self, request, queryset):
        """
        Apply status filter (draft/published).
        
        Args:
            request: Django HTTP request object
            queryset: Base queryset
            
        Returns:
            QuerySet: Filtered queryset
        """
        status = request.GET.get('status')
        
        if status == 'draft':
            queryset = queryset.filter(is_draft='1')
        elif status == 'published':
            queryset = queryset.filter(is_draft='0')
        
        return queryset
    
    def _apply_zone_filter(self, request, queryset):
        """
        Apply zone/region filter.
        
        Args:
            request: Django HTTP request object
            queryset: Base queryset
            
        Returns:
            QuerySet: Filtered queryset
        """
        zone_id = request.GET.get('zone')
        
        if zone_id and zone_id != 'all':
            try:
                zone_id = int(zone_id)
                queryset = queryset.filter(zone_channel__id_zone_channel=zone_id)
            except (ValueError, TypeError):
                pass
        
        return queryset
    
    def _apply_sorting(self, request, queryset):
        """
        Apply sorting based on sort parameter.
        
        Args:
            request: Django HTTP request object
            queryset: Base queryset
            
        Returns:
            QuerySet: Sorted queryset
        """
        sort_by = request.GET.get('sort_by', '-created_at')
        
        valid_sort_fields = [
            'created_at', '-created_at',
            'broadcast_date', '-broadcast_date',
            'channel__channel_name', '-channel__channel_name',
            'version', '-version',
            'window_count', '-window_count',
            'total_ads', '-total_ads',
            'total_avails', '-total_avails',
            'is_draft', '-is_draft'
        ]
        
        if sort_by in valid_sort_fields:
            queryset = queryset.order_by(sort_by)
        else:
            # Default sorting
            queryset = queryset.order_by('-created_at')
        
        return queryset
    
    def _get_items_per_page(self, request):
        """
        Get number of items per page from request or default.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            int: Items per page
        """
        try:
            per_page = int(request.GET.get('per_page', 20))
            # Limit to reasonable range
            return max(10, min(per_page, 100))
        except (ValueError, TypeError):
            return 20
    
    def _build_context(self, request, page_obj, paginator):
        """
        Build template context with all necessary data.
        
        Args:
            request: Django HTTP request object
            page_obj: Paginated page object
            paginator: Paginator instance
            
        Returns:
            dict: Template context
        """
        user = request.user
        
        context = {
            # Pagination data
            'playlists': page_obj.object_list,
            'page_obj': page_obj,
            'paginator': paginator,
            'is_paginated': paginator.num_pages > 1,
            
            # Filter data
            'channels': self._get_user_channels(user),
            'zones': self._get_available_zones(user),
            'filter_params': self._get_current_filters(request),
            'sort_options': self._get_sort_options(),
            
            # Statistics
            'stats': self._get_playlist_stats(user),
            
            # Additional data
            'date_range': self._get_date_range(),
            'total_results': paginator.count,
            'current_page': page_obj.number,
            'total_pages': paginator.num_pages,
        }
        
        return context
    
    def _get_user_channels(self, user):
        """
        Get channels available to the current user.
        
        Args:
            user: Current user object
            
        Returns:
            QuerySet: User's channels
        """
        # return Channel.objects.filter(id_user=user).order_by('channel_name')
        # we will return all active channels 
        return Channel.objects.filter(status="active").order_by('name')
    
    def _get_available_zones(self, user):
        """
        Get zones available to the current user.
        
        Args:
            user: Current user object
            
        Returns:
            QuerySet: Available zones
        """
        # return ChannelZone.objects.filter(
        #     id_channel__id_user=user
        # ).distinct().order_by('networkname', 'region')
        # For Now We will get all zones
        return ChannelZone.objects.filter(is_active=True)
    
    def _get_current_filters(self, request):
        """
        Get current filter parameters for template.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            dict: Current filter values
        """
        return {
            'search': request.GET.get('search', ''),
            'channel': request.GET.get('channel', 'all'),
            'zone': request.GET.get('zone', 'all'),
            'status': request.GET.get('status', 'all'),
            'date_from': request.GET.get('date_from', ''),
            'date_to': request.GET.get('date_to', ''),
            'date_range': request.GET.get('date_range', ''),
            'sort_by': request.GET.get('sort_by', '-created_at'),
            'per_page': request.GET.get('per_page', '20'),
        }
    
    def _get_sort_options(self):
        """
        Get available sorting options.
        
        Returns:
            list: Sort options for template
        """
        return [
            {'value': '-created_at', 'label': 'Newest First'},
            {'value': 'created_at', 'label': 'Oldest First'},
            {'value': '-broadcast_date', 'label': 'Broadcast Date (Newest)'},
            {'value': 'broadcast_date', 'label': 'Broadcast Date (Oldest)'},
            {'value': 'channel__channel_name', 'label': 'Channel Name (A-Z)'},
            {'value': '-channel__channel_name', 'label': 'Channel Name (Z-A)'},
            {'value': '-window_count', 'label': 'Most Windows'},
            {'value': '-total_ads', 'label': 'Most Ads'},
            {'value': '-total_avails', 'label': 'Most Avails'},
            {'value': 'is_draft', 'label': 'Published First'},
            {'value': '-is_draft', 'label': 'Drafts First'},
        ]
        
    def _get_playlist_stats(self, user):
        """
            Get playlist statistics for dashboard.

            Args:
            user: Current user object

            Returns:
            dict: Playlist statistics
        """

        base_queryset = Playlists.objects.filter(channel__id_user=user)
        today = timezone.now().date()
        week_ago = today - datetime.timedelta(days=7)

        return {
            'total_playlists': base_queryset.count(),
            'draft_playlists': base_queryset.filter(is_draft='1').count(),
            'published_playlists': base_queryset.filter(is_draft='0').count(),
            'todays_playlists': base_queryset.filter(broadcast_date=today).count(),
            'recent_playlists': base_queryset.filter(
                created_at__gte=timezone.now() - datetime.timedelta(days=7)
            ).count(),
            'upcoming_playlists': base_queryset.filter(
                broadcast_date__gt=today,
                is_draft='0'
            ).count(),
            'past_playlists': base_queryset.filter(
                broadcast_date__lt=today
            ).count(),
        } 

    def _get_date_range(self):
        """
        Get date range information for filtering.
        
        Returns:
            dict: Date range options
        """
        today = timezone.now().date()
        
        return {
            'today': today,
            'yesterday': today - datetime.timedelta(days=1),
            'week_start': today - datetime.timedelta(days=today.weekday()),
            'month_start': today.replace(day=1),
            'min_date': today - datetime.timedelta(days=365),  # One year ago
            'max_date': today + datetime.timedelta(days=365),  # One year ahead
        }
    
    def _handle_ajax_request(self, request):
        """
        Handle AJAX requests for dynamic updates.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse: JSON response with data
        """
        action = request.GET.get('action')
        
        if action == 'get_zones':
            return self._get_zones_for_channel_ajax(request)
        elif action == 'quick_stats':
            return self._get_quick_stats_ajax(request)
        elif action == 'export_data':
            return self._export_playlist_data_ajax(request)
        elif action == 'load_more':
            return self._load_more_playlists_ajax(request)
        
        # Default: return filtered playlist data
        return self._get_filtered_playlists_ajax(request)
    
    def _get_zones_for_channel_ajax(self, request):
        """
        Get zones for a specific channel via AJAX.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse: JSON response with zone data
        """
        channel_id = request.GET.get('channel_id')
        
        if not channel_id:
            return JsonResponse({'success': False, 'error': 'Channel ID required'})
        
        try:
            channel_id = int(channel_id)
            zones = ChannelZone.objects.filter(
                id_channel_id=channel_id
            ).values('id_zone_channel', 'networkname', 'region')
            
            return JsonResponse({
                'success': True,
                'zones': list(zones)
            })
        except (ValueError, TypeError):
            return JsonResponse({'success': False, 'error': 'Invalid channel ID'})
    
    def _get_quick_stats_ajax(self, request):
        """
        Get quick statistics via AJAX.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse: JSON response with statistics
        """
        user = request.user
        stats = self._get_playlist_stats(user)
        
        return JsonResponse({
            'success': True,
            'stats': stats
        })
    
    def _export_playlist_data_ajax(self, request):
        """
        Export playlist data via AJAX.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse: JSON response with export status
        """
        # Get filtered queryset for export
        queryset = self._get_filtered_queryset(request)
        
        # For now, return success with count
        # In a real implementation, you would generate a file and return the URL
        return JsonResponse({
            'success': True,
            'message': f'Export prepared for {queryset.count()} playlists',
            'download_url': '#',  # Replace with actual download URL
            'count': queryset.count()
        })
    
    def _load_more_playlists_ajax(self, request):
        """
        Load more playlists for infinite scroll.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse: JSON response with more playlists
        """
        page = request.GET.get('page', 1)
        per_page = self._get_items_per_page(request)
        
        queryset = self._get_filtered_queryset(request)
        paginator = Paginator(queryset, per_page)
        
        try:
            page_obj = paginator.get_page(page)
        except (PageNotAnInteger, EmptyPage):
            return JsonResponse({'success': False, 'error': 'Invalid page'})
        
        playlists_data = []
        for playlist in page_obj.object_list:
            playlists_data.append({
                'id': playlist.id_playlist,
                'channel_name': playlist.id_channel.channel_name,
                'zone': f"{playlist.id_zone_channel.networkname} - {playlist.id_zone_channel.region}",
                'broadcast_date': playlist.broadcast_date.strftime('%Y-%m-%d'),
                'version': playlist.version,
                'is_draft': playlist.is_draft == '1',
                'window_count': getattr(playlist, 'window_count', 0),
                'total_ads': getattr(playlist, 'total_ads', 0),
                'total_avails': getattr(playlist, 'total_avails', 0),
                'creation_date': playlist.created_at.strftime('%Y-%m-%d %H:%M'),
                'detail_url': f'/playlists/{playlist.id_playlist}/',
                'edit_url': f'/playlists/{playlist.id_playlist}/edit/',
            })
        
        return JsonResponse({
            'success': True,
            'playlists': playlists_data,
            'has_next': page_obj.has_next(),
            'next_page': page_obj.next_page_number() if page_obj.has_next() else None,
            'total_pages': paginator.num_pages,
            'current_page': page_obj.number
        })
    
    def _get_filtered_playlists_ajax(self, request):
        """
        Get filtered playlists for AJAX requests.
        
        Args:
            request: Django HTTP request object
            
        Returns:
            JsonResponse: JSON response with filtered playlists
        """
        queryset = self._get_filtered_queryset(request)
        
        # Limit results for AJAX to avoid large responses
        limit = min(int(request.GET.get('limit', 50)), 100)
        playlists = queryset[:limit]
        
        playlists_data = []
        for playlist in playlists:
            playlists_data.append({
                'id': playlist.id,
                'channel_name': playlist.channel.name,
                'zone': f"{playlist.zone_channel.networkname} - {playlist.zone_channel.region}",
                'broadcast_date': playlist.broadcast_date.strftime('%Y-%m-%d'),
                'version': playlist.version,
                'is_draft': playlist.is_draft == '1',
                'window_count': getattr(playlist, 'window_count', 0),
                'total_ads': getattr(playlist, 'total_ads', 0),
                'creation_date': playlist.created_at.strftime('%Y-%m-%d %H:%M'),
            })
        
        return JsonResponse({
            'success': True,
            'playlists': playlists_data,
            'total_count': queryset.count(),
            'displayed_count': len(playlists_data)
        })
    
    def _handle_bulk_delete(self, request, playlist_ids):
        """
        Handle bulk deletion of playlists.
        
        Args:
            request: Django HTTP request object
            playlist_ids: List of playlist IDs to delete
            
        Returns:
            JsonResponse: Result of bulk operation
        """
        try:
            user = request.user
            deleted_count = 0
            errors = []
            
            for playlist_id in playlist_ids:
                try:
                    playlist = Playlists.objects.get(
                        id_playlist=playlist_id,
                        id_channel__id_user=user
                    )
                    
                    # Check if can delete (same logic as single delete)
                    today = datetime.date.today()
                    if playlist.broadcast_date >= today and playlist.is_draft == '0':
                        errors.append(f"Cannot delete active playlist {playlist_id}")
                        continue
                    
                    # Delete playlist and related objects
                    self._delete_playlist_cascade(playlist)
                    deleted_count += 1
                    
                except Playlists.DoesNotExist:
                    errors.append(f"Playlist {playlist_id} not found")
                except Exception as e:
                    errors.append(f"Error deleting playlist {playlist_id}: {str(e)}")
            
            return JsonResponse({
                'success': True,
                'deleted_count': deleted_count,
                'errors': errors,
                'message': f'Successfully deleted {deleted_count} playlists'
            })
            
        except Exception as e:
            return JsonResponse({
                'success': False,
                'message': f'Bulk delete failed: {str(e)}'
            })
    
    def _handle_bulk_export(self, request, playlist_ids):
        """
        Handle bulk export of playlists.
        
        Args:
            request: Django HTTP request object
            playlist_ids: List of playlist IDs to export
            
        Returns:
            JsonResponse: Result of bulk operation
        """
        # Placeholder for bulk export functionality
        return JsonResponse({
            'success': True,
            'message': f'Export prepared for {len(playlist_ids)} playlists',
            'download_url': '#'  # Replace with actual export URL
        })
    
    def _handle_bulk_status_change(self, request, playlist_ids):
        """
        Handle bulk status change (draft/published).
        
        Args:
            request: Django HTTP request object
            playlist_ids: List of playlist IDs to update
            
        Returns:
            JsonResponse: Result of bulk operation
        """
        new_status = request.POST.get('new_status')
        
        if new_status not in ['0', '1']:  # 0 = published, 1 = draft
            return JsonResponse({
                'success': False,
                'message': 'Invalid status value'
            })
        
        try:
            user = request.user
            updated_count = 0
            
            playlists = Playlists.objects.filter(
                id_playlist__in=playlist_ids,
                id_channel__id_user=user
            )
            
            for playlist in playlists:
                playlist.is_draft = new_status
                playlist.save()
                updated_count += 1
            
            status_name = 'draft' if new_status == '1' else 'published'
            
            return JsonResponse({
                'success': True,
                'updated_count': updated_count,
                'message': f'Successfully updated {updated_count} playlists to {status_name}'
            })
            
        except Exception as e:
            return JsonResponse({
                'success': False,
                'message': f'Bulk status update failed: {str(e)}'
            })
    
    def _delete_playlist_cascade(self, playlist):
        """
        Delete playlist and all related objects in proper order.
        
        Args:
            playlist: Playlist model instance
        """
        # Delete in reverse order of creation to maintain referential integrity
        for window in playlist.windows_set.all():
            for avail in window.avails_set.all():
                avail.adspotsinAvail_set.all().delete()
            window.avails_set.all().delete()
        playlist.windows_set.all().delete()
        playlist.delete()


class PlaylistEditView(LoginRequiredMixin, View):
    """
    Class-based view for editing playlists with form handling.
    Combines the functionality of the original new_playlist view but for editing.
    """
    
    def get(self, request, playlist_id):
        """
        Handle GET requests - render the edit form with existing data.
        
        Args:
            request: Django HTTP request object
            playlist_id: ID of the playlist to edit
            
        Returns:
            Rendered edit form template
        """
        playlist = get_object_or_404(
            Playlists.objects.select_related(
                'channel', 
                'zone_channel'
            ).prefetch_related('windows__avails__adspots_in_avail'),
            id=playlist_id,
            channel__id_user=request.user
        )
        
        # Check if playlist can be edited
        if not self._can_edit_playlist(playlist):
            messages.error(request, "This playlist cannot be edited.")
            return redirect('playlists:playlist_detail', id_playlist=playlist_id)
        
        # Get user channels for form
        channels = Channel.objects.filter(id_user=request.user)
        
        # Get zones for the current channel
        zones = ChannelZone.objects.filter(id_channel=playlist.channel)
        
        # Structure existing data for the form
        playlist_data = self._structure_playlist_data(playlist)
        
        context = {
            'playlist': playlist,
            'playlist_data': playlist_data,
            'data': {'channels': channels},
            'zones': zones,
            'is_edit': True
        }
        
        return render(request, "playlists/edit.html", context)
    
    def post(self, request, playlist_id):
        """
        Handle POST requests - update the playlist.
        
        Args:
            request: Django HTTP request object
            playlist_id: ID of the playlist to edit
            
        Returns:
            Redirect to appropriate page
        """
        playlist = get_object_or_404(
            Playlists.objects.select_related('channel', 'zone_channel'),
            id=playlist_id,
            channel__id_user=request.user
        )
        
        # Check if playlist can be edited
        if not self._can_edit_playlist(playlist):
            messages.error(request, "This playlist cannot be edited.")
            return redirect('playlists:playlist_detail', id_playlist=playlist_id)
        
        # Determine action
        apply_btn = request.POST.get("apply_btn")
        draft_btn = request.POST.get("draft_btn")
        
        if apply_btn:
            return self._handle_update_playlist(request, playlist)
        elif draft_btn:
            return self._handle_save_as_draft(request, playlist)
        
        return redirect("playlists:edit_playlist", playlist_id=playlist_id)
    
    def _can_edit_playlist(self, playlist):
        """
        Check if playlist can be edited.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            bool: True if playlist can be edited
        """
        # Can edit if it's a draft or if broadcast date is in the future
        today = datetime.date.today()
        
        if playlist.is_draft:
            return True
        
        if playlist.broadcast_date >= today or (datetime.datetime.now().time() <= datetime.time(0, 30)):
            return True 
        
        return False
    
    def _structure_playlist_data(self, playlist):
        """
        Structure existing playlist data for form rendering.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            dict: Structured playlist data
        """
        data = {
            'windows': [],
            'channel_id': playlist.channel.id_channel,
            'zone_id': playlist.zone_channel.id_zone_channel,
            'broadcast_date': playlist.broadcast_date.strftime('%m/%d/%Y'),
            'version': playlist.version,
            'is_draft': playlist.is_draft == '1'
        }
        
        for window in playlist.windows.all().order_by('created_at'):
            window_data = {
                'id': window.id,
                'start_time': window.window_start.strftime('%H:%M'),
                'end_time': window.window_end.strftime('%H:%M'),
                'avails': []
            }
            
            for avail in window.avails.all().order_by('availinwindow'):
                avail_data = {
                    'id': avail.id,
                    'start_time': avail.avail_start.strftime('%H:%M'),
                    'position': avail.availinwindow,
                    'ads': []
                }
                
                for ad in avail.adspots_in_avail.all().order_by('positioninavail'):
                    avail_data['ads'].append({
                        'id': ad.id,
                        'adspot_id': ad.adspot.id,
                        'position': ad.positioninavail,
                        'traffic_id': ad.trafficid
                    })
                
                window_data['avails'].append(avail_data)
            
            data['windows'].append(window_data)
        
        return data
    
    def _handle_update_playlist(self, request, playlist):
        """
        Handle playlist update (apply changes).
        
        Args:
            request: Django HTTP request object
            playlist: Playlist model instance
            
        Returns:
            HttpResponse redirect
        """
        try:
            with transaction.atomic():
                # Update playlist basic info
                self._update_playlist_basic_info(request, playlist)
                
                # Clear existing structure
                self._clear_playlist_structure(playlist)
                
                # Recreate structure with new data
                self._recreate_playlist_structure(request, playlist)
                
                # Generate XML if needed
                xml_playlist_res = generateSchedule(playlist)
                
                # Log the update activity
                self._log_update_activity(request.user, playlist)
            
            messages.success(request, "Playlist updated successfully.")
            return redirect("playlists:playlist_detail", id_playlist=playlist.id)
            
        except Exception as e:
            messages.error(request, f"Error updating playlist: {str(e)}")
            return redirect("playlists:edit_playlist", playlist_id=playlist.id)
    
    def _handle_save_as_draft(self, request, playlist):
        """
        Handle saving playlist as draft.
        
        Args:
            request: Django HTTP request object
            playlist: Playlist model instance
            
        Returns:
            HttpResponse redirect
        """
        try:
            with transaction.atomic():
                # Update playlist to draft status
                playlist.is_draft = '1'
                playlist.draft_version = str(int(playlist.version) + 1)
                
                # Update basic info
                self._update_playlist_basic_info(request, playlist)
                
                # Clear and recreate structure
                self._clear_playlist_structure(playlist)
                self._recreate_playlist_structure(request, playlist)
                
                playlist.save()
                
                # Log the draft save activity
                self._log_draft_activity(request.user, playlist)
            
            messages.success(request, "Playlist saved as draft.")
            return redirect("playlists:draft_playlist")
            
        except Exception as e:
            messages.error(request, f"Error saving draft: {str(e)}")
            return redirect("playlists:edit_playlist", playlist_id=playlist.id_playlist)
    
    def _update_playlist_basic_info(self, request, playlist):
        """
        Update basic playlist information.
        
        Args:
            request: Django HTTP request object
            playlist: Playlist model instance
        """
        # Extract basic info from request
        daydate = request.POST.get('day')
        if daydate:
            daydate = datetime.datetime.strptime(str(daydate), '%m/%d/%Y')
            daydate = daydate.strftime('%Y-%m-%d')
            
            playlist.broadcast_date = daydate
            playlist.start_date = f"{daydate}T00:01:00+00:00"
            playlist.end_date = f"{daydate}T23:59:00+00:00"
        
        # Update zone if changed
        zonename = request.POST.get('zonename', '').strip()
        if zonename:
            channel_zone = ChannelZone.objects.get(
                id_channel=playlist.channel,
                id_zone_channel=zonename
            )
            playlist.id_zone_channel = channel_zone
        
        playlist.save()
        
    def _clear_playlist_structure(self, playlist):
        """
        Clear existing playlist structure (windows, avails, ads).
        
        Args:
            playlist: Playlist model instance
        """
        # Delete in proper order to maintain referential integrity
        for window in playlist.windows.all():
            for avail in window.avails.all():
                avail.adspots_in_avail.all().delete()
            window.avails.all().delete()
        playlist.windows.all().delete()
    
    def _recreate_playlist_structure(self, request, playlist):
        """
        Recreate playlist structure from form data.
        Uses the same logic as the original new_playlist view.
        
        Args:
            request: Django HTTP request object
            playlist: Playlist model instance
        """
        number_of_windows = request.POST.get('numofwin')
        daydate = playlist.broadcast_date.strftime('%Y-%m-%d')
        traffic = 0
        
        if number_of_windows and int(number_of_windows) > 0:
            for i in range(int(number_of_windows)):
                if request.POST.get(f'numofavails[{i}]'):
                    numofavails = request.POST.get(f'numofavails[{i}]')
                    
                    # Create window
                    window_start = request.POST.get(f'windowstart[{i}]')
                    window_start = f"{daydate} {window_start}:00"
                    
                    window_end = request.POST.get(f'windowend[{i}]')
                    window_end = f"{daydate} {window_end}:00"
                    
                    # Calculate duration
                    FMT = '%Y-%m-%d %H:%M:%S'
                    window_duration = datetime.datetime.strptime(window_end, FMT) - datetime.datetime.strptime(window_start, FMT)
                    window_duration = datetime.datetime.strptime(str(window_duration), '%H:%M:%S')
                    window_duration = window_duration.strftime('%H%M%S00')
                    
                    window = Windows(
                        id_playlist=playlist,
                        window_start=window_start,
                        window_end=window_end,
                        window_duration=window_duration
                    )
                    window.save()
                    
                    # Create avails
                    for j in range(int(numofavails)):
                        if request.POST.get(f'availstart[{i}][{j}]'):
                            av_start = request.POST.get(f'availstart[{i}][{j}]')
                            av_start = f"{daydate} {av_start}:00"
                            
                            number_of_ads = request.POST.get(f'numofads[{i}][{j}]')
                            
                            avail = Avails(
                                id_window=window,
                                avail_start=av_start,
                                availinwindow=str(j + 1),
                                datetime=datetime.datetime.now()
                            )
                            avail.save()
                            
                            # Create ads
                            for k in range(int(number_of_ads)):
                                if request.POST.get(f'ad[{i}][{j}][{k}]'):
                                    adspot = request.POST.get(f'ad[{i}][{j}][{k}]')
                                    traffic += 1
                                    
                                    ads_in_avail = AdspotsInAvail(
                                        id_avail=avail,
                                        id_adspot_id=adspot,
                                        positioninavail=str(k + 1),
                                        trafficid=traffic
                                    )
                                    ads_in_avail.save()
    
    def _log_update_activity(self, user, playlist):
        """
        Log playlist update activity.
        
        Args:
            user: User who updated the playlist
            playlist: Updated playlist instance
        """
        try:
            activity = Activity(
                activity="Update Playlist",
                date=datetime.datetime.now(),
                description=f"User {user.username} updated playlist ID: {playlist.id_playlist} "
                           f"for channel {playlist.id_channel.channel_name}"
            )
            activity.save()
        except:
            pass
    
    def _log_draft_activity(self, user, playlist):
        """
        Log playlist draft save activity.
        
        Args:
            user: User who saved the draft
            playlist: Draft playlist instance
        """
        try:
            activity = Activity(
                activity="Save Playlist Draft",
                date=datetime.datetime.now(),
                description=f"User {user.username} saved playlist ID: {playlist.id_playlist} as draft"
            )
            activity.save()
        except:
            pass


class PlaylistDeleteView(LoginRequiredMixin, View):
    """
    Class-based view for deleting playlists with proper authorization.
    Supports both AJAX and regular HTTP requests.
    """
    
    def post(self, request, playlist_id):
        """
        Handle playlist deletion.
        
        Args:
            request: Django HTTP request object
            playlist_id: ID of the playlist to delete
            
        Returns:
            JsonResponse or HttpResponse redirect
        """
        try:
            playlist = get_object_or_404(
                Playlists.objects.select_related('channel'),
                id_playlist=playlist_id,
                id_channel__id_user=request.user
            )
            
            # Check if playlist can be deleted
            if not self._can_delete_playlist(playlist):
                return self._handle_delete_error(
                    request, 
                    "Cannot delete this playlist. It may be currently active or have dependencies."
                )
            
            # Store playlist info for logging
            playlist_info = {
                'id': playlist.id_playlist,
                'channel': playlist.id_channel.channel_name,
                'broadcast_date': playlist.broadcast_date,
                'version': playlist.version
            }
            
            # Delete playlist and related objects
            with transaction.atomic():
                self._delete_playlist_cascade(playlist)
            
            # Log the deletion activity
            self._log_deletion_activity(request.user, playlist_info)
            
            # Handle AJAX vs regular request
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return JsonResponse({
                    'success': True,
                    'message': f'Playlist for {playlist_info["channel"]} deleted successfully.'
                })
            else:
                messages.success(
                    request, 
                    f'Playlist for {playlist_info["channel"]} deleted successfully.'
                )
                return redirect('playlists:list_playlist')
                
        except Playlists.DoesNotExist:
            return self._handle_delete_error(request, "Playlist not found.")
        except Exception as e:
            return self._handle_delete_error(request, f"Error deleting playlist: {str(e)}")
    
    def get(self, request, playlist_id):
        """
        Handle GET request - show confirmation page.
        
        Args:
            request: Django HTTP request object
            playlist_id: ID of the playlist to delete
            
        Returns:
            Rendered confirmation template
        """
        playlist = get_object_or_404(
            Playlists.objects.select_related('channel', 'zone_channel'),
            id_playlist=playlist_id,
            id_channel__id_user=request.user
        )
        
        context = {
            'playlist': playlist,
            'dependencies': self._get_playlist_dependencies(playlist),
            'can_delete': self._can_delete_playlist(playlist)
        }
        
        return render(request, 'playlists/confirm_delete.html', context)
    
    def _can_delete_playlist(self, playlist):
        """
        Check if playlist can be safely deleted.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            bool: True if playlist can be deleted
        """
        # Check if playlist is currently active (broadcast date is today or future)
        today = datetime.date.today()
        if playlist.broadcast_date >= today and playlist.is_draft == '0':
            return False
        
        # Add other business rules here
        # e.g., check if playlist is referenced by other systems
        
        return True
    
    def _delete_playlist_cascade(self, playlist):
        """
        Delete playlist and all related objects in proper order.
        
        Args:
            playlist: Playlist model instance
        """
        # Delete in reverse order of creation to maintain referential integrity
        
        # Delete ad spots in avails
        for window in playlist.windows_set.all():
            for avail in window.avails_set.all():
                avail.adspotsinAvail_set.all().delete()
        
        # Delete avails
        for window in playlist.windows_set.all():
            window.avails_set.all().delete()
        
        # Delete windows
        playlist.windows_set.all().delete()
        
        # Delete the playlist itself
        playlist.delete()
    
    def _get_playlist_dependencies(self, playlist):
        """
        Get information about playlist dependencies.
        
        Args:
            playlist: Playlist model instance
            
        Returns:
            dict: Dependencies information
        """
        dependencies = {
            'windows': playlist.windows_set.count(),
            'total_avails': 0,
            'total_ads': 0
        }
        
        for window in playlist.windows_set.all():
            avails_count = window.avails_set.count()
            dependencies['total_avails'] += avails_count
            
            for avail in window.avails_set.all():
                dependencies['total_ads'] += avail.adspotsinAvail_set.count()
        
        return dependencies
    
    def _log_deletion_activity(self, user, playlist_info):
        """
        Log playlist deletion activity.
        
        Args:
            user: User who deleted the playlist
            playlist_info: Dictionary with playlist information
        """
        try:
            activity = Activity(
                activity="Delete Playlist",
                date=datetime.datetime.now(),
                description=f"User {user.username} deleted playlist ID: {playlist_info['id']} "
                           f"for channel {playlist_info['channel']} "
                           f"(Broadcast: {playlist_info['broadcast_date']})"
            )
            activity.save()
        except:
            # Log activity is optional, don't fail if it doesn't work
            pass
    
    def _handle_delete_error(self, request, error_message):
        """
        Handle deletion errors for both AJAX and regular requests.
        
        Args:
            request: Django HTTP request object
            error_message: Error message string
            
        Returns:
            JsonResponse or HttpResponse redirect
        """
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': False,
                'message': error_message
            })
        else:
            messages.error(request, error_message)
            return redirect('playlists:list_playlist')


class PlaylistDuplicateView(LoginRequiredMixin, View):
    """
    Class-based view for duplicating playlists with all related objects.
    """
    
    def post(self, request, playlist_id):
        """
        Handle playlist duplication.
        
        Args:
            request: Django HTTP request object
            playlist_id: ID of the playlist to duplicate
            
        Returns:
            JsonResponse or HttpResponse redirect
        """
        try:
            original_playlist = get_object_or_404(
                Playlists.objects.select_related('channel', 'zone_channel'),
                id_playlist=playlist_id,
                id_channel__id_user=request.user
            )
            
            # Get duplication parameters
            new_broadcast_date = request.POST.get('broadcast_date')
            copy_as_draft = request.POST.get('as_draft', 'false').lower() == 'true'
            
            # Validate new broadcast date
            if new_broadcast_date:
                try:
                    new_date = datetime.datetime.strptime(new_broadcast_date, '%Y-%m-%d').date()
                except ValueError:
                    return self._handle_duplicate_error(request, "Invalid broadcast date format.")
            else:
                # Default to tomorrow
                new_date = datetime.date.today() + datetime.timedelta(days=1)
            
            # Check for existing playlist on the same date
            if self._playlist_exists_for_date(original_playlist, new_date):
                return self._handle_duplicate_error(
                    request, 
                    f"A playlist already exists for {new_date} on this channel."
                )
            
            # Duplicate the playlist
            with transaction.atomic():
                new_playlist = self._duplicate_playlist_structure(
                    original_playlist, 
                    new_date, 
                    copy_as_draft
                )
            
            # Log the duplication activity
            self._log_duplication_activity(request.user, original_playlist, new_playlist)
            
            # Handle AJAX vs regular request
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return JsonResponse({
                    'success': True,
                    'message': f'Playlist duplicated successfully for {new_date}.',
                    'new_playlist_id': new_playlist.id_playlist,
                    'redirect_url': reverse('playlists:playlist_detail', args=[new_playlist.id_playlist])
                })
            else:
                messages.success(
                    request, 
                    f'Playlist duplicated successfully for {new_date}.'
                )
                return redirect('playlists:playlist_detail', id_playlist=new_playlist.id_playlist)
                
        except Playlists.DoesNotExist:
            return self._handle_duplicate_error(request, "Original playlist not found.")
        except Exception as e:
            return self._handle_duplicate_error(request, f"Error duplicating playlist: {str(e)}")
    
    def get(self, request, playlist_id):
        """
        Show duplication form.
        
        Args:
            request: Django HTTP request object
            playlist_id: ID of the playlist to duplicate
            
        Returns:
            Rendered duplication form template
        """
        playlist = get_object_or_404(
            Playlists.objects.select_related('channel', 'zone_channel'),
            id_playlist=playlist_id,
            id_channel__id_user=request.user
        )
        
        # Get suggested dates (next 7 days, excluding existing playlists)
        suggested_dates = self._get_suggested_dates(playlist)
        
        context = {
            'playlist': playlist,
            'suggested_dates': suggested_dates,
            'default_date': datetime.date.today() + datetime.timedelta(days=1)
        }
        
        return render(request, 'playlists/duplicate_form.html', context)
    
    def _playlist_exists_for_date(self, original_playlist, target_date):
        """
        Check if a playlist already exists for the target date.
        
        Args:
            original_playlist: Original playlist instance
            target_date: Target broadcast date
            
        Returns:
            bool: True if playlist exists for the date
        """
        return Playlists.objects.filter(
            id_channel=original_playlist.id_channel,
            id_zone_channel=original_playlist.id_zone_channel,
            broadcast_date=target_date
        ).exists()
    
    def _duplicate_playlist_structure(self, original_playlist, new_date, as_draft):
        """
        Duplicate the entire playlist structure.
        
        Args:
            original_playlist: Original playlist instance
            new_date: New broadcast date
            as_draft: Whether to create as draft
            
        Returns:
            Playlists: New playlist instance
        """
        # Create new playlist
        new_playlist = Playlists(
            id_channel=original_playlist.id_channel,
            version="1",  # Always start with version 1 for duplicates
            broadcast_date=new_date,
            start_date=f"{new_date}T00:01:00+00:00",
            end_date=f"{new_date}T23:59:00+00:00", 
            id_zone_channel=original_playlist.id_zone_channel,
            is_draft='1' if as_draft else '0',
            draft_version='1' if as_draft else '0'
        )
        new_playlist.save()
        
        # Duplicate windows and related structures
        traffic_counter = 0
        
        for original_window in original_playlist.windows_set.all().order_by('id_window'):
            # Calculate new window times for the new date
            new_window_start, new_window_end = self._calculate_new_window_times(
                original_window, new_date
            )
            
            # Create new window
            new_window = Windows(
                id_playlist=new_playlist,
                window_start=new_window_start,
                window_end=new_window_end,
                window_duration=original_window.window_duration
            )
            new_window.save()
            
            # Duplicate avails
            for original_avail in original_window.avails_set.all().order_by('id_avail'):
                new_avail_start = self._calculate_new_avail_start(original_avail, new_date)
                
                new_avail = Avails(
                    id_window=new_window,
                    avail_start=new_avail_start,
                    availinwindow=original_avail.availinwindow,
                    datetime=datetime.datetime.now()
                )
                new_avail.save()
                
                # Duplicate ads
                for original_ad in original_avail.adspotsinAvail_set.all().order_by('id_adspotsinAvail'):
                    traffic_counter += 1
                    
                    new_ad = AdspotsInAvail(
                        id_avail=new_avail,
                        id_adspot=original_ad.id_adspot,
                        positioninavail=original_ad.positioninavail,
                        trafficid=traffic_counter
                    )
                    new_ad.save()
        
        return new_playlist
    
    def _calculate_new_window_times(self, original_window, new_date):
        """
        Calculate new window start and end times for the new date.
        
        Args:
            original_window: Original window instance
            new_date: New broadcast date
            
        Returns:
            tuple: (new_start_time, new_end_time)
        """
        # Extract time from original window
        original_start = original_window.window_start
        original_end = original_window.window_end
        
        # Create new datetime with new date but same time
        new_start = datetime.datetime.combine(
            new_date, 
            original_start.time()
        )
        new_end = datetime.datetime.combine(
            new_date, 
            original_end.time()
        )
        
        return new_start.strftime('%Y-%m-%d %H:%M:%S'), new_end.strftime('%Y-%m-%d %H:%M:%S')
    
    def _calculate_new_avail_start(self, original_avail, new_date):
        """
        Calculate new avail start time for the new date.
        
        Args:
            original_avail: Original avail instance
            new_date: New broadcast date
            
        Returns:
            str: New avail start time
        """
        original_start = original_avail.avail_start
        new_start = datetime.datetime.combine(
            new_date, 
            original_start.time()
        )
        return new_start.strftime('%Y-%m-%d %H:%M:%S')
    
    def _get_suggested_dates(self, playlist):
        """
        Get suggested dates for duplication (next 7 days without existing playlists).
        
        Args:
            playlist: Original playlist instance
            
        Returns:
            list: List of suggested dates
        """
        suggested_dates = []
        current_date = datetime.date.today() + datetime.timedelta(days=1)
        
        for i in range(14):  # Check next 14 days
            if not self._playlist_exists_for_date(playlist, current_date):
                suggested_dates.append(current_date)
                
            current_date += datetime.timedelta(days=1)
            
            # Stop after finding 7 available dates
            if len(suggested_dates) >= 7:
                break
        
        return suggested_dates
    
    def _log_duplication_activity(self, user, original_playlist, new_playlist):
        """
        Log playlist duplication activity.
        
        Args:
            user: User who duplicated the playlist
            original_playlist: Original playlist instance
            new_playlist: New playlist instance
        """
        try:
            activity = Activity(
                activity="Duplicate Playlist",
                date=datetime.datetime.now(),
                description=f"User {user.username} duplicated playlist ID: {original_playlist.id_playlist} "
                           f"to new playlist ID: {new_playlist.id_playlist} "
                           f"(New broadcast date: {new_playlist.broadcast_date})"
            )
            activity.save()
        except:
            # Log activity is optional, don't fail if it doesn't work
            pass
    
    def _handle_duplicate_error(self, request, error_message):
        """
        Handle duplication errors for both AJAX and regular requests.
        
        Args:
            request: Django HTTP request object
            error_message: Error message string
            
        Returns:
            JsonResponse or HttpResponse redirect
        """
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': False,
                'message': error_message
            })
        else:
            messages.error(request, error_message)
            return redirect('playlists:list_playlist')

 