# Adtlas TV Advertising Platform - Channels Views
# Comprehensive view handlers for TV channels, networks, and coverage management

from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse, Http404
from django.views.generic import ListView, DetailView, TemplateView
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from django.db.models import Q, Count, Sum, Avg, F, Prefetch
from django.core.paginator import Paginator
from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
from datetime import datetime, timedelta
import json
import logging

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

# Configure logging
logger = logging.getLogger(__name__)


class ChannelListView(ListView):
    """
    Display paginated list of TV channels with filtering and search capabilities.
    
    Features:
    - Channel type filtering (broadcast, cable, satellite, streaming)
    - Content category filtering
    - Network-based filtering
    - Search by channel name, call sign, or number
    - Coverage zone filtering
    - Sorting options (name, channel number, launch date)
    """
    
    model = TVChannel
    template_name = 'channels/channel_list.html'
    context_object_name = 'channels'
    paginate_by = 20
    
    def get_queryset(self):
        """
        Build optimized queryset with filters and search.
        """
        queryset = TVChannel.objects.filter(is_active=True).select_related(
            'network'
        ).prefetch_related(
            'coverage_zones',
            'audience_demographics'
        )
        
        # Apply filters from GET parameters
        channel_type = self.request.GET.get('type')
        if channel_type:
            queryset = queryset.filter(channel_type=channel_type)
        
        content_category = self.request.GET.get('category')
        if content_category:
            queryset = queryset.filter(content_category=content_category)
        
        network_id = self.request.GET.get('network')
        if network_id:
            try:
                queryset = queryset.filter(network_id=int(network_id))
            except (ValueError, TypeError):
                pass
        
        zone_id = self.request.GET.get('zone')
        if zone_id:
            try:
                queryset = queryset.filter(
                    coverage_zones__zone_id=int(zone_id),
                    coverage_zones__is_active=True
                )
            except (ValueError, TypeError):
                pass
        
        # Search functionality
        search_query = self.request.GET.get('search')
        if search_query:
            queryset = queryset.filter(
                Q(name__icontains=search_query) |
                Q(call_sign__icontains=search_query) |
                Q(channel_number__icontains=search_query) |
                Q(network__name__icontains=search_query)
            )
        
        # Sorting
        sort_by = self.request.GET.get('sort', 'channel_number')
        valid_sorts = ['name', 'channel_number', 'launch_date', '-launch_date']
        if sort_by in valid_sorts:
            queryset = queryset.order_by(sort_by)
        else:
            queryset = queryset.order_by('channel_number')
        
        return queryset.distinct()
    
    def get_context_data(self, **kwargs):
        """
        Add filter options and statistics to context.
        """
        context = super().get_context_data(**kwargs)
        
        # Get filter options
        context['channel_types'] = TVChannel.CHANNEL_TYPE_CHOICES
        context['content_categories'] = TVChannel.CONTENT_CATEGORY_CHOICES
        context['networks'] = BroadcastNetwork.objects.filter(
            is_active=True
        ).order_by('name')
        context['zones'] = GeographicZone.objects.filter(
            is_active=True,
            zone_type__in=['country', 'state', 'city']
        ).order_by('zone_type', 'name')
        
        # Current filter values
        context['current_filters'] = {
            'type': self.request.GET.get('type', ''),
            'category': self.request.GET.get('category', ''),
            'network': self.request.GET.get('network', ''),
            'zone': self.request.GET.get('zone', ''),
            'search': self.request.GET.get('search', ''),
            'sort': self.request.GET.get('sort', 'channel_number')
        }
        
        # Channel statistics
        context['total_channels'] = self.get_queryset().count()
        context['channel_stats'] = self._get_channel_statistics()
        
        return context
    
    def _get_channel_statistics(self):
        """
        Calculate channel statistics for dashboard display.
        """
        cache_key = 'channel_stats'
        stats = cache.get(cache_key)
        
        if not stats:
            stats = {
                'by_type': dict(TVChannel.objects.filter(
                    is_active=True
                ).values_list('channel_type').annotate(
                    count=Count('id')
                )),
                'by_category': dict(TVChannel.objects.filter(
                    is_active=True
                ).values_list('content_category').annotate(
                    count=Count('id')
                )),
                'hd_channels': TVChannel.objects.filter(
                    is_active=True, hd_available=True
                ).count(),
                'uhd_channels': TVChannel.objects.filter(
                    is_active=True, uhd_4k_available=True
                ).count(),
                'premium_channels': TVChannel.objects.filter(
                    is_active=True, is_premium=True
                ).count()
            }
            cache.set(cache_key, stats, 300)  # Cache for 5 minutes
        
        return stats


class ChannelDetailView(DetailView):
    """
    Display detailed information about a specific TV channel.
    
    Includes:
    - Channel metadata and specifications
    - Network information
    - Coverage zones and population reach
    - Current and upcoming programming schedule
    - Audience demographics
    - Advertising opportunities
    """
    
    model = TVChannel
    template_name = 'channels/channel_detail.html'
    context_object_name = 'channel'
    
    def get_queryset(self):
        """
        Optimize queryset with related data.
        """
        return TVChannel.objects.filter(
            is_active=True
        ).select_related(
            'network'
        ).prefetch_related(
            'coverage_zones__zone',
            'content_schedules',
            'audience_demographics'
        )
    
    def get_context_data(self, **kwargs):
        """
        Add comprehensive channel data to context.
        """
        context = super().get_context_data(**kwargs)
        channel = self.object
        
        # Coverage information
        context['coverage_data'] = self._get_coverage_data(channel)
        
        # Programming schedule
        context['current_program'] = self._get_current_program(channel)
        context['upcoming_programs'] = self._get_upcoming_programs(channel)
        
        # Audience demographics
        context['demographics'] = self._get_demographics_data(channel)
        
        # Related channels (same network)
        context['related_channels'] = self._get_related_channels(channel)
        
        # Advertising information
        context['advertising_data'] = self._get_advertising_data(channel)
        
        return context
    
    def _get_coverage_data(self, channel):
        """
        Calculate coverage statistics and zone information.
        """
        coverage = ChannelCoverage.objects.filter(
            channel=channel,
            is_active=True
        ).select_related('zone').aggregate(
            total_population=Sum('zone__population'),
            total_households=Sum('zone__tv_households'),
            avg_penetration=Avg('penetration_rate'),
            total_subscribers=Sum('subscriber_count')
        )
        
        coverage_zones = ChannelCoverage.objects.filter(
            channel=channel,
            is_active=True
        ).select_related('zone').order_by(
            'zone__zone_type', 'zone__name'
        )
        
        return {
            'statistics': coverage,
            'zones': coverage_zones,
            'zone_count': coverage_zones.count()
        }
    
    def _get_current_program(self, channel):
        """
        Get currently airing program.
        """
        now = timezone.now()
        return ContentSchedule.objects.filter(
            channel=channel,
            start_time__lte=now,
            end_time__gte=now
        ).first()
    
    def _get_upcoming_programs(self, channel, hours=6):
        """
        Get upcoming programs for the next few hours.
        """
        now = timezone.now()
        end_time = now + timedelta(hours=hours)
        
        return ContentSchedule.objects.filter(
            channel=channel,
            start_time__gte=now,
            start_time__lte=end_time
        ).order_by('start_time')[:10]
    
    def _get_demographics_data(self, channel):
        """
        Get latest audience demographics data.
        """
        latest_demo = AudienceDemographics.objects.filter(
            channel=channel
        ).order_by('-measurement_date').first()
        
        if latest_demo:
            return {
                'latest': latest_demo,
                'age_distribution': json.loads(latest_demo.age_distribution or '{}'),
                'gender_distribution': json.loads(latest_demo.gender_distribution or '{}'),
                'viewing_trends': self._get_viewing_trends(channel)
            }
        
        return None
    
    def _get_viewing_trends(self, channel, days=30):
        """
        Calculate viewing trends over the past month.
        """
        cutoff_date = timezone.now().date() - timedelta(days=days)
        
        trends = AudienceDemographics.objects.filter(
            channel=channel,
            measurement_date__gte=cutoff_date
        ).aggregate(
            avg_viewers=Avg('total_viewers'),
            avg_engagement=Avg('viewer_engagement_score'),
            avg_viewing_hours=Avg('viewing_hours_per_week')
        )
        
        return trends
    
    def _get_related_channels(self, channel):
        """
        Get other channels from the same network.
        """
        return TVChannel.objects.filter(
            network=channel.network,
            is_active=True
        ).exclude(
            id=channel.id
        ).order_by('channel_number')[:5]
    
    def _get_advertising_data(self, channel):
        """
        Calculate advertising opportunities and metrics.
        """
        if not channel.advertising_enabled:
            return None
        
        # Get advertising breaks from recent schedule
        recent_date = timezone.now().date() - timedelta(days=7)
        
        ad_stats = ContentSchedule.objects.filter(
            channel=channel,
            start_time__date__gte=recent_date,
            advertising_breaks__gt=0
        ).aggregate(
            avg_breaks_per_hour=Avg('advertising_breaks'),
            avg_ad_duration=Avg('ad_break_duration'),
            total_ad_inventory=Sum('ad_break_duration')
        )
        
        return {
            'statistics': ad_stats,
            'cpm_estimate': self._calculate_cpm_estimate(channel),
            'prime_time_availability': self._get_prime_time_availability(channel)
        }
    
    def _calculate_cpm_estimate(self, channel):
        """
        Calculate estimated CPM based on audience and market factors.
        """
        # This is a simplified calculation - in reality, this would involve
        # complex market analysis and real-time bidding data
        
        latest_demo = AudienceDemographics.objects.filter(
            channel=channel
        ).order_by('-measurement_date').first()
        
        if not latest_demo:
            return None
        
        base_cpm = 5.0  # Base CPM in dollars
        
        # Adjust based on engagement score
        engagement_multiplier = latest_demo.viewer_engagement_score / 50.0
        
        # Adjust based on content category
        category_multipliers = {
            'news': 1.2,
            'sports': 1.5,
            'entertainment': 1.0,
            'educational': 0.8,
            'children': 1.1,
            'lifestyle': 1.1,
            'documentary': 0.9
        }
        
        category_multiplier = category_multipliers.get(
            channel.content_category, 1.0
        )
        
        estimated_cpm = base_cpm * engagement_multiplier * category_multiplier
        
        return round(estimated_cpm, 2)
    
    def _get_prime_time_availability(self, channel):
        """
        Check advertising availability during prime time hours.
        """
        # Prime time is typically 7 PM to 11 PM
        today = timezone.now().date()
        prime_start = timezone.make_aware(
            datetime.combine(today, datetime.min.time().replace(hour=19))
        )
        prime_end = timezone.make_aware(
            datetime.combine(today, datetime.min.time().replace(hour=23))
        )
        
        prime_programs = ContentSchedule.objects.filter(
            channel=channel,
            start_time__gte=prime_start,
            start_time__lt=prime_end
        ).count()
        
        return {
            'programs_scheduled': prime_programs,
            'availability': 'high' if prime_programs < 3 else 'limited'
        }


class NetworkListView(ListView):
    """
    Display list of broadcast networks with hierarchy and statistics.
    """
    
    model = BroadcastNetwork
    template_name = 'channels/network_list.html'
    context_object_name = 'networks'
    paginate_by = 15
    
    def get_queryset(self):
        """
        Get networks with channel counts and filtering.
        """
        queryset = BroadcastNetwork.objects.filter(
            is_active=True
        ).annotate(
            channel_count=Count('channels', filter=Q(channels__is_active=True))
        ).order_by('network_type', 'name')
        
        # Filter by network type
        network_type = self.request.GET.get('type')
        if network_type:
            queryset = queryset.filter(network_type=network_type)
        
        # Search functionality
        search_query = self.request.GET.get('search')
        if search_query:
            queryset = queryset.filter(
                Q(name__icontains=search_query) |
                Q(short_name__icontains=search_query)
            )
        
        return queryset
    
    def get_context_data(self, **kwargs):
        """
        Add network statistics and filter options.
        """
        context = super().get_context_data(**kwargs)
        
        context['network_types'] = BroadcastNetwork.NETWORK_TYPE_CHOICES
        context['current_filters'] = {
            'type': self.request.GET.get('type', ''),
            'search': self.request.GET.get('search', '')
        }
        
        # Network statistics
        context['network_stats'] = {
            'total_networks': self.get_queryset().count(),
            'by_type': dict(BroadcastNetwork.objects.filter(
                is_active=True
            ).values_list('network_type').annotate(
                count=Count('id')
            ))
        }
        
        return context


class NetworkDetailView(DetailView):
    """
    Display detailed information about a broadcast network.
    """
    
    model = BroadcastNetwork
    template_name = 'channels/network_detail.html'
    context_object_name = 'network'
    
    def get_queryset(self):
        """
        Optimize queryset with related data.
        """
        return BroadcastNetwork.objects.filter(
            is_active=True
        ).prefetch_related(
            'channels',
            'subsidiary_networks'
        )
    
    def get_context_data(self, **kwargs):
        """
        Add network channels and subsidiary information.
        """
        context = super().get_context_data(**kwargs)
        network = self.object
        
        # Network channels
        context['channels'] = network.channels.filter(
            is_active=True
        ).order_by('channel_number')
        
        # Subsidiary networks
        context['subsidiaries'] = network.subsidiary_networks.filter(
            is_active=True
        ).order_by('name')
        
        # Network statistics
        context['network_stats'] = self._get_network_statistics(network)
        
        return context
    
    def _get_network_statistics(self, network):
        """
        Calculate comprehensive network statistics.
        """
        channels = network.channels.filter(is_active=True)
        
        # Coverage statistics
        coverage_stats = ChannelCoverage.objects.filter(
            channel__in=channels,
            is_active=True
        ).aggregate(
            total_population=Sum('zone__population'),
            total_subscribers=Sum('subscriber_count'),
            avg_penetration=Avg('penetration_rate')
        )
        
        # Content statistics
        content_stats = {
            'total_channels': channels.count(),
            'hd_channels': channels.filter(hd_available=True).count(),
            'premium_channels': channels.filter(is_premium=True).count(),
            'by_category': dict(channels.values_list(
                'content_category'
            ).annotate(count=Count('id')))
        }
        
        return {
            'coverage': coverage_stats,
            'content': content_stats
        }


@method_decorator(cache_page(300), name='dispatch')  # Cache for 5 minutes
class CoverageMapView(TemplateView):
    """
    Display interactive coverage map showing channel availability by zone.
    """
    
    template_name = 'channels/coverage_map.html'
    
    def get_context_data(self, **kwargs):
        """
        Prepare data for coverage map visualization.
        """
        context = super().get_context_data(**kwargs)
        
        # Get coverage data for map
        context['coverage_data'] = self._get_coverage_map_data()
        context['zones'] = GeographicZone.objects.filter(
            is_active=True
        ).order_by('zone_type', 'name')
        
        return context
    
    def _get_coverage_map_data(self):
        """
        Prepare coverage data for map visualization.
        """
        coverage_data = []
        
        zones = GeographicZone.objects.filter(
            is_active=True,
            latitude__isnull=False,
            longitude__isnull=False
        ).prefetch_related(
            Prefetch(
                'channel_coverage',
                queryset=ChannelCoverage.objects.filter(
                    is_active=True
                ).select_related('channel')
            )
        )
        
        for zone in zones:
            zone_data = {
                'id': zone.id,
                'name': zone.name,
                'type': zone.zone_type,
                'latitude': float(zone.latitude),
                'longitude': float(zone.longitude),
                'population': zone.population,
                'channels': []
            }
            
            for coverage in zone.channel_coverage.all():
                channel_data = {
                    'id': coverage.channel.id,
                    'name': coverage.channel.name,
                    'number': coverage.channel.channel_number,
                    'signal_strength': coverage.signal_strength,
                    'penetration': coverage.penetration_rate
                }
                zone_data['channels'].append(channel_data)
            
            coverage_data.append(zone_data)
        
        return coverage_data


def channel_schedule_api(request, channel_id):
    """
    API endpoint for channel programming schedule.
    
    Returns JSON data for current and upcoming programs.
    """
    try:
        channel = get_object_or_404(TVChannel, id=channel_id, is_active=True)
        
        # Get date range from parameters
        start_date = request.GET.get('start_date')
        end_date = request.GET.get('end_date')
        
        if start_date:
            try:
                start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
            except ValueError:
                start_date = timezone.now().date()
        else:
            start_date = timezone.now().date()
        
        if end_date:
            try:
                end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
            except ValueError:
                end_date = start_date + timedelta(days=1)
        else:
            end_date = start_date + timedelta(days=1)
        
        # Get schedule data
        schedule = ContentSchedule.objects.filter(
            channel=channel,
            start_time__date__gte=start_date,
            start_time__date__lte=end_date
        ).order_by('start_time')
        
        # Format response data
        schedule_data = []
        for program in schedule:
            program_data = {
                'id': program.id,
                'title': program.title,
                'description': program.description,
                'content_type': program.content_type,
                'genre': program.genre,
                'start_time': program.start_time.isoformat(),
                'end_time': program.end_time.isoformat(),
                'duration_minutes': program.duration_minutes,
                'is_live': program.is_live,
                'is_premiere': program.is_premiere,
                'rating': program.rating,
                'advertising_breaks': program.advertising_breaks
            }
            schedule_data.append(program_data)
        
        return JsonResponse({
            'success': True,
            'channel': {
                'id': channel.id,
                'name': channel.name,
                'call_sign': channel.call_sign,
                'number': channel.channel_number
            },
            'schedule': schedule_data,
            'date_range': {
                'start': start_date.isoformat(),
                'end': end_date.isoformat()
            }
        })
    
    except Exception as e:
        logger.error(f"Error in channel_schedule_api: {str(e)}")
        return JsonResponse({
            'success': False,
            'error': 'Failed to retrieve schedule data'
        }, status=500)


def zone_channels_api(request, zone_id):
    """
    API endpoint for channels available in a specific zone.
    
    Returns JSON data with channel availability and coverage information.
    """
    try:
        zone = get_object_or_404(GeographicZone, id=zone_id, is_active=True)
        
        # Get channel coverage for this zone
        coverage = ChannelCoverage.objects.filter(
            zone=zone,
            is_active=True
        ).select_related('channel', 'channel__network').order_by(
            'channel__channel_number'
        )
        
        # Format response data
        channels_data = []
        for cov in coverage:
            channel = cov.channel
            channel_data = {
                'id': channel.id,
                'name': channel.name,
                'call_sign': channel.call_sign,
                'channel_number': channel.channel_number,
                'channel_type': channel.channel_type,
                'content_category': channel.content_category,
                'network': {
                    'id': channel.network.id,
                    'name': channel.network.name,
                    'short_name': channel.network.short_name
                },
                'coverage': {
                    'signal_strength': cov.signal_strength,
                    'coverage_percentage': cov.coverage_percentage,
                    'subscriber_count': cov.subscriber_count,
                    'penetration_rate': cov.penetration_rate
                },
                'features': {
                    'hd_available': channel.hd_available,
                    'uhd_4k_available': channel.uhd_4k_available,
                    'is_premium': channel.is_premium,
                    'advertising_enabled': channel.advertising_enabled
                }
            }
            channels_data.append(channel_data)
        
        return JsonResponse({
            'success': True,
            'zone': {
                'id': zone.id,
                'name': zone.name,
                'type': zone.zone_type,
                'population': zone.population,
                'tv_households': zone.tv_households
            },
            'channels': channels_data,
            'total_channels': len(channels_data)
        })
    
    except Exception as e:
        logger.error(f"Error in zone_channels_api: {str(e)}")
        return JsonResponse({
            'success': False,
            'error': 'Failed to retrieve zone channels data'
        }, status=500)


def network_coverage_api(request, network_id):
    """
    API endpoint for network coverage statistics.
    
    Returns comprehensive coverage data for all channels in a network.
    """
    try:
        network = get_object_or_404(BroadcastNetwork, id=network_id, is_active=True)
        
        # Get all channels for this network
        channels = network.channels.filter(is_active=True)
        
        # Calculate coverage statistics
        coverage_stats = ChannelCoverage.objects.filter(
            channel__in=channels,
            is_active=True
        ).aggregate(
            total_zones=Count('zone', distinct=True),
            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')
        )
        
        # Get coverage by zone type
        zone_coverage = ChannelCoverage.objects.filter(
            channel__in=channels,
            is_active=True
        ).values(
            'zone__zone_type'
        ).annotate(
            zone_count=Count('zone', distinct=True),
            population=Sum('zone__population')
        ).order_by('zone__zone_type')
        
        return JsonResponse({
            'success': True,
            'network': {
                'id': network.id,
                'name': network.name,
                'short_name': network.short_name,
                'type': network.network_type
            },
            'coverage_statistics': coverage_stats,
            'zone_breakdown': list(zone_coverage),
            'channel_count': channels.count()
        })
    
    except Exception as e:
        logger.error(f"Error in network_coverage_api: {str(e)}")
        return JsonResponse({
            'success': False,
            'error': 'Failed to retrieve network coverage data'
        }, status=500)