# Adtlas TV Advertising Platform - Channel Models Tests
# Comprehensive tests for channel-related models

from django.test import TestCase
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.utils import timezone
from decimal import Decimal
from datetime import datetime, timedelta
from channels.models import (
    GeographicZone, BroadcastNetwork, TVChannel, 
    ChannelCoverage, ContentSchedule, AudienceDemographics
)


class GeographicZoneModelTest(TestCase):
    """
    Test cases for the GeographicZone model.
    
    Tests model creation, validation, string representation,
    and business logic constraints.
    """
    
    def setUp(self):
        """Set up test data for GeographicZone tests."""
        self.zone_data = {
            'name': 'New York City',
            'zone_type': 'city',
            'description': 'Major metropolitan area in New York state',
            'population': 8500000,
            'area_sq_km': Decimal('783.8'),
            'is_active': True
        }
    
    def test_zone_creation(self):
        """Test successful creation of a geographic zone."""
        zone = GeographicZone.objects.create(**self.zone_data)
        
        self.assertEqual(zone.name, 'New York City')
        self.assertEqual(zone.zone_type, 'city')
        self.assertEqual(zone.population, 8500000)
        self.assertEqual(zone.area_sq_km, Decimal('783.8'))
        self.assertTrue(zone.is_active)
        self.assertIsNotNone(zone.created_at)
        self.assertIsNotNone(zone.updated_at)
    
    def test_zone_str_representation(self):
        """Test string representation of geographic zone."""
        zone = GeographicZone.objects.create(**self.zone_data)
        expected_str = f"{zone.name} ({zone.zone_type})"
        self.assertEqual(str(zone), expected_str)
    
    def test_zone_name_uniqueness(self):
        """Test that zone names must be unique."""
        GeographicZone.objects.create(**self.zone_data)
        
        # Attempt to create another zone with the same name
        with self.assertRaises(IntegrityError):
            GeographicZone.objects.create(**self.zone_data)
    
    def test_zone_type_choices(self):
        """Test that zone_type accepts only valid choices."""
        valid_types = ['country', 'state', 'city', 'region', 'dma']
        
        for zone_type in valid_types:
            zone_data = self.zone_data.copy()
            zone_data['name'] = f'Test Zone {zone_type}'
            zone_data['zone_type'] = zone_type
            
            zone = GeographicZone.objects.create(**zone_data)
            self.assertEqual(zone.zone_type, zone_type)
    
    def test_population_density_property(self):
        """Test the population density calculated property."""
        zone = GeographicZone.objects.create(**self.zone_data)
        expected_density = zone.population / float(zone.area_sq_km)
        self.assertAlmostEqual(zone.population_density, expected_density, places=2)
    
    def test_zone_with_zero_area(self):
        """Test zone with zero area (should handle division by zero)."""
        zone_data = self.zone_data.copy()
        zone_data['area_sq_km'] = Decimal('0')
        zone_data['name'] = 'Zero Area Zone'
        
        zone = GeographicZone.objects.create(**zone_data)
        self.assertEqual(zone.population_density, 0)


class BroadcastNetworkModelTest(TestCase):
    """
    Test cases for the BroadcastNetwork model.
    """
    
    def setUp(self):
        """Set up test data for BroadcastNetwork tests."""
        self.network_data = {
            'name': 'ABC Television Network',
            'description': 'American commercial broadcast television network',
            'website_url': 'https://abc.com',
            'headquarters_location': 'New York, NY',
            'is_active': True
        }
    
    def test_network_creation(self):
        """Test successful creation of a broadcast network."""
        network = BroadcastNetwork.objects.create(**self.network_data)
        
        self.assertEqual(network.name, 'ABC Television Network')
        self.assertEqual(network.website_url, 'https://abc.com')
        self.assertEqual(network.headquarters_location, 'New York, NY')
        self.assertTrue(network.is_active)
    
    def test_network_str_representation(self):
        """Test string representation of broadcast network."""
        network = BroadcastNetwork.objects.create(**self.network_data)
        self.assertEqual(str(network), network.name)
    
    def test_network_name_uniqueness(self):
        """Test that network names must be unique."""
        BroadcastNetwork.objects.create(**self.network_data)
        
        with self.assertRaises(IntegrityError):
            BroadcastNetwork.objects.create(**self.network_data)
    
    def test_network_optional_fields(self):
        """Test creation with only required fields."""
        minimal_data = {
            'name': 'Minimal Network',
            'is_active': True
        }
        
        network = BroadcastNetwork.objects.create(**minimal_data)
        self.assertEqual(network.name, 'Minimal Network')
        self.assertEqual(network.description, '')
        self.assertEqual(network.website_url, '')


class TVChannelModelTest(TestCase):
    """
    Test cases for the TVChannel model.
    """
    
    def setUp(self):
        """Set up test data for TVChannel tests."""
        self.network = BroadcastNetwork.objects.create(
            name='Test Network',
            is_active=True
        )
        
        self.channel_data = {
            'name': 'WABC-TV',
            'call_sign': 'WABC',
            'frequency': Decimal('7.1'),
            'network': self.network,
            'description': 'ABC affiliate in New York',
            'website_url': 'https://abc7ny.com',
            'is_active': True
        }
    
    def test_channel_creation(self):
        """Test successful creation of a TV channel."""
        channel = TVChannel.objects.create(**self.channel_data)
        
        self.assertEqual(channel.name, 'WABC-TV')
        self.assertEqual(channel.call_sign, 'WABC')
        self.assertEqual(channel.frequency, Decimal('7.1'))
        self.assertEqual(channel.network, self.network)
        self.assertTrue(channel.is_active)
    
    def test_channel_str_representation(self):
        """Test string representation of TV channel."""
        channel = TVChannel.objects.create(**self.channel_data)
        expected_str = f"{channel.name} ({channel.call_sign})"
        self.assertEqual(str(channel), expected_str)
    
    def test_call_sign_uniqueness(self):
        """Test that call signs must be unique."""
        TVChannel.objects.create(**self.channel_data)
        
        # Create another channel with same call sign
        duplicate_data = self.channel_data.copy()
        duplicate_data['name'] = 'Another WABC'
        
        with self.assertRaises(IntegrityError):
            TVChannel.objects.create(**duplicate_data)
    
    def test_channel_without_network(self):
        """Test channel creation without a network (independent station)."""
        channel_data = self.channel_data.copy()
        channel_data['network'] = None
        channel_data['call_sign'] = 'WIND'
        channel_data['name'] = 'Independent Station'
        
        channel = TVChannel.objects.create(**channel_data)
        self.assertIsNone(channel.network)
        self.assertEqual(channel.call_sign, 'WIND')
    
    def test_frequency_validation(self):
        """Test that frequency must be positive."""
        channel_data = self.channel_data.copy()
        channel_data['frequency'] = Decimal('-1.0')
        channel_data['call_sign'] = 'WNEG'
        
        # Note: This test assumes model-level validation
        # In practice, you might implement this in clean() method
        channel = TVChannel.objects.create(**channel_data)
        self.assertEqual(channel.frequency, Decimal('-1.0'))
        # Add validation logic in model if needed


class ChannelCoverageModelTest(TestCase):
    """
    Test cases for the ChannelCoverage model.
    """
    
    def setUp(self):
        """Set up test data for ChannelCoverage tests."""
        self.network = BroadcastNetwork.objects.create(
            name='Test Network',
            is_active=True
        )
        
        self.channel = TVChannel.objects.create(
            name='Test Channel',
            call_sign='TEST',
            frequency=Decimal('10.1'),
            network=self.network,
            is_active=True
        )
        
        self.zone = GeographicZone.objects.create(
            name='Test City',
            zone_type='city',
            population=100000,
            area_sq_km=Decimal('100.0'),
            is_active=True
        )
        
        self.coverage_data = {
            'channel': self.channel,
            'zone': self.zone,
            'coverage_percentage': Decimal('85.5'),
            'signal_strength': 'strong',
            'is_primary': True
        }
    
    def test_coverage_creation(self):
        """Test successful creation of channel coverage."""
        coverage = ChannelCoverage.objects.create(**self.coverage_data)
        
        self.assertEqual(coverage.channel, self.channel)
        self.assertEqual(coverage.zone, self.zone)
        self.assertEqual(coverage.coverage_percentage, Decimal('85.5'))
        self.assertEqual(coverage.signal_strength, 'strong')
        self.assertTrue(coverage.is_primary)
    
    def test_coverage_str_representation(self):
        """Test string representation of channel coverage."""
        coverage = ChannelCoverage.objects.create(**self.coverage_data)
        expected_str = f"{coverage.channel.name} in {coverage.zone.name} ({coverage.coverage_percentage}%)"
        self.assertEqual(str(coverage), expected_str)
    
    def test_coverage_unique_together(self):
        """Test that channel-zone combination must be unique."""
        ChannelCoverage.objects.create(**self.coverage_data)
        
        # Attempt to create duplicate coverage
        with self.assertRaises(IntegrityError):
            ChannelCoverage.objects.create(**self.coverage_data)
    
    def test_signal_strength_choices(self):
        """Test valid signal strength choices."""
        valid_strengths = ['excellent', 'strong', 'good', 'fair', 'poor', 'weak']
        
        for strength in valid_strengths:
            coverage_data = self.coverage_data.copy()
            coverage_data['signal_strength'] = strength
            
            # Create new zone for each test to avoid unique constraint
            zone = GeographicZone.objects.create(
                name=f'Zone {strength}',
                zone_type='city',
                is_active=True
            )
            coverage_data['zone'] = zone
            
            coverage = ChannelCoverage.objects.create(**coverage_data)
            self.assertEqual(coverage.signal_strength, strength)
    
    def test_coverage_percentage_bounds(self):
        """Test coverage percentage validation (should be 0-100)."""
        # Test valid percentage
        coverage = ChannelCoverage.objects.create(**self.coverage_data)
        self.assertEqual(coverage.coverage_percentage, Decimal('85.5'))
        
        # Test edge cases (model should handle these, but validation might be in forms/serializers)
        edge_cases = [Decimal('0.0'), Decimal('100.0'), Decimal('150.0')]
        
        for percentage in edge_cases:
            zone = GeographicZone.objects.create(
                name=f'Zone {percentage}',
                zone_type='city',
                is_active=True
            )
            
            coverage_data = self.coverage_data.copy()
            coverage_data['coverage_percentage'] = percentage
            coverage_data['zone'] = zone
            
            coverage = ChannelCoverage.objects.create(**coverage_data)
            self.assertEqual(coverage.coverage_percentage, percentage)


class ContentScheduleModelTest(TestCase):
    """
    Test cases for the ContentSchedule model.
    """
    
    def setUp(self):
        """Set up test data for ContentSchedule tests."""
        self.network = BroadcastNetwork.objects.create(
            name='Test Network',
            is_active=True
        )
        
        self.channel = TVChannel.objects.create(
            name='Test Channel',
            call_sign='TEST',
            frequency=Decimal('10.1'),
            network=self.network,
            is_active=True
        )
        
        self.schedule_data = {
            'channel': self.channel,
            'program_title': 'Evening News',
            'start_time': timezone.now(),
            'end_time': timezone.now() + timedelta(hours=1),
            'description': 'Local and national news coverage',
            'genre': 'news',
            'rating': 'TV-G'
        }
    
    def test_schedule_creation(self):
        """Test successful creation of content schedule."""
        schedule = ContentSchedule.objects.create(**self.schedule_data)
        
        self.assertEqual(schedule.channel, self.channel)
        self.assertEqual(schedule.program_title, 'Evening News')
        self.assertEqual(schedule.genre, 'news')
        self.assertEqual(schedule.rating, 'TV-G')
    
    def test_schedule_str_representation(self):
        """Test string representation of content schedule."""
        schedule = ContentSchedule.objects.create(**self.schedule_data)
        expected_str = f"{schedule.program_title} on {schedule.channel.name} at {schedule.start_time.strftime('%Y-%m-%d %H:%M')}"
        self.assertEqual(str(schedule), expected_str)
    
    def test_schedule_duration_property(self):
        """Test the duration calculated property."""
        schedule = ContentSchedule.objects.create(**self.schedule_data)
        expected_duration = schedule.end_time - schedule.start_time
        self.assertEqual(schedule.duration, expected_duration)
    
    def test_schedule_without_end_time(self):
        """Test schedule creation without end time (ongoing program)."""
        schedule_data = self.schedule_data.copy()
        schedule_data['end_time'] = None
        schedule_data['program_title'] = 'Live Stream'
        
        schedule = ContentSchedule.objects.create(**schedule_data)
        self.assertIsNone(schedule.end_time)
        self.assertIsNone(schedule.duration)
    
    def test_genre_choices(self):
        """Test valid genre choices."""
        valid_genres = ['news', 'sports', 'entertainment', 'documentary', 'movie', 'series', 'reality', 'other']
        
        for genre in valid_genres:
            schedule_data = self.schedule_data.copy()
            schedule_data['genre'] = genre
            schedule_data['program_title'] = f'Test {genre}'
            schedule_data['start_time'] = timezone.now() + timedelta(hours=len(valid_genres))
            schedule_data['end_time'] = schedule_data['start_time'] + timedelta(hours=1)
            
            schedule = ContentSchedule.objects.create(**schedule_data)
            self.assertEqual(schedule.genre, genre)
    
    def test_rating_choices(self):
        """Test valid rating choices."""
        valid_ratings = ['TV-Y', 'TV-Y7', 'TV-G', 'TV-PG', 'TV-14', 'TV-MA']
        
        for rating in valid_ratings:
            schedule_data = self.schedule_data.copy()
            schedule_data['rating'] = rating
            schedule_data['program_title'] = f'Test {rating}'
            schedule_data['start_time'] = timezone.now() + timedelta(hours=len(valid_ratings))
            schedule_data['end_time'] = schedule_data['start_time'] + timedelta(hours=1)
            
            schedule = ContentSchedule.objects.create(**schedule_data)
            self.assertEqual(schedule.rating, rating)


class AudienceDemographicsModelTest(TestCase):
    """
    Test cases for the AudienceDemographics model.
    """
    
    def setUp(self):
        """Set up test data for AudienceDemographics tests."""
        self.network = BroadcastNetwork.objects.create(
            name='Test Network',
            is_active=True
        )
        
        self.channel = TVChannel.objects.create(
            name='Test Channel',
            call_sign='TEST',
            frequency=Decimal('10.1'),
            network=self.network,
            is_active=True
        )
        
        self.demographics_data = {
            'channel': self.channel,
            'measurement_date': timezone.now().date(),
            'total_viewers': 150000,
            'age_18_34': 25000,
            'age_35_54': 60000,
            'age_55_plus': 65000,
            'male_viewers': 75000,
            'female_viewers': 75000,
            'household_income_high': 45000,
            'household_income_medium': 75000,
            'household_income_low': 30000
        }
    
    def test_demographics_creation(self):
        """Test successful creation of audience demographics."""
        demographics = AudienceDemographics.objects.create(**self.demographics_data)
        
        self.assertEqual(demographics.channel, self.channel)
        self.assertEqual(demographics.total_viewers, 150000)
        self.assertEqual(demographics.age_18_34, 25000)
        self.assertEqual(demographics.male_viewers, 75000)
        self.assertEqual(demographics.female_viewers, 75000)
    
    def test_demographics_str_representation(self):
        """Test string representation of audience demographics."""
        demographics = AudienceDemographics.objects.create(**self.demographics_data)
        expected_str = f"{demographics.channel.name} - {demographics.measurement_date} ({demographics.total_viewers:,} viewers)"
        self.assertEqual(str(demographics), expected_str)
    
    def test_demographics_unique_together(self):
        """Test that channel-date combination must be unique."""
        AudienceDemographics.objects.create(**self.demographics_data)
        
        # Attempt to create duplicate demographics for same channel and date
        with self.assertRaises(IntegrityError):
            AudienceDemographics.objects.create(**self.demographics_data)
    
    def test_demographics_calculated_properties(self):
        """Test calculated properties for demographics analysis."""
        demographics = AudienceDemographics.objects.create(**self.demographics_data)
        
        # Test age distribution percentages
        total_age_viewers = demographics.age_18_34 + demographics.age_35_54 + demographics.age_55_plus
        expected_18_34_pct = (demographics.age_18_34 / total_age_viewers) * 100
        
        # Note: These would be implemented as properties in the model
        # For now, just verify the data is stored correctly
        self.assertEqual(demographics.age_18_34, 25000)
        self.assertEqual(demographics.age_35_54, 60000)
        self.assertEqual(demographics.age_55_plus, 65000)
    
    def test_demographics_data_consistency(self):
        """Test that demographic data is internally consistent."""
        demographics = AudienceDemographics.objects.create(**self.demographics_data)
        
        # Gender totals should equal total viewers
        gender_total = demographics.male_viewers + demographics.female_viewers
        self.assertEqual(gender_total, demographics.total_viewers)
        
        # Income categories should equal total viewers
        income_total = (demographics.household_income_high + 
                       demographics.household_income_medium + 
                       demographics.household_income_low)
        self.assertEqual(income_total, demographics.total_viewers)


class ModelRelationshipTest(TestCase):
    """
    Test cases for model relationships and cascading behavior.
    """
    
    def setUp(self):
        """Set up test data for relationship tests."""
        self.network = BroadcastNetwork.objects.create(
            name='Test Network',
            is_active=True
        )
        
        self.channel = TVChannel.objects.create(
            name='Test Channel',
            call_sign='TEST',
            frequency=Decimal('10.1'),
            network=self.network,
            is_active=True
        )
        
        self.zone = GeographicZone.objects.create(
            name='Test Zone',
            zone_type='city',
            is_active=True
        )
    
    def test_network_channel_relationship(self):
        """Test the relationship between networks and channels."""
        # Test forward relationship
        self.assertEqual(self.channel.network, self.network)
        
        # Test reverse relationship
        self.assertIn(self.channel, self.network.channels.all())
    
    def test_channel_coverage_relationship(self):
        """Test the relationship between channels and coverage areas."""
        coverage = ChannelCoverage.objects.create(
            channel=self.channel,
            zone=self.zone,
            coverage_percentage=Decimal('80.0'),
            signal_strength='strong'
        )
        
        # Test forward relationship
        self.assertEqual(coverage.channel, self.channel)
        self.assertEqual(coverage.zone, self.zone)
        
        # Test reverse relationships
        self.assertIn(coverage, self.channel.coverage_areas.all())
        self.assertIn(coverage, self.zone.coverage_areas.all())
    
    def test_network_deletion_cascade(self):
        """Test what happens when a network is deleted."""
        channel_id = self.channel.id
        
        # Delete the network
        self.network.delete()
        
        # Channel should still exist but network should be None
        channel = TVChannel.objects.get(id=channel_id)
        self.assertIsNone(channel.network)
    
    def test_channel_deletion_cascade(self):
        """Test cascading deletion when a channel is deleted."""
        # Create related objects
        coverage = ChannelCoverage.objects.create(
            channel=self.channel,
            zone=self.zone,
            coverage_percentage=Decimal('80.0'),
            signal_strength='strong'
        )
        
        schedule = ContentSchedule.objects.create(
            channel=self.channel,
            program_title='Test Program',
            start_time=timezone.now(),
            end_time=timezone.now() + timedelta(hours=1)
        )
        
        demographics = AudienceDemographics.objects.create(
            channel=self.channel,
            measurement_date=timezone.now().date(),
            total_viewers=100000,
            age_18_34=20000,
            age_35_54=40000,
            age_55_plus=40000,
            male_viewers=50000,
            female_viewers=50000,
            household_income_high=30000,
            household_income_medium=50000,
            household_income_low=20000
        )
        
        coverage_id = coverage.id
        schedule_id = schedule.id
        demographics_id = demographics.id
        
        # Delete the channel
        self.channel.delete()
        
        # Related objects should be deleted due to CASCADE
        self.assertFalse(ChannelCoverage.objects.filter(id=coverage_id).exists())
        self.assertFalse(ContentSchedule.objects.filter(id=schedule_id).exists())
        self.assertFalse(AudienceDemographics.objects.filter(id=demographics_id).exists())
        
        # Zone should still exist
        self.assertTrue(GeographicZone.objects.filter(id=self.zone.id).exists())