# Adtlas TV Advertising Platform - Channels Serializers
# Django REST Framework serializers for TV channels, networks, and coverage data

import json
from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from django.db.models import Sum, Avg, Count


from .models import (
    GeographicZone,
    BroadcastNetwork,
    TVChannel,
    ChannelCoverage,
    ContentSchedule,
    AudienceDemographics
)


class GeographicZoneSerializer(serializers.ModelSerializer):
    """
    Serializer for Geographic Zone model.
    
    Provides comprehensive zone information including hierarchical relationships,
    demographics, and coverage statistics.
    """
    
    parent_zone_name = serializers.CharField(
        source='parent_zone.name', 
        read_only=True
    )
    child_zones_count = serializers.SerializerMethodField()
    coverage_statistics = serializers.SerializerMethodField()
    
    class Meta:
        model = GeographicZone
        fields = [
            'id',
            'name',
            'code',
            'zone_type',
            'parent_zone',
            'parent_zone_name',
            'population',
            'tv_households',
            'latitude',
            'longitude',
            'timezone',
            'child_zones_count',
            'coverage_statistics',
            'is_active',
            'created_at',
            'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at']
    
    def get_child_zones_count(self, obj):
        """
        Get count of child zones.
        """
        return obj.child_zones.filter(is_active=True).count()
    
    def get_coverage_statistics(self, obj):
        """
        Get coverage statistics for this zone.
        """
        coverage = ChannelCoverage.objects.filter(
            zone=obj,
            is_active=True
        ).aggregate(
            total_channels=Count('channel', distinct=True),
            avg_penetration=Avg('penetration_rate'),
            total_subscribers=Sum('subscriber_count')
        )
        
        return {
            'channels_available': coverage['total_channels'] or 0,
            'average_penetration_rate': round(coverage['avg_penetration'] or 0, 2),
            'total_subscribers': coverage['total_subscribers'] or 0
        }
    
    def validate_parent_zone(self, value):
        """
        Validate parent zone to prevent circular references.
        """
        if value and self.instance:
            # Check for circular reference
            current = value
            while current:
                if current.id == self.instance.id:
                    raise serializers.ValidationError(
                        _("Cannot set parent zone that would create a circular reference.")
                    )
                current = current.parent_zone
        return value


class BroadcastNetworkSerializer(serializers.ModelSerializer):
    """
    Serializer for Broadcast Network model.
    
    Includes network hierarchy, channel counts, and subsidiary information.
    """
    
    parent_network_name = serializers.CharField(
        source='parent_network.name', 
        read_only=True
    )
    channels_count = serializers.SerializerMethodField()
    subsidiaries_count = serializers.SerializerMethodField()
    coverage_reach = serializers.SerializerMethodField()
    
    class Meta:
        model = BroadcastNetwork
        fields = [
            'id',
            'name',
            'short_name',
            'network_type',
            'parent_network',
            'parent_network_name',
            'founded_date',
            'headquarters_location',
            'website_url',
            'logo_url',
            'description',
            'target_demographics',
            'channels_count',
            'subsidiaries_count',
            'coverage_reach',
            'is_active',
            'created_at',
            'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at']
    
    def get_channels_count(self, obj):
        """
        Get count of active channels for this network.
        """
        return obj.channels.filter(is_active=True).count()
    
    def get_subsidiaries_count(self, obj):
        """
        Get count of subsidiary networks.
        """
        return obj.subsidiary_networks.filter(is_active=True).count()
    
    def get_coverage_reach(self, obj):
        """
        Calculate total coverage reach for the network.
        """
        channels = obj.channels.filter(is_active=True)
        coverage = ChannelCoverage.objects.filter(
            channel__in=channels,
            is_active=True
        ).aggregate(
            total_population=Sum('zone__population'),
            total_zones=Count('zone', distinct=True),
            avg_penetration=Avg('penetration_rate')
        )
        
        return {
            'total_population_reached': coverage['total_population'] or 0,
            'geographic_zones_covered': coverage['total_zones'] or 0,
            'average_penetration_rate': round(coverage['avg_penetration'] or 0, 2)
        }


class TVChannelListSerializer(serializers.ModelSerializer):
    """
    Lightweight serializer for TV Channel list views.
    
    Optimized for performance in list displays with essential information only.
    """
    
    network_name = serializers.CharField(source='network.name', read_only=True)
    network_short_name = serializers.CharField(source='network.short_name', read_only=True)
    coverage_zones_count = serializers.SerializerMethodField()
    
    class Meta:
        model = TVChannel
        fields = [
            'id',
            'name',
            'call_sign',
            'channel_number',
            'channel_type',
            'content_category',
            'network',
            'network_name',
            'network_short_name',
            'coverage_zones_count',
            'hd_available',
            'uhd_4k_available',
            'is_premium',
            'advertising_enabled',
            'is_active'
        ]
    
    def get_coverage_zones_count(self, obj):
        """
        Get count of coverage zones for this channel.
        """
        return obj.coverage_zones.filter(is_active=True).count()


class TVChannelDetailSerializer(serializers.ModelSerializer):
    """
    Comprehensive serializer for TV Channel detail views.
    
    Includes full channel information, coverage data, and related statistics.
    """
    
    network_details = BroadcastNetworkSerializer(source='network', read_only=True)
    coverage_statistics = serializers.SerializerMethodField()
    audience_metrics = serializers.SerializerMethodField()
    programming_info = serializers.SerializerMethodField()
    advertising_opportunities = serializers.SerializerMethodField()
    
    class Meta:
        model = TVChannel
        fields = [
            'id',
            'name',
            'call_sign',
            'channel_number',
            'channel_type',
            'content_category',
            'network',
            'network_details',
            'primary_language',
            'secondary_languages',
            'target_demographics',
            'description',
            'website_url',
            'logo_url',
            'hd_available',
            'uhd_4k_available',
            'is_premium',
            'advertising_enabled',
            'launch_date',
            'coverage_statistics',
            'audience_metrics',
            'programming_info',
            'advertising_opportunities',
            'is_active',
            'created_at',
            'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at']
    
    def get_coverage_statistics(self, obj):
        """
        Get comprehensive coverage statistics.
        """
        coverage = ChannelCoverage.objects.filter(
            channel=obj,
            is_active=True
        ).aggregate(
            total_zones=Count('zone'),
            total_population=Sum('zone__population'),
            total_households=Sum('zone__tv_households'),
            total_subscribers=Sum('subscriber_count'),
            avg_penetration=Avg('penetration_rate'),
            avg_signal_strength=Avg('signal_strength')
        )
        
        return {
            'coverage_zones': coverage['total_zones'] or 0,
            'population_reached': coverage['total_population'] or 0,
            'tv_households_reached': coverage['total_households'] or 0,
            'total_subscribers': coverage['total_subscribers'] or 0,
            'average_penetration_rate': round(coverage['avg_penetration'] or 0, 2),
            'average_signal_strength': coverage['avg_signal_strength'] or 0
        }
    
    def get_audience_metrics(self, obj):
        """
        Get latest audience demographics and metrics.
        """
        latest_demo = AudienceDemographics.objects.filter(
            channel=obj
        ).order_by('-measurement_date').first()
        
        if not latest_demo:
            return None
        
        return {
            'measurement_date': latest_demo.measurement_date,
            'total_viewers': latest_demo.total_viewers,
            'engagement_score': latest_demo.viewer_engagement_score,
            'viewing_hours_per_week': latest_demo.viewing_hours_per_week,
            'primary_age_group': latest_demo.primary_age_group,
            'age_distribution': json.loads(latest_demo.age_distribution or '{}'),
            'gender_distribution': json.loads(latest_demo.gender_distribution or '{}')
        }
    
    def get_programming_info(self, obj):
        """
        Get current and upcoming programming information.
        """
        from django.utils import timezone
        from datetime import timedelta
        
        now = timezone.now()
        
        # Current program
        current_program = ContentSchedule.objects.filter(
            channel=obj,
            start_time__lte=now,
            end_time__gte=now
        ).first()
        
        # Next few programs
        upcoming_programs = ContentSchedule.objects.filter(
            channel=obj,
            start_time__gte=now,
            start_time__lte=now + timedelta(hours=6)
        ).order_by('start_time')[:5]
        
        return {
            'current_program': {
                'title': current_program.title if current_program else None,
                'content_type': current_program.content_type if current_program else None,
                'end_time': current_program.end_time if current_program else None
            },
            'upcoming_programs_count': upcoming_programs.count(),
            'next_program': {
                'title': upcoming_programs[0].title if upcoming_programs else None,
                'start_time': upcoming_programs[0].start_time if upcoming_programs else None
            }
        }
    
    def get_advertising_opportunities(self, obj):
        """
        Get advertising opportunities and metrics.
        """
        if not obj.advertising_enabled:
            return None
        
        from django.utils import timezone
        from datetime import timedelta
        
        # Get recent advertising data
        recent_date = timezone.now().date() - timedelta(days=7)
        
        ad_stats = ContentSchedule.objects.filter(
            channel=obj,
            start_time__date__gte=recent_date,
            advertising_breaks__gt=0
        ).aggregate(
            avg_breaks_per_program=Avg('advertising_breaks'),
            avg_ad_duration=Avg('ad_break_duration'),
            total_ad_inventory=Sum('ad_break_duration')
        )
        
        return {
            'average_breaks_per_program': round(ad_stats['avg_breaks_per_program'] or 0, 1),
            'average_ad_duration_seconds': ad_stats['avg_ad_duration'] or 0,
            'weekly_ad_inventory_seconds': ad_stats['total_ad_inventory'] or 0,
            'estimated_cpm': self._calculate_estimated_cpm(obj)
        }
    
    def _calculate_estimated_cpm(self, channel):
        """
        Calculate estimated CPM for advertising.
        """
        # Simplified CPM calculation
        base_cpm = 5.0
        
        # Adjust based on channel type and category
        type_multipliers = {
            'broadcast': 1.2,
            'cable': 1.0,
            'satellite': 1.1,
            'streaming': 0.9
        }
        
        category_multipliers = {
            'news': 1.3,
            'sports': 1.5,
            'entertainment': 1.0,
            'educational': 0.8,
            'children': 1.1
        }
        
        type_mult = type_multipliers.get(channel.channel_type, 1.0)
        category_mult = category_multipliers.get(channel.content_category, 1.0)
        
        estimated_cpm = base_cpm * type_mult * category_mult
        
        return round(estimated_cpm, 2)


class ChannelCoverageSerializer(serializers.ModelSerializer):
    """
    Serializer for Channel Coverage relationships.
    
    Manages the many-to-many relationship between channels and zones.
    """
    
    channel_name = serializers.CharField(source='channel.name', read_only=True)
    channel_number = serializers.CharField(source='channel.channel_number', read_only=True)
    zone_name = serializers.CharField(source='zone.name', read_only=True)
    zone_type = serializers.CharField(source='zone.zone_type', read_only=True)
    estimated_viewers = serializers.SerializerMethodField()
    
    class Meta:
        model = ChannelCoverage
        fields = [
            'id',
            'channel',
            'channel_name',
            'channel_number',
            'zone',
            'zone_name',
            'zone_type',
            'signal_strength',
            'coverage_percentage',
            'subscriber_count',
            'penetration_rate',
            'estimated_viewers',
            'launch_date_in_zone',
            'notes',
            'is_active',
            'created_at',
            'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at']
    
    def get_estimated_viewers(self, obj):
        """
        Calculate estimated viewers based on penetration and households.
        """
        return obj.estimated_viewers
    
    def validate(self, data):
        """
        Validate coverage data consistency.
        """
        # Ensure coverage percentage is realistic
        if data.get('coverage_percentage', 0) > 100:
            raise serializers.ValidationError({
                'coverage_percentage': _('Coverage percentage cannot exceed 100%.')
            })
        
        # Ensure penetration rate is realistic
        if data.get('penetration_rate', 0) > 100:
            raise serializers.ValidationError({
                'penetration_rate': _('Penetration rate cannot exceed 100%.')
            })
        
        # Validate subscriber count against zone households
        zone = data.get('zone')
        subscriber_count = data.get('subscriber_count', 0)
        
        if zone and zone.tv_households and subscriber_count > zone.tv_households:
            raise serializers.ValidationError({
                'subscriber_count': _('Subscriber count cannot exceed total TV households in zone.')
            })
        
        return data


class ContentScheduleSerializer(serializers.ModelSerializer):
    """
    Serializer for Content Schedule (EPG) data.
    
    Handles programming schedule information with advertising breaks.
    """
    
    channel_name = serializers.CharField(source='channel.name', read_only=True)
    channel_number = serializers.CharField(source='channel.channel_number', read_only=True)
    duration_formatted = serializers.SerializerMethodField()
    is_currently_airing = serializers.SerializerMethodField()
    advertising_revenue_potential = serializers.SerializerMethodField()
    
    class Meta:
        model = ContentSchedule
        fields = [
            'id',
            'channel',
            'channel_name',
            'channel_number',
            'title',
            'description',
            'content_type',
            'genre',
            'rating',
            'start_time',
            'end_time',
            'duration_minutes',
            'duration_formatted',
            'target_demographics',
            'advertising_breaks',
            'ad_break_duration',
            'is_live',
            'is_premiere',
            'is_currently_airing',
            'advertising_revenue_potential',
            'external_id',
            'created_at',
            'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at', 'end_time']
    
    def get_duration_formatted(self, obj):
        """
        Format duration in human-readable format.
        """
        hours, minutes = divmod(obj.duration_minutes, 60)
        if hours > 0:
            return f"{hours}h {minutes}m"
        return f"{minutes}m"
    
    def get_is_currently_airing(self, obj):
        """
        Check if the program is currently airing.
        """
        return obj.is_currently_airing
    
    def get_advertising_revenue_potential(self, obj):
        """
        Calculate potential advertising revenue for this program.
        """
        if obj.advertising_breaks == 0:
            return 0
        
        # Simplified calculation based on ad breaks and duration
        base_rate_per_second = 0.10  # Base rate per second of advertising
        
        # Adjust based on content type and time slot
        content_multipliers = {
            'movie': 1.2,
            'series': 1.0,
            'news': 1.3,
            'sports': 1.5,
            'documentary': 0.8,
            'children': 1.1
        }
        
        multiplier = content_multipliers.get(obj.content_type, 1.0)
        
        # Prime time bonus (7 PM to 11 PM)
        if 19 <= obj.start_time.hour <= 22:
            multiplier *= 1.5
        
        potential_revenue = (obj.ad_break_duration * base_rate_per_second * multiplier)
        
        return round(potential_revenue, 2)
    
    def validate(self, data):
        """
        Validate schedule data consistency.
        """
        start_time = data.get('start_time')
        duration_minutes = data.get('duration_minutes')
        
        if start_time and duration_minutes:
            from datetime import timedelta
            end_time = start_time + timedelta(minutes=duration_minutes)
            data['end_time'] = end_time
        
        # Validate advertising breaks
        ad_breaks = data.get('advertising_breaks', 0)
        ad_duration = data.get('ad_break_duration', 0)
        
        if ad_breaks > 0 and ad_duration == 0:
            raise serializers.ValidationError({
                'ad_break_duration': _('Ad break duration must be specified when advertising breaks are present.')
            })
        
        if ad_breaks == 0 and ad_duration > 0:
            raise serializers.ValidationError({
                'advertising_breaks': _('Number of advertising breaks must be specified when ad duration is set.')
            })
        
        return data


class AudienceDemographicsSerializer(serializers.ModelSerializer):
    """
    Serializer for Audience Demographics data.
    
    Handles viewership analytics and demographic information.
    """
    
    channel_name = serializers.CharField(source='channel.name', read_only=True)
    zone_name = serializers.CharField(source='zone.name', read_only=True)
    age_distribution_parsed = serializers.SerializerMethodField()
    gender_distribution_parsed = serializers.SerializerMethodField()
    engagement_level = serializers.SerializerMethodField()
    
    class Meta:
        model = AudienceDemographics
        fields = [
            'id',
            'channel',
            'channel_name',
            'zone',
            'zone_name',
            'measurement_date',
            'total_viewers',
            'age_distribution',
            'age_distribution_parsed',
            'gender_distribution',
            'gender_distribution_parsed',
            'income_distribution',
            'education_distribution',
            'viewing_hours_per_week',
            'peak_viewing_times',
            'viewer_engagement_score',
            'engagement_level',
            'primary_age_group',
            'created_at',
            'updated_at'
        ]
        read_only_fields = ['created_at', 'updated_at']
    
    def get_age_distribution_parsed(self, obj):
        """
        Parse age distribution JSON data.
        """
        try:
            return json.loads(obj.age_distribution or '{}')
        except (json.JSONDecodeError, TypeError):
            return {}
    
    def get_gender_distribution_parsed(self, obj):
        """
        Parse gender distribution JSON data.
        """
        try:
            return json.loads(obj.gender_distribution or '{}')
        except (json.JSONDecodeError, TypeError):
            return {}
    
    def get_engagement_level(self, obj):
        """
        Categorize engagement score into levels.
        """
        score = obj.viewer_engagement_score
        
        if score >= 80:
            return 'high'
        elif score >= 60:
            return 'medium'
        elif score >= 40:
            return 'low'
        else:
            return 'very_low'
    
    def validate_age_distribution(self, value):
        """
        Validate age distribution JSON format.
        """
        if value:
            try:
                data = json.loads(value)
                if not isinstance(data, dict):
                    raise serializers.ValidationError(
                        _('Age distribution must be a valid JSON object.')
                    )
            except json.JSONDecodeError:
                raise serializers.ValidationError(
                    _('Age distribution must be valid JSON.')
                )
        return value
    
    def validate_gender_distribution(self, value):
        """
        Validate gender distribution JSON format.
        """
        if value:
            try:
                data = json.loads(value)
                if not isinstance(data, dict):
                    raise serializers.ValidationError(
                        _('Gender distribution must be a valid JSON object.')
                    )
            except json.JSONDecodeError:
                raise serializers.ValidationError(
                    _('Gender distribution must be valid JSON.')
                )
        return value