"""
Stream Models for Stream Processor Application

This module contains Django models for managing streaming operations,
including channel configuration, stream sessions, HLS segments,
and monitoring data. These models provide the data structure for
the entire stream processing workflow.
"""

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.core.validators import MinValueValidator, MaxValueValidator
from apps.core.models import TimestampedModel, UUIDModel, StatusModel
from apps.core.validators import (
    validate_hls_url, validate_directory_path, validate_name_format,
    validate_slug_format, validate_segment_duration, validate_max_segments,
    validate_retry_attempts, validate_video_dimensions, validate_video_bitrate,
    validate_audio_bitrate, validate_sample_rate, validate_audio_channels,
    validate_framerate
)
import os
import json


class Channel(TimestampedModel, UUIDModel):
    """
    Model representing a streaming channel configuration.
    
    A channel defines the source stream URL, encoding parameters,
    and output configuration for a specific streaming operation.
    Multiple stream sessions can be associated with a single channel.
    
    Attributes:
        name (CharField): Human-readable name for the channel
        slug (SlugField): URL-friendly identifier for the channel
        hls_url (URLField): Source HLS stream URL to capture
        description (TextField): Optional description of the channel
        is_active (BooleanField): Whether the channel is currently active
        output_directory (CharField): Directory path for stream output
        segment_duration (PositiveIntegerField): HLS segment duration in seconds
        max_segments (PositiveIntegerField): Maximum number of segments to keep
        retry_attempts (PositiveIntegerField): Number of retry attempts on failure
        retry_interval (PositiveIntegerField): Seconds between retry attempts
        created_by (ForeignKey): User who created this channel
    """
    
    # Channel identification and metadata
    name = models.CharField(
        max_length=100,
        unique=True,
        validators=[validate_name_format],
        help_text="Human-readable name for the channel"
    )
    
    slug = models.SlugField(
        max_length=100,
        unique=True,
        validators=[validate_slug_format],
        help_text="URL-friendly identifier for the channel"
    )
    
    hls_url = models.URLField(
        max_length=500,
        validators=[validate_hls_url],
        help_text="Source HLS stream URL to capture"
    )
    
    description = models.TextField(
        blank=True,
        help_text="Optional description of the channel content"
    )
    
    # Channel status and configuration
    is_active = models.BooleanField(
        default=True,
        db_index=True,
        help_text="Whether this channel is currently active"
    )
    
    # Output configuration
    output_directory = models.CharField(
        max_length=200,
        validators=[validate_directory_path],
        help_text="Directory path for stream output files"
    )
    
    # HLS configuration
    segment_duration = models.PositiveIntegerField(
        default=6,
        validators=[validate_segment_duration],
        help_text="Duration of each HLS segment in seconds"
    )
    
    max_segments = models.PositiveIntegerField(
        default=10,
        validators=[validate_max_segments],
        help_text="Maximum number of segments to keep in playlist"
    )
    
    # Retry configuration
    retry_attempts = models.PositiveIntegerField(
        default=5,
        validators=[validate_retry_attempts],
        help_text="Number of retry attempts on capture failure"
    )
    
    retry_interval = models.PositiveIntegerField(
        default=10,
        validators=[MinValueValidator(1), MaxValueValidator(300)],
        help_text="Seconds to wait between retry attempts"
    )
    
    # User association
    created_by = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='channels',
        help_text="User who created this channel"
    )
    
    class Meta:
        verbose_name = "Channel"
        verbose_name_plural = "Channels"
        ordering = ['name']
        indexes = [
            models.Index(fields=['slug']),
            models.Index(fields=['is_active']),
            models.Index(fields=['created_at']),
        ]
    
    def __str__(self):
        """String representation of the channel."""
        return self.name
    
    def get_output_path(self):
        """Get the full output path for this channel."""
        return os.path.join(self.output_directory, self.slug)
    
    def get_hls_path(self):
        """Get the HLS output path for this channel."""
        return os.path.join(self.get_output_path(), 'hls')
    
    def get_playlist_path(self):
        """Get the path to the master playlist file."""
        return os.path.join(self.get_hls_path(), 'playlist.m3u8')
    
    def get_active_session(self):
        """Get the currently active stream session for this channel."""
        return self.sessions.filter(status='active').first()


class VideoConfiguration(TimestampedModel, UUIDModel):
    """
    Model for video encoding configuration parameters.
    
    This model stores video encoding settings that can be applied
    to stream processing operations, allowing for different quality
    profiles and encoding options.
    
    Attributes:
        name (CharField): Name for this video configuration
        resolution (CharField): Video resolution (e.g., "1920x1080")
        aspect_ratio (CharField): Video aspect ratio (e.g., "16:9")
        frame_rate (PositiveIntegerField): Video frame rate in FPS
        min_bitrate (CharField): Minimum video bitrate
        max_bitrate (CharField): Maximum video bitrate
        codec (CharField): Video codec to use
        preset (CharField): Encoding preset for speed/quality trade-off
        profile (CharField): Video profile setting
        level (CharField): Video level setting
    """
    
    # Configuration identification
    name = models.CharField(
        max_length=50,
        unique=True,
        help_text="Name for this video configuration"
    )
    
    # Video parameters
    resolution = models.CharField(
        max_length=20,
        default='1280x720',
        help_text="Video resolution (width x height)"
    )
    
    aspect_ratio = models.CharField(
        max_length=10,
        default='16:9',
        help_text="Video aspect ratio"
    )
    
    frame_rate = models.PositiveIntegerField(
        default=25,
        validators=[MinValueValidator(1), MaxValueValidator(120)],
        help_text="Video frame rate in frames per second"
    )
    
    # Bitrate settings
    min_bitrate = models.CharField(
        max_length=20,
        default='2000k',
        help_text="Minimum video bitrate (e.g., '2000k')"
    )
    
    max_bitrate = models.CharField(
        max_length=20,
        default='4000k',
        help_text="Maximum video bitrate (e.g., '4000k')"
    )
    
    # Codec settings
    codec = models.CharField(
        max_length=20,
        default='h264',
        choices=[
            ('h264', 'H.264'),
            ('h265', 'H.265'),
            ('vp9', 'VP9'),
        ],
        help_text="Video codec to use for encoding"
    )
    
    preset = models.CharField(
        max_length=20,
        default='medium',
        choices=[
            ('ultrafast', 'Ultra Fast'),
            ('superfast', 'Super Fast'),
            ('veryfast', 'Very Fast'),
            ('faster', 'Faster'),
            ('fast', 'Fast'),
            ('medium', 'Medium'),
            ('slow', 'Slow'),
            ('slower', 'Slower'),
            ('veryslow', 'Very Slow'),
        ],
        help_text="Encoding preset for speed/quality trade-off"
    )
    
    profile = models.CharField(
        max_length=20,
        default='main',
        choices=[
            ('baseline', 'Baseline'),
            ('main', 'Main'),
            ('high', 'High'),
        ],
        help_text="Video profile setting"
    )
    
    level = models.CharField(
        max_length=10,
        default='3.1',
        help_text="Video level setting (e.g., '3.1', '4.0')"
    )
    
    class Meta:
        verbose_name = "Video Configuration"
        verbose_name_plural = "Video Configurations"
        ordering = ['name']
    
    def __str__(self):
        """String representation of the video configuration."""
        return f"{self.name} ({self.resolution})"


class AudioConfiguration(TimestampedModel, UUIDModel):
    """
    Model for audio encoding configuration parameters.
    
    This model stores audio encoding settings that can be applied
    to stream processing operations, allowing for different audio
    quality profiles and encoding options.
    
    Attributes:
        name (CharField): Name for this audio configuration
        codec (CharField): Audio codec to use
        bitrate (CharField): Audio bitrate setting
        sample_rate (PositiveIntegerField): Audio sample rate in Hz
        channels (PositiveIntegerField): Number of audio channels
        normalize (BooleanField): Whether to apply audio normalization
    """
    
    # Configuration identification
    name = models.CharField(
        max_length=50,
        unique=True,
        help_text="Name for this audio configuration"
    )
    
    # Audio codec
    codec = models.CharField(
        max_length=20,
        default='aac',
        choices=[
            ('aac', 'AAC'),
            ('mp3', 'MP3'),
            ('opus', 'Opus'),
        ],
        help_text="Audio codec to use for encoding"
    )
    
    # Audio quality settings
    bitrate = models.CharField(
        max_length=20,
        default='128k',
        help_text="Audio bitrate (e.g., '128k', '256k')"
    )
    
    sample_rate = models.PositiveIntegerField(
        default=48000,
        choices=[
            (8000, '8 kHz'),
            (16000, '16 kHz'),
            (22050, '22.05 kHz'),
            (44100, '44.1 kHz'),
            (48000, '48 kHz'),
            (96000, '96 kHz'),
        ],
        help_text="Audio sample rate in Hz"
    )
    
    channels = models.PositiveIntegerField(
        default=2,
        choices=[
            (1, 'Mono'),
            (2, 'Stereo'),
            (6, '5.1 Surround'),
        ],
        help_text="Number of audio channels"
    )
    
    # Audio processing options
    normalize = models.BooleanField(
        default=True,
        help_text="Apply audio normalization (loudnorm filter)"
    )
    
    class Meta:
        verbose_name = "Audio Configuration"
        verbose_name_plural = "Audio Configurations"
        ordering = ['name']
    
    def __str__(self):
        """String representation of the audio configuration."""
        return f"{self.name} ({self.bitrate})"


class StreamSession(TimestampedModel, UUIDModel, StatusModel):
    """
    Model representing an active or historical stream session.
    
    A stream session tracks a specific instance of stream capture
    and processing, including start/end times, configuration used,
    and processing statistics.
    
    Attributes:
        channel (ForeignKey): Channel this session belongs to
        video_config (ForeignKey): Video configuration used
        audio_config (ForeignKey): Audio configuration used
        started_at (DateTimeField): When the session started
        ended_at (DateTimeField): When the session ended (if applicable)
        segments_processed (PositiveIntegerField): Number of segments processed
        errors_count (PositiveIntegerField): Number of errors encountered
        last_error (TextField): Last error message encountered
        process_id (CharField): System process ID for monitoring
        statistics (JSONField): Additional statistics data
    """
    
    # Session relationships
    channel = models.ForeignKey(
        Channel,
        on_delete=models.CASCADE,
        related_name='sessions',
        help_text="Channel this session belongs to"
    )
    
    video_config = models.ForeignKey(
        VideoConfiguration,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        help_text="Video configuration used for this session"
    )
    
    audio_config = models.ForeignKey(
        AudioConfiguration,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        help_text="Audio configuration used for this session"
    )
    
    # Session timing
    started_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When the stream capture started"
    )
    
    ended_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="When the stream capture ended"
    )
    
    # Session statistics
    segments_processed = models.PositiveIntegerField(
        default=0,
        help_text="Number of segments successfully processed"
    )
    
    errors_count = models.PositiveIntegerField(
        default=0,
        help_text="Number of errors encountered during processing"
    )
    
    last_error = models.TextField(
        blank=True,
        help_text="Last error message encountered"
    )
    
    # Process monitoring
    process_id = models.CharField(
        max_length=50,
        blank=True,
        help_text="System process ID for monitoring"
    )
    
    # Additional data storage
    statistics = models.JSONField(
        default=dict,
        blank=True,
        help_text="Additional statistics and metadata"
    )
    
    class Meta:
        verbose_name = "Stream Session"
        verbose_name_plural = "Stream Sessions"
        ordering = ['-started_at']
        indexes = [
            models.Index(fields=['channel', 'status']),
            models.Index(fields=['started_at']),
            models.Index(fields=['status']),
        ]
    
    def __str__(self):
        """String representation of the stream session."""
        return f"{self.channel.name} - {self.status} ({self.started_at})"
    
    def duration(self):
        """Calculate the duration of the session."""
        if self.started_at:
            end_time = self.ended_at or timezone.now()
            return end_time - self.started_at
        return None
    
    def is_running(self):
        """Check if the session is currently running."""
        return self.status in ['active', 'processing'] and not self.ended_at
    
    def add_error(self, error_message):
        """Add an error to the session and increment error count."""
        self.errors_count += 1
        self.last_error = error_message
        self.save(update_fields=['errors_count', 'last_error'])


class HLSSegment(TimestampedModel, UUIDModel):
    """
    Model representing an individual HLS segment file.
    
    This model tracks individual .ts segment files that make up
    the HLS stream, including their metadata, file paths, and
    processing status.
    
    Attributes:
        session (ForeignKey): Stream session this segment belongs to
        filename (CharField): Name of the segment file
        file_path (CharField): Full path to the segment file
        sequence_number (PositiveIntegerField): Sequence number in playlist
        duration (FloatField): Duration of the segment in seconds
        file_size (PositiveBigIntegerField): Size of the segment file in bytes
        processed_at (DateTimeField): When the segment was processed
        is_available (BooleanField): Whether the file is currently available
    """
    
    # Segment relationships
    session = models.ForeignKey(
        StreamSession,
        on_delete=models.CASCADE,
        related_name='segments',
        help_text="Stream session this segment belongs to"
    )
    
    # File information
    filename = models.CharField(
        max_length=200,
        help_text="Name of the segment file"
    )
    
    file_path = models.CharField(
        max_length=500,
        help_text="Full path to the segment file"
    )
    
    # Segment metadata
    sequence_number = models.PositiveIntegerField(
        help_text="Sequence number in the HLS playlist"
    )
    
    duration = models.FloatField(
        validators=[MinValueValidator(0.1), MaxValueValidator(60.0)],
        help_text="Duration of the segment in seconds"
    )
    
    file_size = models.PositiveBigIntegerField(
        null=True,
        blank=True,
        help_text="Size of the segment file in bytes"
    )
    
    # Processing information
    processed_at = models.DateTimeField(
        auto_now_add=True,
        help_text="When the segment was processed"
    )
    
    is_available = models.BooleanField(
        default=True,
        db_index=True,
        help_text="Whether the segment file is currently available"
    )
    
    class Meta:
        verbose_name = "HLS Segment"
        verbose_name_plural = "HLS Segments"
        ordering = ['session', 'sequence_number']
        unique_together = ['session', 'sequence_number']
        indexes = [
            models.Index(fields=['session', 'sequence_number']),
            models.Index(fields=['processed_at']),
            models.Index(fields=['is_available']),
        ]
    
    def __str__(self):
        """String representation of the HLS segment."""
        return f"{self.session.channel.name} - Segment {self.sequence_number}"
    
    def file_exists(self):
        """Check if the segment file actually exists on disk."""
        return os.path.exists(self.file_path)
    
    def get_file_size(self):
        """Get the actual file size from disk."""
        try:
            return os.path.getsize(self.file_path)
        except OSError:
            return None
    
    def delete_file(self):
        """Delete the segment file from disk."""
        try:
            if os.path.exists(self.file_path):
                os.remove(self.file_path)
                self.is_available = False
                self.save(update_fields=['is_available'])
                return True
        except OSError:
            pass
        return False


# Signal handlers for automatic jingle detection
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=HLSSegment)
def trigger_jingle_detection(sender, instance, created, **kwargs):
    """
    Automatically trigger jingle detection when a new HLS segment is created.
    
    Args:
        sender: The model class (HLSSegment)
        instance: The actual instance being saved
        created: Boolean indicating if this is a new instance
        **kwargs: Additional keyword arguments
    """
    if created and instance.is_available:
        # Import here to avoid circular imports
        from apps.streams.tasks import process_new_segment
        
        # Trigger jingle detection task asynchronously
        process_new_segment.delay(str(instance.id))
