from django.db import models
from django.utils import timezone
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _
from django.core.validators import RegexValidator

from apps.common.models import BaseModel

class ChannelZone(BaseModel):
    """Geographic zones for channel broadcasting."""
    name = models.CharField(max_length=255)
    code = models.CharField(max_length=10, unique=True)
    description = models.TextField(blank=True)
    timezone = models.CharField(max_length=50, default="UTC")
    is_active = models.BooleanField(default=True)
    
    class Meta:
        db_table = "channel_zones"
        ordering = ["name"]
    
    def __str__(self):
        return f"{self.name} ({self.code})"


class ChannelCodec(BaseModel):
    """Video/Audio codec configurations for channels."""
    name = models.CharField(max_length=100)
    video_codec = models.CharField(max_length=50)
    audio_codec = models.CharField(max_length=50)
    resolution = models.CharField(max_length=20, blank=True)  # e.g., 1920x1080
    bitrate = models.PositiveIntegerField(help_text="Bitrate in kbps")
    frame_rate = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    
    # FFmpeg specific configuration
    ffmpeg_options = models.JSONField(
        default=dict,
        blank=True,
        help_text="Additional FFmpeg options as JSON"
    )
    
    class Meta:
        db_table = "channel_codecs"
        ordering = ["name"]
    
    def __str__(self):
        return f"{self.name} ({self.video_codec}/{self.audio_codec})"


class ChannelZoneRelation(BaseModel):
    """
    Through model for Channel-Zone Many-to-Many relationship.
    Contains zone-specific configuration for each channel.
    """
    VPN_TYPES = [
        ('none', 'No VPN'),
        ('ipsec', 'IPSec Tunnel'),
        ('openvpn', 'OpenVPN'),
        ('wireguard', 'WireGuard'),
    ]
    
    channel = models.ForeignKey(
        'Channel',
        on_delete=models.CASCADE,
        related_name='zone_relations'
    )
    zone = models.ForeignKey(
        ChannelZone,
        on_delete=models.CASCADE,
        related_name='channel_relations'
    )
    
    # Zone-specific codec configuration
    codec = models.ForeignKey(
        ChannelCodec,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Zone-specific codec",
        help_text="Codec configuration for this zone"
    )
    
    # Zone-specific stream URL
    stream_url = models.URLField(
        blank=True,
        verbose_name="Zone-specific stream URL",
        help_text="Stream URL specific to this zone"
    )
    
    # FTP Connection Information
    ftp_host = models.CharField(
        max_length=255,
        blank=True,
        verbose_name="FTP Host",
        help_text="FTP server hostname or IP address"
    )
    ftp_username = models.CharField(
        max_length=100,
        blank=True,
        verbose_name="FTP Username"
    )
    ftp_password = models.CharField(
        max_length=255,
        blank=True,
        verbose_name="FTP Password"
    )
    ftp_port = models.PositiveIntegerField(
        default=21,
        verbose_name="FTP Port"
    )
    ftp_root_directory = models.CharField(
        max_length=500,
        blank=True,
        default="/",
        verbose_name="FTP Root Directory",
        help_text="Root directory path on FTP server"
    )
    ftp_use_passive = models.BooleanField(
        default=True,
        verbose_name="Use FTP Passive Mode"
    )
    
    # VPN Configuration
    vpn_type = models.CharField(
        max_length=20,
        choices=VPN_TYPES,
        default='none',
        verbose_name="VPN Type"
    )
    vpn_server_address = models.CharField(
        max_length=255,
        blank=True,
        verbose_name="VPN Server Address",
        help_text="VPN server IP address or hostname"
    )
    vpn_username = models.CharField(
        max_length=100,
        blank=True,
        verbose_name="VPN Username"
    )
    vpn_password = models.CharField(
        max_length=255,
        blank=True,
        verbose_name="VPN Password"
    )
    
    # IPSec specific fields
    ipsec_preshared_key = models.TextField(
        blank=True,
        verbose_name="IPSec Pre-shared Key"
    )
    ipsec_local_subnet = models.CharField(
        max_length=18,
        blank=True,
        verbose_name="IPSec Local Subnet",
        help_text="e.g., 192.168.1.0/24"
    )
    ipsec_remote_subnet = models.CharField(
        max_length=18,
        blank=True,
        verbose_name="IPSec Remote Subnet",
        help_text="e.g., 10.0.0.0/24"
    )
    
    # OpenVPN specific fields
    openvpn_config_file = models.FileField(
        upload_to='vpn_configs/',
        blank=True,
        null=True,
        verbose_name="OpenVPN Config File"
    )
    openvpn_ca_cert = models.TextField(
        blank=True,
        verbose_name="OpenVPN CA Certificate"
    )
    openvpn_client_cert = models.TextField(
        blank=True,
        verbose_name="OpenVPN Client Certificate"
    )
    openvpn_client_key = models.TextField(
        blank=True,
        verbose_name="OpenVPN Client Private Key"
    )
    
    # WireGuard specific fields
    wireguard_private_key = models.TextField(
        blank=True,
        verbose_name="WireGuard Private Key"
    )
    wireguard_public_key = models.TextField(
        blank=True,
        verbose_name="WireGuard Public Key"
    )
    wireguard_endpoint = models.CharField(
        max_length=255,
        blank=True,
        verbose_name="WireGuard Endpoint",
        help_text="e.g., vpn.example.com:51820"
    )
    wireguard_allowed_ips = models.TextField(
        blank=True,
        verbose_name="WireGuard Allowed IPs",
        help_text="Comma-separated list of allowed IP ranges"
    )
    
    # Connection settings
    is_active = models.BooleanField(
        default=True,
        verbose_name="Active in Zone"
    )
    priority = models.PositiveIntegerField(
        default=1,
        verbose_name="Priority",
        help_text="Higher number = higher priority"
    )
    
    class Meta:
        db_table = "channel_zone_relations"
        unique_together = ['channel', 'zone']
        ordering = ['channel', 'zone', '-priority']
    
    def __str__(self):
        return f"{self.channel.name} in {self.zone.name}"
    
    def get_connection_info(self):
        """Get connection information for this zone."""
        return {
            'stream_url': self.stream_url,
            'ftp': {
                'host': self.ftp_host,
                'username': self.ftp_username,
                'password': self.ftp_password,
                'port': self.ftp_port,
                'root_directory': self.ftp_root_directory,
                'use_passive': self.ftp_use_passive,
            },
            'vpn': {
                'type': self.vpn_type,
                'server_address': self.vpn_server_address,
                'username': self.vpn_username,
                'password': self.vpn_password,
            }
        }


class Channel(BaseModel):
    """
    Television channels with comprehensive metadata and relationships.
    
    Represents individual TV channels with their broadcasting details,
    network affiliations, geographic coverage, and advertising inventory.
    """
    CHANNEL_TYPES = [
        ('terrestrial', _('Terrestrial/Over-the-Air')),
        ('cable', _('Cable Channel')),
        ('satellite', _('Satellite Channel')),
        ('iptv', _('IPTV Channel')),
        ('streaming', _('Streaming Channel')),
        ('radio', _('Radio Station')),
    ] 
    
    STATUS_CHOICES = [
        ("active", "Active"),
        ("inactive", "Inactive"),
        ("maintenance", "Maintenance"),
        ("testing", "Testing"),
    ]
    
    # Channel Metadata
    name = models.CharField(
        max_length=200,
        verbose_name=_('Channel Name'),
        help_text=_('Full name of the TV channel')
    ) 
    display_name = models.CharField(
        max_length=20,
        unique=True,
        verbose_name=_('Call Sign'),
        help_text=_('Official call sign or identifier (e.g., "WABC-TV", "ESPN")')
    )
    channel_number = models.CharField(
        max_length=20,
        verbose_name=_('Channel Number'),
        help_text=_('Channel number on cable/satellite systems (e.g., "7.1", "ESPN")')
    ) 
    channel_type = models.CharField(
        max_length=20,
        choices=CHANNEL_TYPES, 
        default="satellite",
        verbose_name=_('Channel Type'),
        help_text=_('Type of broadcasting technology')
    ) 
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default="active",
        verbose_name=_('Channel Status'),
        help_text=_('Current status of the channel (e.g., Active, Inactive, Maintenance)')
    )
    
    # Multi-zone support - Many-to-Many relationship with zones
    zones = models.ManyToManyField(
        ChannelZone,
        through='ChannelZoneRelation',
        related_name='channels',
        blank=True,
        verbose_name=_('Channel Zones'),
        help_text=_('Geographic zones where this channel is available')
    )
    
    # Technical details (fallback/default values)
    stream_url = models.URLField(
        verbose_name=_('Default Stream URL'),
        help_text=_('Default URL for live streaming of the channel'),
        blank=True
    )
    backup_stream_url = models.URLField(
        verbose_name=_('Backup Stream URL'),
        help_text=_('URL for backup live streaming in case of issues'),
        blank=True
    )
    codec = models.ForeignKey(
        ChannelCodec,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name=_('Default Video/Audio Codec'),
        help_text=_('Default codec configuration for video and audio')
    )
    
    # Branding
    logo = models.ImageField(
        verbose_name=_('Channel Logo'),
        help_text=_('Logo or image representing the channel'),
        upload_to="channels/logos/",
        blank=True,
        null=True
    )
    description = models.TextField(
        verbose_name=_('Channel Description'),
        help_text=_('Short description or tagline about the channel'),
        blank=True
    )
    website = models.URLField(
        verbose_name=_('Channel Website'),
        help_text=_('Official website of the channel'),
        blank=True
    )
    
    # Broadcasting details
    language = models.CharField(
        max_length=250,
        blank=True,
        verbose_name=_('Channel Language'),
        help_text=_('Language of the content broadcasted on the channel')
    )
    category = models.CharField(
        max_length=250,
        blank=True,
        verbose_name=_('Channel Category'),
        help_text=_('Category of content broadcasted on the channel (e.g., News, Sports, Entertainment)')
    )
    target_audience = models.CharField(
        max_length=100,
        blank=True,
        verbose_name=_('Target Audience'),
        help_text=_('Specific demographic or group the channel is targeting (e.g., Teens, Mature, Children)')
    )
    
    # Ad insertion settings
    supports_dai = models.BooleanField(
        verbose_name=_('Supports DAI'),
        default=True, 
        help_text="Dynamic Ad Insertion support"
    )
    max_ad_duration = models.PositiveIntegerField(
        verbose_name=_('Max Ad Duration'),
        default=180, 
        help_text="Maximum ad break duration in seconds"
    )
    min_ad_gap = models.PositiveIntegerField(
        verbose_name=_('Min Ad Gap'),
        default=300, 
        help_text="Minimum gap between ads in seconds"
    )
    
    # Monitoring
    last_health_check = models.DateTimeField(
        verbose_name=_('Last Health Check'),
        null=True, 
        blank=True
    )
    is_online = models.BooleanField(
        verbose_name=_('Is Online'),
        default=True
    )
    
    class Meta:
        db_table = "channels"
        ordering = ["channel_number", "name"]
    
    def __str__(self):
        if self.channel_number:
            return f"{self.channel_number}. {self.display_name or self.name}"
        return self.display_name or self.name
    
    @property
    def is_active(self):
        return self.status == "active" and self.is_online
    
    def update_health_status(self, is_online=True):
        """Update channel health status."""
        self.is_online = is_online
        self.last_health_check = timezone.now()
        self.save(update_fields=["is_online", "last_health_check"])
    
    def get_zone_relation(self, zone):
        """Get the zone relation for a specific zone."""
        try:
            return self.zone_relations.get(zone=zone)
        except ChannelZoneRelation.DoesNotExist:
            return None
    
    def get_stream_url_for_zone(self, zone):
        """Get stream URL for a specific zone."""
        zone_relation = self.get_zone_relation(zone)
        if zone_relation and zone_relation.stream_url:
            return zone_relation.stream_url
        return self.stream_url
    
    def get_codec_for_zone(self, zone):
        """Get codec configuration for a specific zone."""
        zone_relation = self.get_zone_relation(zone)
        if zone_relation and zone_relation.codec:
            return zone_relation.codec
        return self.codec


class EPGProgram(BaseModel):
    """Electronic Program Guide entries."""
    PROGRAM_TYPES = [
        ("movie", "Movie"),
        ("series", "TV Series"),
        ("news", "News"),
        ("sports", "Sports"),
        ("documentary", "Documentary"),
        ("entertainment", "Entertainment"),
        ("kids", "Kids"),
        ("music", "Music"),
        ("other", "Other"),
    ]
    
    channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name="programs")
    
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    program_type = models.CharField(max_length=20, choices=PROGRAM_TYPES, default="other")
    
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    duration = models.PositiveIntegerField(help_text="Duration in minutes")
    
    # Content details
    season_number = models.PositiveIntegerField(null=True, blank=True)
    episode_number = models.PositiveIntegerField(null=True, blank=True)
    original_air_date = models.DateField(null=True, blank=True)
    
    # Ratings and content
    content_rating = models.CharField(max_length=10, blank=True)  # G, PG, PG-13, etc.
    language = models.CharField(max_length=50, blank=True)
    subtitles_available = models.BooleanField(default=False)
    
    # Ad insertion opportunities
    has_ad_breaks = models.BooleanField(default=True)
    ad_break_positions = models.JSONField(default=list, blank=True)  # List of seconds where ads can be inserted
    
    class Meta:
        db_table = "epg_programs"
        ordering = ["channel", "start_time"]
        indexes = [
            models.Index(fields=["channel", "start_time"]),
            models.Index(fields=["start_time", "end_time"]),
        ]
    
    def __str__(self):
        return f"{self.title} on {self.channel.name} at {self.start_time}"
    
    @property
    def is_currently_airing(self):
        now = timezone.now()
        return self.start_time <= now <= self.end_time
    
    def get_ad_opportunities(self):
        """Get available ad insertion opportunities."""
        if not self.has_ad_breaks:
            return []
        
        # Default ad breaks at beginning, middle, and end
        if not self.ad_break_positions:
            duration_seconds = self.duration * 60
            return [0, duration_seconds // 2, duration_seconds - 30]
        
        return self.ad_break_positions


class Jingle(BaseModel):
    """Channel jingles and station IDs with enhanced placement types."""
    JINGLE_TYPES = [
        ("station_id", "Station ID"),
        ("bumper", "Bumper"),
        ("promo", "Promo"),
        ("transition", "Transition"),
        ("commercial_break", "Commercial Break"),
    ]
    
    PLACEMENT_TYPES = [
        ("start", "Start of Show"),
        ("end", "End of Show"),
        ("commercial_start", "Start of Commercial Break"),
        ("commercial_end", "End of Commercial Break"),
        ("program_transition", "Program Transition"),
        ("hourly", "Hourly"),
        ("half_hourly", "Half Hourly"),
        ("random", "Random Placement"),
    ]
    
    channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name="jingles")
    
    name = models.CharField(max_length=255)
    jingle_type = models.CharField(max_length=20, choices=JINGLE_TYPES)
    placement_type = models.CharField(
        max_length=20, 
        choices=PLACEMENT_TYPES,
        default="random",
        help_text="When this jingle should be played"
    )
    
    file = models.FileField(upload_to="jingles/")
    duration = models.PositiveIntegerField(help_text="Duration in seconds")
    
    # Audio fingerprinting data for detection
    audio_fingerprint = models.TextField(
        blank=True,
        help_text="Audio fingerprint data for detection in streams"
    )
    video_fingerprint = models.TextField(
        blank=True,
        help_text="Video fingerprint data for detection in streams"
    )
    
    # Usage settings
    is_active = models.BooleanField(default=True)
    priority = models.PositiveIntegerField(default=1, help_text="Higher number = higher priority")
    
    # Scheduling
    start_date = models.DateField(null=True, blank=True)
    end_date = models.DateField(null=True, blank=True)
    time_slots = models.JSONField(default=list, blank=True)  # List of time ranges when this jingle can play
    
    # Usage tracking
    play_count = models.PositiveIntegerField(default=0)
    last_played = models.DateTimeField(null=True, blank=True)
    
    class Meta:
        db_table = "jingles"
        ordering = ["channel", "-priority", "name"]
    
    def __str__(self):
        return f"{self.name} ({self.channel.name})"
    
    def can_play_now(self):
        """Check if jingle can be played at current time."""
        if not self.is_active:
            return False
        
        now = timezone.now()
        
        # Check date range
        if self.start_date and now.date() < self.start_date:
            return False
        if self.end_date and now.date() > self.end_date:
            return False
        
        # Check time slots
        if self.time_slots:
            current_time = now.time()
            for slot in self.time_slots:
                start_time = timezone.datetime.strptime(slot["start"], "%H:%M").time()
                end_time = timezone.datetime.strptime(slot["end"], "%H:%M").time()
                if start_time <= current_time <= end_time:
                    return True
            return False
        
        return True
    
    def mark_played(self):
        """Mark jingle as played."""
        self.play_count += 1
        self.last_played = timezone.now()
        self.save(update_fields=["play_count", "last_played"])


class JingleDetection(BaseModel):
    """Log of detected jingles in live streams."""
    DETECTION_STATUS = [
        ("detected", "Detected"),
        ("confirmed", "Confirmed"),
        ("false_positive", "False Positive"),
    ]
    
    channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name="jingle_detections")
    jingle = models.ForeignKey(Jingle, on_delete=models.CASCADE, related_name="detections")
    
    # Detection metadata
    detected_at = models.DateTimeField(auto_now_add=True)
    start_timestamp = models.DateTimeField(
        help_text="When the jingle started playing in the stream"
    )
    end_timestamp = models.DateTimeField(
        help_text="When the jingle ended playing in the stream"
    )
    
    # Detection confidence and method
    confidence_score = models.DecimalField(
        max_digits=5, 
        decimal_places=4,
        help_text="Confidence score from 0.0 to 1.0"
    )
    detection_method = models.CharField(
        max_length=50,
        default="audio_fingerprint",
        help_text="Method used for detection (audio_fingerprint, video_fingerprint, etc.)"
    )
    
    # Stream context
    stream_position = models.PositiveIntegerField(
        help_text="Position in stream where jingle was detected (seconds)"
    )
    
    # Status and validation
    status = models.CharField(
        max_length=20,
        choices=DETECTION_STATUS,
        default="detected"
    )
    
    # Additional metadata
    metadata = models.JSONField(
        default=dict,
        blank=True,
        help_text="Additional detection metadata"
    )
    
    class Meta:
        db_table = "jingle_detections"
        ordering = ["-detected_at"]
        indexes = [
            models.Index(fields=["channel", "detected_at"]),
            models.Index(fields=["jingle", "detected_at"]),
            models.Index(fields=["start_timestamp", "end_timestamp"]),
        ]
    
    def __str__(self):
        return f"{self.jingle.name} detected on {self.channel.name} at {self.start_timestamp}"
    
    @property
    def duration(self):
        """Get detection duration in seconds."""
        if self.start_timestamp and self.end_timestamp:
            return (self.end_timestamp - self.start_timestamp).total_seconds()
        return 0
    
    def infer_ad_break_duration(self):
        """
        Infer ad break duration based on jingle placement.
        Returns estimated ad break duration in seconds.
        """
        if self.jingle.placement_type == "commercial_start":
            # Look for the corresponding commercial_end jingle
            try:
                end_detection = JingleDetection.objects.filter(
                    channel=self.channel,
                    jingle__placement_type="commercial_end",
                    start_timestamp__gt=self.start_timestamp,
                    start_timestamp__lt=self.start_timestamp + timezone.timedelta(minutes=10)
                ).first()
                
                if end_detection:
                    return (end_detection.start_timestamp - self.end_timestamp).total_seconds()
            except JingleDetection.DoesNotExist:
                pass
        
        # Fallback to channel's default ad break duration
        return self.channel.max_ad_duration


class ChannelSchedule(BaseModel):
    """Channel broadcasting schedule."""
    SCHEDULE_TYPES = [
        ("regular", "Regular Programming"),
        ("special", "Special Event"),
        ("maintenance", "Maintenance"),
        ("test", "Test Broadcast"),
    ]
    
    channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name="schedules")
    
    title = models.CharField(max_length=255)
    schedule_type = models.CharField(max_length=20, choices=SCHEDULE_TYPES, default="regular")
    
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    
    # Content details
    description = models.TextField(blank=True)
    content_url = models.URLField(blank=True)
    backup_content_url = models.URLField(blank=True)
    
    # Ad settings
    allow_ads = models.BooleanField(default=True)
    ad_break_duration = models.PositiveIntegerField(default=120, help_text="Ad break duration in seconds")
    
    # Status
    is_active = models.BooleanField(default=True)
    
    class Meta:
        db_table = "channel_schedules"
        ordering = ["channel", "start_time"]
    
    def __str__(self):
        return f"{self.title} on {self.channel.name}"
    
    @property
    def is_currently_active(self):
        now = timezone.now()
        return self.is_active and self.start_time <= now <= self.end_time
