"""
Electronic Program Guide (EPG) and Channel Schedule Models

This module contains models for managing television programming:
- EPGProgram: Electronic Program Guide entries for scheduled content
- ChannelSchedule: Channel broadcasting schedule and special events

These models work together to provide comprehensive programming information
and scheduling capabilities for television channels.
"""

from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from apps.common.models import BaseModel
from apps.channels.models.channels import Channel


class EPGProgram(BaseModel):
    """
    Electronic Program Guide (EPG) entries for scheduled television content.
    
    Represents individual TV programs with comprehensive metadata including
    timing, content classification, and advertising integration points.
    Used for program scheduling, content discovery, and ad insertion planning.
    
    Attributes:
        channel: Reference to the broadcasting channel
        title: Program title/name
        description: Detailed program description
        program_type: Content category classification
        start_time: Program start timestamp
        end_time: Program end timestamp
        duration: Program duration in minutes
        season_number: Season number for series content
        episode_number: Episode number within season
        original_air_date: Original broadcast date
        content_rating: Age/content rating (G, PG, PG-13, etc.)
        language: Primary program language
        subtitles_available: Whether subtitles are available
        has_ad_breaks: Whether program supports ad insertion
        ad_break_positions: List of ad insertion timestamps
    """
    
    PROGRAM_TYPES = [
        ("movie", _("Movie")),
        ("series", _("TV Series")),
        ("news", _("News")),
        ("sports", _("Sports")),
        ("documentary", _("Documentary")),
        ("entertainment", _("Entertainment")),
        ("kids", _("Kids")),
        ("music", _("Music")),
        ("other", _("Other")),
    ]
    
    # Relationships
    channel = models.ForeignKey(
        Channel,
        on_delete=models.CASCADE,
        related_name="programs",
        verbose_name=_("Channel"),
        help_text=_("Channel broadcasting this program")
    )
    
    # Program metadata
    title = models.CharField(
        max_length=255,
        verbose_name=_("Program Title"),
        help_text=_("Full title of the program")
    )
    description = models.TextField(
        blank=True,
        verbose_name=_("Program Description"),
        help_text=_("Detailed description of the program content")
    )
    program_type = models.CharField(
        max_length=20,
        choices=PROGRAM_TYPES,
        default="other",
        verbose_name=_("Program Type"),
        help_text=_("Category classification of the program")
    )
    
    # Scheduling
    start_time = models.DateTimeField(
        verbose_name=_("Start Time"),
        help_text=_("When the program starts broadcasting")
    )
    end_time = models.DateTimeField(
        verbose_name=_("End Time"),
        help_text=_("When the program ends broadcasting")
    )
    duration = models.PositiveIntegerField(
        verbose_name=_("Duration"),
        help_text=_("Program duration in minutes")
    )
    
    # Series/Episode information
    season_number = models.PositiveIntegerField(
        null=True,
        blank=True,
        verbose_name=_("Season Number"),
        help_text=_("Season number for series content")
    )
    episode_number = models.PositiveIntegerField(
        null=True,
        blank=True,
        verbose_name=_("Episode Number"),
        help_text=_("Episode number within the season")
    )
    original_air_date = models.DateField(
        null=True,
        blank=True,
        verbose_name=_("Original Air Date"),
        help_text=_("Original broadcast date of this content")
    )
    
    # Content classification
    content_rating = models.CharField(
        max_length=10,
        blank=True,
        verbose_name=_("Content Rating"),
        help_text=_("Age/content rating (G, PG, PG-13, R, etc.)")
    )
    language = models.CharField(
        max_length=50,
        blank=True,
        verbose_name=_("Language"),
        help_text=_("Primary language of the program")
    )
    subtitles_available = models.BooleanField(
        default=False,
        verbose_name=_("Subtitles Available"),
        help_text=_("Whether closed captions/subtitles are available")
    )
    
    # Advertising integration
    has_ad_breaks = models.BooleanField(
        default=True,
        verbose_name=_("Has Ad Breaks"),
        help_text=_("Whether this program supports commercial breaks")
    )
    ad_break_positions = models.JSONField(
        default=list,
        blank=True,
        verbose_name=_("Ad Break Positions"),
        help_text=_("List of timestamps (in seconds) where ads can be inserted")
    )
    
    class Meta:
        db_table = "epg_programs"
        verbose_name = _("EPG Program")
        verbose_name_plural = _("EPG Programs")
        ordering = ["channel", "start_time"]
        indexes = [
            models.Index(fields=["channel", "start_time"], name="epg_channel_start_idx"),
            models.Index(fields=["start_time", "end_time"], name="epg_time_range_idx"),
            models.Index(fields=["program_type"], name="epg_type_idx"),
        ]
        constraints = [
            models.CheckConstraint(
                check=models.Q(end_time__gt=models.F('start_time')),
                name='epg_valid_time_range'
            ),
            models.CheckConstraint(
                check=models.Q(duration__gt=0),
                name='epg_positive_duration'
            ),
        ]
    
    def __str__(self):
        return f"{self.title} on {self.channel.name} at {self.start_time.strftime('%Y-%m-%d %H:%M')}"
    
    def clean(self):
        """Validate program data consistency."""
        super().clean()
        
        if self.start_time and self.end_time:
            if self.start_time >= self.end_time:
                raise ValidationError({
                    'end_time': _('End time must be after start time.')
                })
            
            # Validate duration matches time range
            calculated_duration = (self.end_time - self.start_time).total_seconds() / 60
            if self.duration and abs(calculated_duration - self.duration) > 1:
                raise ValidationError({
                    'duration': _('Duration does not match the time range.')
                })
    
    def save(self, *args, **kwargs):
        """Auto-calculate duration if not provided."""
        if self.start_time and self.end_time and not self.duration:
            self.duration = int((self.end_time - self.start_time).total_seconds() / 60)
        super().save(*args, **kwargs)
    
    @property
    def is_currently_airing(self):
        """Check if program is currently broadcasting."""
        now = timezone.now()
        return self.start_time <= now <= self.end_time
    
    @property
    def is_upcoming(self):
        """Check if program is scheduled for future broadcast."""
        return self.start_time > timezone.now()
    
    @property
    def is_completed(self):
        """Check if program has finished broadcasting."""
        return self.end_time < timezone.now()
    
    def get_ad_opportunities(self):
        """
        Get available advertisement insertion opportunities.
        
        Returns:
            list: List of timestamps (in seconds from start) where ads can be inserted
        """
        if not self.has_ad_breaks:
            return []
        
        # Return custom ad break positions if defined
        if self.ad_break_positions:
            return self.ad_break_positions
        
        # Generate default ad breaks for different program types
        duration_seconds = self.duration * 60
        
        if self.program_type == "movie" and duration_seconds > 3600:  # Movies > 1 hour
            # Movies: Beginning, middle, end
            return [0, duration_seconds // 2, duration_seconds - 30]
        elif self.program_type in ["series", "entertainment"] and duration_seconds > 1800:  # Shows > 30 min
            # TV shows: Beginning, quarter points, end
            quarter = duration_seconds // 4
            return [0, quarter, quarter * 2, quarter * 3, duration_seconds - 30]
        elif self.program_type == "news":
            # News: Every 10 minutes
            return list(range(0, duration_seconds, 600))
        else:
            # Default: Beginning and end
            return [0, duration_seconds - 30]
    
    def get_time_until_start(self):
        """Get time remaining until program starts."""
        if self.is_currently_airing or self.is_completed:
            return None
        return self.start_time - timezone.now()
    
    def get_time_remaining(self):
        """Get time remaining in current program."""
        if not self.is_currently_airing:
            return None
        return self.end_time - timezone.now()
    
    def overlaps_with(self, other_program):
        """Check if this program overlaps with another program."""
        return (
            self.start_time < other_program.end_time and
            self.end_time > other_program.start_time
        )



class ChannelSchedule(BaseModel):
    """
    Channel broadcasting schedule for special events and programming.
    
    Manages non-regular programming schedules including special events,
    maintenance windows, and test broadcasts. Works alongside EPG programs
    to provide comprehensive channel scheduling.
    
    Attributes:
        channel: Reference to the broadcasting channel
        title: Schedule entry title
        schedule_type: Type of scheduled content
        start_time: Schedule start timestamp
        end_time: Schedule end timestamp
        description: Detailed schedule description
        content_url: Primary content stream URL
        backup_content_url: Backup content stream URL
        allow_ads: Whether ads are permitted during this schedule
        ad_break_duration: Duration of ad breaks in seconds
        is_active: Whether this schedule entry is active
    """
    
    SCHEDULE_TYPES = [
        ("regular", _("Regular Programming")),
        ("special", _("Special Event")),
        ("maintenance", _("Maintenance")),
        ("test", _("Test Broadcast")),
        ("emergency", _("Emergency Broadcast")),
        ("replay", _("Replay/Rerun")),
    ]
    
    # Relationships
    channel = models.ForeignKey(
        Channel,
        on_delete=models.CASCADE,
        related_name="schedules",
        verbose_name=_("Channel"),
        help_text=_("Channel for this schedule entry")
    )
    
    # Schedule metadata
    title = models.CharField(
        max_length=255,
        verbose_name=_("Schedule Title"),
        help_text=_("Title or name for this schedule entry")
    )
    schedule_type = models.CharField(
        max_length=20,
        choices=SCHEDULE_TYPES,
        default="regular",
        verbose_name=_("Schedule Type"),
        help_text=_("Type of scheduled content or event")
    )
    
    # Timing
    start_time = models.DateTimeField(
        verbose_name=_("Start Time"),
        help_text=_("When this schedule entry begins")
    )
    end_time = models.DateTimeField(
        verbose_name=_("End Time"),
        help_text=_("When this schedule entry ends")
    )
    
    # Content details
    description = models.TextField(
        blank=True,
        verbose_name=_("Description"),
        help_text=_("Detailed description of the scheduled content")
    )
    content_url = models.URLField(
        blank=True,
        verbose_name=_("Content URL"),
        help_text=_("Primary URL for the scheduled content stream")
    )
    backup_content_url = models.URLField(
        blank=True,
        verbose_name=_("Backup Content URL"),
        help_text=_("Backup URL for content streaming")
    )
    
    # Advertising settings
    allow_ads = models.BooleanField(
        default=True,
        verbose_name=_("Allow Advertisements"),
        help_text=_("Whether ads can be inserted during this schedule")
    )
    ad_break_duration = models.PositiveIntegerField(
        default=120,
        verbose_name=_("Ad Break Duration"),
        help_text=_("Duration of advertisement breaks in seconds")
    )
    
    # Status and priority
    is_active = models.BooleanField(
        default=True,
        verbose_name=_("Active"),
        help_text=_("Whether this schedule entry is currently active")
    )
    priority = models.PositiveIntegerField(
        default=1,
        verbose_name=_("Priority"),
        help_text=_("Schedule priority (higher number = higher priority)")
    )
    
    # Notifications
    notify_before_minutes = models.PositiveIntegerField(
        default=30,
        verbose_name=_("Notification Lead Time"),
        help_text=_("Minutes before start time to send notifications")
    )
    
    class Meta:
        db_table = "channel_schedules"
        verbose_name = _("Channel Schedule")
        verbose_name_plural = _("Channel Schedules")
        ordering = ["channel", "start_time", "-priority"]
        indexes = [
            models.Index(fields=["channel", "start_time"], name="schedule_channel_start_idx"),
            models.Index(fields=["schedule_type"], name="schedule_type_idx"),
            models.Index(fields=["is_active", "start_time"], name="schedule_active_start_idx"),
        ]
        constraints = [
            models.CheckConstraint(
                check=models.Q(end_time__gt=models.F('start_time')),
                name='schedule_valid_time_range'
            ),
            models.CheckConstraint(
                check=models.Q(priority__gte=1),
                name='schedule_positive_priority'
            ),
        ]
    
    def __str__(self):
        return f"{self.title} on {self.channel.name} ({self.start_time.strftime('%Y-%m-%d %H:%M')})"
    
    def clean(self):
        """Validate schedule data consistency."""
        super().clean()
        
        if self.start_time and self.end_time:
            if self.start_time >= self.end_time:
                raise ValidationError({
                    'end_time': _('End time must be after start time.')
                })
    
    @property
    def is_currently_active(self):
        """Check if schedule is currently active and within time range."""
        if not self.is_active:
            return False
        
        now = timezone.now()
        return self.start_time <= now <= self.end_time
    
    @property
    def is_upcoming(self):
        """Check if schedule is upcoming."""
        return self.is_active and self.start_time > timezone.now()
    
    @property
    def is_completed(self):
        """Check if schedule has completed."""
        return self.end_time < timezone.now()
    
    @property
    def duration_minutes(self):
        """Get schedule duration in minutes."""
        if self.start_time and self.end_time:
            return int((self.end_time - self.start_time).total_seconds() / 60)
        return 0
    
    def get_effective_content_url(self):
        """Get the effective content URL (primary or backup)."""
        return self.content_url or self.backup_content_url
    
    def get_time_until_start(self):
        """Get time remaining until schedule starts."""
        if self.is_currently_active or self.is_completed:
            return None
        return self.start_time - timezone.now()
    
    def get_time_remaining(self):
        """Get time remaining in current schedule."""
        if not self.is_currently_active:
            return None
        return self.end_time - timezone.now()
    
    def should_send_notification(self):
        """Check if notification should be sent for upcoming schedule."""
        if not self.is_upcoming:
            return False
        
        time_until_start = self.get_time_until_start()
        if not time_until_start:
            return False
        
        minutes_until_start = time_until_start.total_seconds() / 60
        return minutes_until_start <= self.notify_before_minutes
    
    def conflicts_with(self, other_schedule):
        """Check if this schedule conflicts with another schedule on the same channel."""
        if self.channel_id != other_schedule.channel_id:
            return False
        
        return (
            self.start_time < other_schedule.end_time and
            self.end_time > other_schedule.start_time
        )
    
    @classmethod
    def get_active_for_channel(cls, channel, at_time=None):
        """Get active schedule for a channel at a specific time."""
        if at_time is None:
            at_time = timezone.now()
        
        return cls.objects.filter(
            channel=channel,
            is_active=True,
            start_time__lte=at_time,
            end_time__gte=at_time
        ).order_by('-priority').first()
    
    @classmethod
    def get_upcoming_for_channel(cls, channel, hours_ahead=24):
        """Get upcoming schedules for a channel within specified hours."""
        now = timezone.now()
        end_time = now + timezone.timedelta(hours=hours_ahead)
        
        return cls.objects.filter(
            channel=channel,
            is_active=True,
            start_time__gte=now,
            start_time__lte=end_time
        ).order_by('start_time')

