# -*- coding: utf-8 -*-
"""
Advertisers App Tests

This module contains comprehensive tests for the Advertisers application.
It includes unit tests, integration tests, and API tests for all models,
views, serializers, and business logic.
"""

import json
from decimal import Decimal
from django.test import TestCase, TransactionTestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test.utils import override_settings
from unittest.mock import patch, Mock

from .models import (
    BrandCategory,
    Agency,
    Advertiser,
    Brand,
    AdvertiserContact,
    AgencyUser,
    AdvertiserBilling
)
from .serializers import (
    BrandCategorySerializer,
    AgencySerializer,
    AdvertiserSerializer,
    BrandSerializer,
    AdvertiserContactSerializer,
    AgencyUserSerializer,
    AdvertiserBillingSerializer
)

# Get User model
User = get_user_model()


class BrandCategoryModelTest(TestCase):
    """
    Test cases for BrandCategory model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.parent_category = BrandCategory.objects.create(
            name="Technology",
            description="Technology brands",
            order=1
        )
    
    def test_create_brand_category(self):
        """Test creating a brand category."""
        category = BrandCategory.objects.create(
            name="Software",
            description="Software companies",
            parent=self.parent_category,
            order=1
        )
        
        self.assertEqual(category.name, "Software")
        self.assertEqual(category.parent, self.parent_category)
        self.assertTrue(category.is_active)
        self.assertIsNotNone(category.created_at)
    
    def test_brand_category_str_representation(self):
        """Test string representation of brand category."""
        self.assertEqual(str(self.parent_category), "Technology")
    
    def test_brand_category_hierarchy(self):
        """Test brand category hierarchy methods."""
        child_category = BrandCategory.objects.create(
            name="Mobile Apps",
            parent=self.parent_category
        )
        
        # Test get_children
        children = self.parent_category.get_children()
        self.assertIn(child_category, children)
        
        # Test get_ancestors
        ancestors = child_category.get_ancestors()
        self.assertIn(self.parent_category, ancestors)
        
        # Test get_descendants
        descendants = self.parent_category.get_descendants()
        self.assertIn(child_category, descendants)
    
    def test_brand_category_ordering(self):
        """Test brand category ordering."""
        category1 = BrandCategory.objects.create(name="First", order=1)
        category2 = BrandCategory.objects.create(name="Second", order=2)
        
        categories = list(BrandCategory.objects.all())
        self.assertEqual(categories[0], self.parent_category)  # order=1
        self.assertEqual(categories[1], category1)  # order=1
        self.assertEqual(categories[2], category2)  # order=2
    
    def test_brand_category_unique_constraint(self):
        """Test unique constraint on name and parent."""
        with self.assertRaises(IntegrityError):
            BrandCategory.objects.create(
                name="Technology",  # Same name as parent_category
                parent=None  # Same parent (None)
            )


class AgencyModelTest(TestCase):
    """
    Test cases for Agency model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.agency_data = {
            'name': 'Test Agency',
            'code': 'TEST001',
            'email': 'test@agency.com',
            'phone': '+1234567890',
            'address': '123 Test St',
            'city': 'Test City',
            'country': 'Test Country',
            'commission_rate': Decimal('15.00')
        }
    
    def test_create_agency(self):
        """Test creating an agency."""
        agency = Agency.objects.create(**self.agency_data)
        
        self.assertEqual(agency.name, 'Test Agency')
        self.assertEqual(agency.code, 'TEST001')
        self.assertEqual(agency.commission_rate, Decimal('15.00'))
        self.assertTrue(agency.is_active)
    
    def test_agency_str_representation(self):
        """Test string representation of agency."""
        agency = Agency.objects.create(**self.agency_data)
        self.assertEqual(str(agency), 'Test Agency')
    
    def test_agency_unique_code(self):
        """Test unique constraint on agency code."""
        Agency.objects.create(**self.agency_data)
        
        with self.assertRaises(IntegrityError):
            Agency.objects.create(
                name='Another Agency',
                code='TEST001',  # Duplicate code
                email='another@agency.com'
            )
    
    def test_agency_commission_rate_validation(self):
        """Test commission rate validation."""
        # Valid commission rate
        agency = Agency(**self.agency_data)
        agency.full_clean()  # Should not raise
        
        # Invalid commission rate (negative)
        agency.commission_rate = Decimal('-5.00')
        with self.assertRaises(ValidationError):
            agency.full_clean()
        
        # Invalid commission rate (over 100)
        agency.commission_rate = Decimal('150.00')
        with self.assertRaises(ValidationError):
            agency.full_clean()
    
    def test_agency_advertiser_count(self):
        """Test advertiser count property."""
        agency = Agency.objects.create(**self.agency_data)
        
        # Initially no advertisers
        self.assertEqual(agency.advertiser_count, 0)
        
        # Create advertisers
        Advertiser.objects.create(
            name='Test Advertiser 1',
            advertiser_type='agency_managed',
            agency=agency
        )
        Advertiser.objects.create(
            name='Test Advertiser 2',
            advertiser_type='agency_managed',
            agency=agency
        )
        
        # Refresh from database
        agency.refresh_from_db()
        self.assertEqual(agency.advertiser_count, 2)


class AdvertiserModelTest(TestCase):
    """
    Test cases for Advertiser model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.agency = Agency.objects.create(
            name='Test Agency',
            code='AGY001',
            email='agency@test.com'
        )
        
        self.advertiser_data = {
            'name': 'Test Advertiser',
            'code': 'ADV001',
            'email': 'advertiser@test.com',
            'advertiser_type': 'individual',
            'industry': 'Technology',
            'credit_limit': Decimal('50000.00')
        }
    
    def test_create_individual_advertiser(self):
        """Test creating an individual advertiser."""
        advertiser = Advertiser.objects.create(**self.advertiser_data)
        
        self.assertEqual(advertiser.name, 'Test Advertiser')
        self.assertEqual(advertiser.advertiser_type, 'individual')
        self.assertIsNone(advertiser.agency)
        self.assertTrue(advertiser.is_active)
    
    def test_create_agency_managed_advertiser(self):
        """Test creating an agency-managed advertiser."""
        advertiser_data = self.advertiser_data.copy()
        advertiser_data.update({
            'advertiser_type': 'agency_managed',
            'agency': self.agency
        })
        
        advertiser = Advertiser.objects.create(**advertiser_data)
        
        self.assertEqual(advertiser.advertiser_type, 'agency_managed')
        self.assertEqual(advertiser.agency, self.agency)
    
    def test_advertiser_str_representation(self):
        """Test string representation of advertiser."""
        advertiser = Advertiser.objects.create(**self.advertiser_data)
        self.assertEqual(str(advertiser), 'Test Advertiser')
    
    def test_advertiser_agency_constraint(self):
        """Test agency constraint validation."""
        # Agency-managed advertiser without agency should fail
        with self.assertRaises(ValidationError):
            advertiser = Advertiser(
                name='Test Advertiser',
                advertiser_type='agency_managed',
                agency=None  # Missing agency
            )
            advertiser.full_clean()
    
    def test_advertiser_brand_count(self):
        """Test brand count property."""
        advertiser = Advertiser.objects.create(**self.advertiser_data)
        
        # Initially no brands
        self.assertEqual(advertiser.brand_count, 0)
        
        # Create brands
        Brand.objects.create(name='Brand 1', advertiser=advertiser)
        Brand.objects.create(name='Brand 2', advertiser=advertiser)
        
        # Refresh from database
        advertiser.refresh_from_db()
        self.assertEqual(advertiser.brand_count, 2)


class BrandModelTest(TestCase):
    """
    Test cases for Brand model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            advertiser_type='individual'
        )
        
        self.category = BrandCategory.objects.create(
            name='Technology',
            description='Tech brands'
        )
        
        self.brand_data = {
            'name': 'Test Brand',
            'code': 'BRD001',
            'advertiser': self.advertiser,
            'category': self.category,
            'description': 'A test brand',
            'website': 'https://testbrand.com',
            'primary_color': '#FF0000',
            'secondary_color': '#00FF00'
        }
    
    def test_create_brand(self):
        """Test creating a brand."""
        brand = Brand.objects.create(**self.brand_data)
        
        self.assertEqual(brand.name, 'Test Brand')
        self.assertEqual(brand.advertiser, self.advertiser)
        self.assertEqual(brand.category, self.category)
        self.assertTrue(brand.is_active)
    
    def test_brand_str_representation(self):
        """Test string representation of brand."""
        brand = Brand.objects.create(**self.brand_data)
        self.assertEqual(str(brand), 'Test Brand')
    
    def test_brand_unique_constraint(self):
        """Test unique constraint on name and advertiser."""
        Brand.objects.create(**self.brand_data)
        
        with self.assertRaises(IntegrityError):
            Brand.objects.create(
                name='Test Brand',  # Same name
                advertiser=self.advertiser,  # Same advertiser
                code='BRD002'
            )
    
    def test_brand_color_validation(self):
        """Test color field validation."""
        # Valid colors
        brand = Brand(**self.brand_data)
        brand.full_clean()  # Should not raise
        
        # Test different color formats
        brand.primary_color = '#FFF'  # Short format
        brand.full_clean()  # Should not raise
        
        brand.primary_color = '#FFFFFF'  # Long format
        brand.full_clean()  # Should not raise


class AdvertiserContactModelTest(TestCase):
    """
    Test cases for AdvertiserContact model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            advertiser_type='individual'
        )
        
        self.contact_data = {
            'advertiser': self.advertiser,
            'first_name': 'John',
            'last_name': 'Doe',
            'email': 'john.doe@test.com',
            'phone': '+1234567890',
            'title': 'Marketing Manager',
            'contact_type': 'primary'
        }
    
    def test_create_contact(self):
        """Test creating an advertiser contact."""
        contact = AdvertiserContact.objects.create(**self.contact_data)
        
        self.assertEqual(contact.first_name, 'John')
        self.assertEqual(contact.last_name, 'Doe')
        self.assertEqual(contact.advertiser, self.advertiser)
        self.assertTrue(contact.is_active)
    
    def test_contact_str_representation(self):
        """Test string representation of contact."""
        contact = AdvertiserContact.objects.create(**self.contact_data)
        self.assertEqual(str(contact), 'John Doe (Test Advertiser)')
    
    def test_contact_get_full_name(self):
        """Test get_full_name method."""
        contact = AdvertiserContact.objects.create(**self.contact_data)
        self.assertEqual(contact.get_full_name(), 'John Doe')
    
    def test_contact_unique_constraint(self):
        """Test unique constraint on email and advertiser."""
        AdvertiserContact.objects.create(**self.contact_data)
        
        with self.assertRaises(IntegrityError):
            AdvertiserContact.objects.create(
                advertiser=self.advertiser,
                first_name='Jane',
                last_name='Smith',
                email='john.doe@test.com',  # Same email
                contact_type='secondary'
            )


class AgencyUserModelTest(TestCase):
    """
    Test cases for AgencyUser model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@user.com',
            password='testpass123'
        )
        
        self.agency = Agency.objects.create(
            name='Test Agency',
            code='AGY001',
            email='agency@test.com'
        )
        
        self.agency_user_data = {
            'user': self.user,
            'agency': self.agency,
            'role': 'account_manager',
            'can_create_campaigns': True,
            'can_manage_advertisers': True,
            'can_view_reports': True,
            'can_manage_billing': False
        }
    
    def test_create_agency_user(self):
        """Test creating an agency user."""
        agency_user = AgencyUser.objects.create(**self.agency_user_data)
        
        self.assertEqual(agency_user.user, self.user)
        self.assertEqual(agency_user.agency, self.agency)
        self.assertEqual(agency_user.role, 'account_manager')
        self.assertTrue(agency_user.is_active)
    
    def test_agency_user_str_representation(self):
        """Test string representation of agency user."""
        agency_user = AgencyUser.objects.create(**self.agency_user_data)
        expected = f'{self.user.get_full_name() or self.user.username} - Test Agency'
        self.assertEqual(str(agency_user), expected)
    
    def test_agency_user_unique_constraint(self):
        """Test unique constraint on user and agency."""
        AgencyUser.objects.create(**self.agency_user_data)
        
        with self.assertRaises(IntegrityError):
            AgencyUser.objects.create(
                user=self.user,  # Same user
                agency=self.agency,  # Same agency
                role='admin'
            )


class AdvertiserBillingModelTest(TestCase):
    """
    Test cases for AdvertiserBilling model.
    """
    
    def setUp(self):
        """Set up test data."""
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            advertiser_type='individual'
        )
        
        self.billing_data = {
            'advertiser': self.advertiser,
            'billing_name': 'Test Advertiser Inc.',
            'tax_id': '123456789',
            'credit_limit': Decimal('100000.00'),
            'current_balance': Decimal('25000.00'),
            'payment_method': 'credit_card',
            'billing_cycle': 'monthly'
        }
    
    def test_create_billing(self):
        """Test creating a billing record."""
        billing = AdvertiserBilling.objects.create(**self.billing_data)
        
        self.assertEqual(billing.advertiser, self.advertiser)
        self.assertEqual(billing.credit_limit, Decimal('100000.00'))
        self.assertEqual(billing.current_balance, Decimal('25000.00'))
        self.assertTrue(billing.is_active)
    
    def test_billing_str_representation(self):
        """Test string representation of billing."""
        billing = AdvertiserBilling.objects.create(**self.billing_data)
        self.assertEqual(str(billing), 'Test Advertiser Inc. - Test Advertiser')
    
    def test_billing_available_credit(self):
        """Test available credit calculation."""
        billing = AdvertiserBilling.objects.create(**self.billing_data)
        expected_available = Decimal('100000.00') - Decimal('25000.00')
        self.assertEqual(billing.available_credit, expected_available)
    
    def test_billing_is_over_limit(self):
        """Test over limit detection."""
        # Under limit
        billing = AdvertiserBilling.objects.create(**self.billing_data)
        self.assertFalse(billing.is_over_limit)
        
        # Over limit
        billing.current_balance = Decimal('150000.00')
        billing.save()
        self.assertTrue(billing.is_over_limit)
    
    def test_billing_unique_constraint(self):
        """Test unique constraint on advertiser."""
        AdvertiserBilling.objects.create(**self.billing_data)
        
        with self.assertRaises(IntegrityError):
            AdvertiserBilling.objects.create(
                advertiser=self.advertiser,  # Same advertiser
                billing_name='Another Name',
                credit_limit=Decimal('50000.00')
            )


class AdvertiserAPITest(APITestCase):
    """
    Test cases for Advertiser API endpoints.
    """
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@user.com',
            password='testpass123'
        )
        
        self.agency = Agency.objects.create(
            name='Test Agency',
            code='AGY001',
            email='agency@test.com'
        )
        
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='ADV001',
            advertiser_type='individual',
            email='advertiser@test.com'
        )
        
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
    
    def test_get_advertiser_list(self):
        """Test getting advertiser list."""
        url = reverse('advertisers:advertiser-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], 'Test Advertiser')
    
    def test_get_advertiser_detail(self):
        """Test getting advertiser detail."""
        url = reverse('advertisers:advertiser-detail', kwargs={'pk': self.advertiser.pk})
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], 'Test Advertiser')
        self.assertEqual(response.data['code'], 'ADV001')
    
    def test_create_advertiser(self):
        """Test creating an advertiser."""
        url = reverse('advertisers:advertiser-list')
        data = {
            'name': 'New Advertiser',
            'code': 'NEW001',
            'advertiser_type': 'individual',
            'email': 'new@advertiser.com',
            'industry': 'Technology'
        }
        
        response = self.client.post(url, data, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['name'], 'New Advertiser')
        self.assertTrue(Advertiser.objects.filter(name='New Advertiser').exists())
    
    def test_update_advertiser(self):
        """Test updating an advertiser."""
        url = reverse('advertisers:advertiser-detail', kwargs={'pk': self.advertiser.pk})
        data = {
            'name': 'Updated Advertiser',
            'code': 'ADV001',
            'advertiser_type': 'individual',
            'email': 'updated@advertiser.com'
        }
        
        response = self.client.put(url, data, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], 'Updated Advertiser')
        
        self.advertiser.refresh_from_db()
        self.assertEqual(self.advertiser.name, 'Updated Advertiser')
    
    def test_delete_advertiser(self):
        """Test deleting an advertiser."""
        url = reverse('advertisers:advertiser-detail', kwargs={'pk': self.advertiser.pk})
        response = self.client.delete(url)
        
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
        self.assertFalse(Advertiser.objects.filter(pk=self.advertiser.pk).exists())
    
    def test_advertiser_statistics(self):
        """Test advertiser statistics endpoint."""
        # Create some brands for statistics
        Brand.objects.create(name='Brand 1', advertiser=self.advertiser)
        Brand.objects.create(name='Brand 2', advertiser=self.advertiser)
        
        url = reverse('advertisers:advertiser-statistics', kwargs={'pk': self.advertiser.pk})
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('brand_count', response.data)
        self.assertEqual(response.data['brand_count'], 2)
    
    def test_advertiser_filtering(self):
        """Test advertiser filtering."""
        # Create additional advertisers
        Advertiser.objects.create(
            name='Tech Advertiser',
            code='TECH001',
            advertiser_type='individual',
            industry='Technology'
        )
        
        url = reverse('advertisers:advertiser-list')
        
        # Filter by industry
        response = self.client.get(url, {'industry': 'Technology'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        
        # Search by name
        response = self.client.get(url, {'search': 'Tech'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
    
    def test_unauthenticated_access(self):
        """Test unauthenticated access is denied."""
        self.client.force_authenticate(user=None)
        
        url = reverse('advertisers:advertiser-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)


class BrandAPITest(APITestCase):
    """
    Test cases for Brand API endpoints.
    """
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@user.com',
            password='testpass123'
        )
        
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='ADV001',
            advertiser_type='individual'
        )
        
        self.category = BrandCategory.objects.create(
            name='Technology',
            description='Tech brands'
        )
        
        self.brand = Brand.objects.create(
            name='Test Brand',
            code='BRD001',
            advertiser=self.advertiser,
            category=self.category
        )
        
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
    
    def test_get_brand_list(self):
        """Test getting brand list."""
        url = reverse('advertisers:brand-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], 'Test Brand')
    
    def test_create_brand(self):
        """Test creating a brand."""
        url = reverse('advertisers:brand-list')
        data = {
            'name': 'New Brand',
            'code': 'NEW001',
            'advertiser': self.advertiser.pk,
            'category': self.category.pk,
            'description': 'A new brand',
            'primary_color': '#FF0000'
        }
        
        response = self.client.post(url, data, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['name'], 'New Brand')
        self.assertTrue(Brand.objects.filter(name='New Brand').exists())
    
    def test_brand_logo_upload(self):
        """Test brand logo upload."""
        url = reverse('advertisers:brand-detail', kwargs={'pk': self.brand.pk})
        
        # Create a simple test image
        logo_content = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\nIDATx\x9cc```\x00\x00\x00\x02\x00\x01H\xaf\xa4q\x00\x00\x00\x00IEND\xaeB`\x82'
        logo = SimpleUploadedFile(
            name='test_logo.png',
            content=logo_content,
            content_type='image/png'
        )
        
        data = {
            'name': 'Test Brand',
            'code': 'BRD001',
            'advertiser': self.advertiser.pk,
            'logo': logo
        }
        
        response = self.client.patch(url, data, format='multipart')
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.brand.refresh_from_db()
        self.assertTrue(self.brand.logo)


class SerializerTest(TestCase):
    """
    Test cases for serializers.
    """
    
    def setUp(self):
        """Set up test data."""
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='ADV001',
            advertiser_type='individual'
        )
        
        self.category = BrandCategory.objects.create(
            name='Technology',
            description='Tech brands'
        )
    
    def test_brand_category_serializer(self):
        """Test BrandCategorySerializer."""
        serializer = BrandCategorySerializer(instance=self.category)
        data = serializer.data
        
        self.assertEqual(data['name'], 'Technology')
        self.assertEqual(data['description'], 'Tech brands')
        self.assertIn('brand_count', data)
    
    def test_advertiser_serializer(self):
        """Test AdvertiserSerializer."""
        serializer = AdvertiserSerializer(instance=self.advertiser)
        data = serializer.data
        
        self.assertEqual(data['name'], 'Test Advertiser')
        self.assertEqual(data['code'], 'ADV001')
        self.assertEqual(data['advertiser_type'], 'individual')
        self.assertIn('brand_count', data)
    
    def test_brand_serializer_validation(self):
        """Test Brand serializer validation."""
        data = {
            'name': 'Test Brand',
            'code': 'BRD001',
            'advertiser': self.advertiser.pk,
            'category': self.category.pk,
            'primary_color': 'invalid_color'  # Invalid color
        }
        
        serializer = BrandSerializer(data=data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('primary_color', serializer.errors)
    
    def test_advertiser_serializer_validation(self):
        """Test Advertiser serializer validation."""
        data = {
            'name': 'Test Advertiser',
            'code': 'ADV001',
            'advertiser_type': 'agency_managed',
            'agency': None  # Missing agency for agency-managed
        }
        
        serializer = AdvertiserSerializer(data=data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('agency', serializer.errors)


class SignalTest(TransactionTestCase):
    """
    Test cases for model signals.
    """
    
    def test_advertiser_billing_creation_signal(self):
        """Test that billing record is created when advertiser is created."""
        advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='ADV001',
            advertiser_type='individual',
            credit_limit=Decimal('50000.00')
        )
        
        # Check that billing record was created
        self.assertTrue(hasattr(advertiser, 'billing'))
        self.assertEqual(advertiser.billing.credit_limit, Decimal('50000.00'))
    
    @patch('apps.advertisers.signals.send_mail')
    def test_agency_welcome_email_signal(self, mock_send_mail):
        """Test that welcome email is sent when agency is created."""
        with override_settings(
            SEND_AGENCY_WELCOME_EMAIL=True,
            DEFAULT_FROM_EMAIL='test@example.com'
        ):
            Agency.objects.create(
                name='Test Agency',
                code='AGY001',
                email='agency@test.com'
            )
            
            # Check that send_mail was called
            mock_send_mail.assert_called_once()
    
    def test_brand_category_hierarchy_validation_signal(self):
        """Test brand category hierarchy validation in signals."""
        parent = BrandCategory.objects.create(name='Parent')
        child = BrandCategory.objects.create(name='Child', parent=parent)
        grandchild = BrandCategory.objects.create(name='Grandchild', parent=child)
        
        # Trying to create a great-grandchild should fail
        with self.assertRaises(ValueError):
            BrandCategory.objects.create(name='Great-Grandchild', parent=grandchild)


class IntegrationTest(APITestCase):
    """
    Integration tests for the entire advertisers app.
    """
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@user.com',
            password='testpass123'
        )
        
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
    
    def test_complete_advertiser_workflow(self):
        """Test complete advertiser creation workflow."""
        # 1. Create agency
        agency_data = {
            'name': 'Test Agency',
            'code': 'AGY001',
            'email': 'agency@test.com',
            'commission_rate': '15.00'
        }
        
        agency_url = reverse('advertisers:agency-list')
        agency_response = self.client.post(agency_url, agency_data, format='json')
        self.assertEqual(agency_response.status_code, status.HTTP_201_CREATED)
        agency_id = agency_response.data['id']
        
        # 2. Create advertiser
        advertiser_data = {
            'name': 'Test Advertiser',
            'code': 'ADV001',
            'advertiser_type': 'agency_managed',
            'agency': agency_id,
            'email': 'advertiser@test.com',
            'industry': 'Technology'
        }
        
        advertiser_url = reverse('advertisers:advertiser-list')
        advertiser_response = self.client.post(advertiser_url, advertiser_data, format='json')
        self.assertEqual(advertiser_response.status_code, status.HTTP_201_CREATED)
        advertiser_id = advertiser_response.data['id']
        
        # 3. Create brand category
        category_data = {
            'name': 'Technology',
            'description': 'Technology brands'
        }
        
        category_url = reverse('advertisers:brandcategory-list')
        category_response = self.client.post(category_url, category_data, format='json')
        self.assertEqual(category_response.status_code, status.HTTP_201_CREATED)
        category_id = category_response.data['id']
        
        # 4. Create brand
        brand_data = {
            'name': 'Test Brand',
            'code': 'BRD001',
            'advertiser': advertiser_id,
            'category': category_id,
            'description': 'A test brand',
            'primary_color': '#FF0000'
        }
        
        brand_url = reverse('advertisers:brand-list')
        brand_response = self.client.post(brand_url, brand_data, format='json')
        self.assertEqual(brand_response.status_code, status.HTTP_201_CREATED)
        
        # 5. Create contact
        contact_data = {
            'advertiser': advertiser_id,
            'first_name': 'John',
            'last_name': 'Doe',
            'email': 'john.doe@test.com',
            'title': 'Marketing Manager',
            'contact_type': 'primary'
        }
        
        contact_url = reverse('advertisers:advertisercontact-list')
        contact_response = self.client.post(contact_url, contact_data, format='json')
        self.assertEqual(contact_response.status_code, status.HTTP_201_CREATED)
        
        # 6. Verify relationships
        advertiser_detail_url = reverse('advertisers:advertiser-detail', kwargs={'pk': advertiser_id})
        advertiser_detail_response = self.client.get(advertiser_detail_url)
        
        self.assertEqual(advertiser_detail_response.status_code, status.HTTP_200_OK)
        self.assertEqual(advertiser_detail_response.data['agency']['id'], agency_id)


class SecurityTest(APITestCase):
    """Security-focused test cases."""
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@user.com',
            password='testpass123'
        )
        
        self.admin_user = User.objects.create_superuser(
            username='admin',
            email='admin@test.com',
            password='adminpass123'
        )
        
        self.agency = Agency.objects.create(
            name='Test Agency',
            code='AGY001',
            email='agency@test.com'
        )
        
        self.advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='ADV001',
            advertiser_type='individual',
            email='advertiser@test.com'
        )
        
        self.client = APIClient()
    
    def test_sql_injection_protection(self):
        """Test protection against SQL injection attacks."""
        self.client.force_authenticate(user=self.user)
        
        # Attempt SQL injection in search parameter
        malicious_search = "'; DROP TABLE advertisers_advertiser; --"
        url = reverse('advertisers:advertiser-list')
        response = self.client.get(url, {'search': malicious_search})
        
        # Should return 200 and not cause database errors
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Verify table still exists by checking advertiser count
        self.assertTrue(Advertiser.objects.filter(pk=self.advertiser.pk).exists())
    
    def test_xss_protection_in_responses(self):
        """Test XSS protection in API responses."""
        self.client.force_authenticate(user=self.user)
        
        # Create advertiser with potentially malicious content
        xss_payload = "<script>alert('XSS')</script>"
        advertiser = Advertiser.objects.create(
            name=f"Test {xss_payload}",
            code='XSS001',
            advertiser_type='individual',
            email='xss@test.com'
        )
        
        url = reverse('advertisers:advertiser-detail', kwargs={'pk': advertiser.pk})
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # Verify the script tag is properly escaped in JSON response
        self.assertNotIn('<script>', str(response.content))
    
    def test_unauthorized_access_prevention(self):
        """Test that unauthorized users cannot access protected endpoints."""
        # Test without authentication
        url = reverse('advertisers:advertiser-list')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
        
        # Test with invalid token
        self.client.credentials(HTTP_AUTHORIZATION='Bearer invalid_token')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
    
    def test_sensitive_data_exposure_prevention(self):
        """Test that sensitive data is not exposed in API responses."""
        self.client.force_authenticate(user=self.user)
        
        # Create billing with sensitive information
        billing = AdvertiserBilling.objects.create(
            advertiser=self.advertiser,
            billing_name='Test Billing',
            tax_id='123-45-6789',  # Sensitive
            credit_limit=Decimal('100000.00'),
            payment_method='credit_card'
        )
        
        url = reverse('advertisers:advertiser-detail', kwargs={'pk': self.advertiser.pk})
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # Verify sensitive fields are not exposed or are masked
        response_str = str(response.content)
        self.assertNotIn('123-45-6789', response_str)
    
    def test_mass_assignment_protection(self):
        """Test protection against mass assignment vulnerabilities."""
        self.client.force_authenticate(user=self.user)
        
        # Attempt to set read-only fields
        url = reverse('advertisers:advertiser-list')
        data = {
            'name': 'Test Advertiser',
            'code': 'ADV002',
            'advertiser_type': 'individual',
            'email': 'test2@advertiser.com',
            'id': 99999,  # Should be ignored
            'created_at': '2020-01-01T00:00:00Z',  # Should be ignored
            'updated_at': '2020-01-01T00:00:00Z'   # Should be ignored
        }
        
        response = self.client.post(url, data, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        # Verify read-only fields were not set to provided values
        self.assertNotEqual(response.data['id'], 99999)
        self.assertNotEqual(response.data['created_at'], '2020-01-01T00:00:00Z')
    
    def test_rate_limiting_headers(self):
        """Test that rate limiting headers are present."""
        self.client.force_authenticate(user=self.user)
        
        url = reverse('advertisers:advertiser-list')
        response = self.client.get(url)
        
        # Check for rate limiting headers (if implemented)
        # Note: This depends on your rate limiting implementation
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # Uncomment if rate limiting is implemented:
        # self.assertIn('X-RateLimit-Limit', response)
        # self.assertIn('X-RateLimit-Remaining', response)


class PerformanceTest(TestCase):
    """Performance-focused test cases."""
    
    def setUp(self):
        """Set up test data for performance tests."""
        self.agency = Agency.objects.create(
            name='Performance Agency',
            code='PERF001',
            email='perf@agency.com'
        )
        
        # Create multiple advertisers for testing
        self.advertisers = []
        for i in range(50):
            advertiser = Advertiser.objects.create(
                name=f'Advertiser {i}',
                code=f'ADV{i:03d}',
                advertiser_type='agency_managed',
                agency=self.agency,
                email=f'advertiser{i}@test.com'
            )
            self.advertisers.append(advertiser)
            
            # Create brands for each advertiser
            for j in range(3):
                Brand.objects.create(
                    name=f'Brand {i}-{j}',
                    code=f'BRD{i:03d}{j}',
                    advertiser=advertiser
                )
    
    def test_query_optimization_with_select_related(self):
        """Test query optimization using select_related."""
        with self.assertNumQueries(1):
            # Should use select_related to avoid N+1 queries
            advertisers = list(
                Advertiser.objects.select_related('agency')
                .filter(agency=self.agency)[:10]
            )
            
            # Access related agency without additional queries
            for advertiser in advertisers:
                _ = advertiser.agency.name
    
    def test_query_optimization_with_prefetch_related(self):
        """Test query optimization using prefetch_related."""
        with self.assertNumQueries(2):  # 1 for advertisers, 1 for brands
            # Should use prefetch_related for reverse foreign key
            advertisers = list(
                Advertiser.objects.prefetch_related('brands')
                .filter(agency=self.agency)[:10]
            )
            
            # Access related brands without additional queries
            for advertiser in advertisers:
                _ = list(advertiser.brands.all())
    
    def test_bulk_operations_performance(self):
        """Test bulk operations for better performance."""
        # Test bulk_create performance
        new_advertisers_data = [
            Advertiser(
                name=f'Bulk Advertiser {i}',
                code=f'BULK{i:03d}',
                advertiser_type='individual',
                email=f'bulk{i}@test.com'
            )
            for i in range(100)
        ]
        
        with self.assertNumQueries(1):
            Advertiser.objects.bulk_create(new_advertisers_data)
        
        # Verify all were created
        bulk_count = Advertiser.objects.filter(name__startswith='Bulk').count()
        self.assertEqual(bulk_count, 100)
    
    def test_pagination_performance(self):
        """Test pagination performance with large datasets."""
        # Test that pagination doesn't load all records
        with self.assertNumQueries(2):  # 1 for count, 1 for page data
            from django.core.paginator import Paginator
            
            paginator = Paginator(Advertiser.objects.all(), 10)
            page = paginator.get_page(1)
            
            # Access page data
            _ = list(page.object_list)
            _ = page.has_next()
            _ = paginator.count
    
    def test_database_index_usage(self):
        """Test that database indexes are being used effectively."""
        from django.db import connection
        
        # Test index usage for common queries
        with connection.cursor() as cursor:
            # Query by code (should use index)
            cursor.execute(
                "SELECT * FROM advertisers_advertiser WHERE code = %s",
                ['ADV001']
            )
            
            # Query by email (should use index)
            cursor.execute(
                "SELECT * FROM advertisers_advertiser WHERE email = %s",
                ['advertiser1@test.com']
            )
            
            # Query by agency (should use foreign key index)
            cursor.execute(
                "SELECT * FROM advertisers_advertiser WHERE agency_id = %s",
                [self.agency.id]
            )
        
        # Note: Actual index usage verification would require
        # database-specific EXPLAIN queries


class BusinessLogicTest(TestCase):
    """Test business logic and validation rules."""
    
    def setUp(self):
        """Set up test data."""
        self.agency = Agency.objects.create(
            name='Business Agency',
            code='BIZ001',
            email='biz@agency.com',
            commission_rate=Decimal('15.00')
        )
        
        self.advertiser = Advertiser.objects.create(
            name='Business Advertiser',
            code='BIZ-ADV001',
            advertiser_type='agency_managed',
            agency=self.agency,
            email='biz@advertiser.com',
            credit_limit=Decimal('100000.00')
        )
    
    def test_commission_calculation_accuracy(self):
        """Test commission calculation accuracy."""
        test_amounts = [
            Decimal('1000.00'),
            Decimal('1234.56'),
            Decimal('0.01'),
            Decimal('999999.99')
        ]
        
        for amount in test_amounts:
            expected_commission = amount * (self.agency.commission_rate / 100)
            calculated_commission = self.agency.get_commission_for_amount(amount)
            
            self.assertEqual(
                calculated_commission,
                expected_commission,
                f"Commission calculation failed for amount {amount}"
            )
    
    def test_credit_limit_enforcement(self):
        """Test credit limit enforcement logic."""
        billing = AdvertiserBilling.objects.create(
            advertiser=self.advertiser,
            billing_name='Test Billing',
            credit_limit=Decimal('50000.00'),
            current_balance=Decimal('0.00'),
            payment_method='credit_card'
        )
        
        # Test within limit
        billing.current_balance = Decimal('25000.00')
        billing.save()
        self.assertFalse(billing.is_over_limit)
        self.assertEqual(billing.available_credit, Decimal('25000.00'))
        
        # Test at limit
        billing.current_balance = Decimal('50000.00')
        billing.save()
        self.assertFalse(billing.is_over_limit)
        self.assertEqual(billing.available_credit, Decimal('0.00'))
        
        # Test over limit
        billing.current_balance = Decimal('60000.00')
        billing.save()
        self.assertTrue(billing.is_over_limit)
        self.assertEqual(billing.available_credit, Decimal('-10000.00'))
    
    def test_agency_advertiser_management_rules(self):
        """Test agency advertiser management business rules."""
        # Agency can manage its own advertisers
        self.assertTrue(self.agency.can_manage_advertiser(self.advertiser))
        
        # Agency cannot manage independent advertisers
        independent_advertiser = Advertiser.objects.create(
            name='Independent Advertiser',
            code='INDEP001',
            advertiser_type='individual',
            email='independent@test.com'
        )
        self.assertFalse(self.agency.can_manage_advertiser(independent_advertiser))
        
        # Agency cannot manage other agency's advertisers
        other_agency = Agency.objects.create(
            name='Other Agency',
            code='OTHER001',
            email='other@agency.com'
        )
        other_advertiser = Advertiser.objects.create(
            name='Other Advertiser',
            code='OTHER-ADV001',
            advertiser_type='agency_managed',
            agency=other_agency,
            email='other@advertiser.com'
        )
        self.assertFalse(self.agency.can_manage_advertiser(other_advertiser))
    
    def test_brand_category_hierarchy_limits(self):
        """Test brand category hierarchy depth limits."""
        # Create hierarchy: Root -> Level1 -> Level2
        root = BrandCategory.objects.create(name='Root')
        level1 = BrandCategory.objects.create(name='Level1', parent=root)
        level2 = BrandCategory.objects.create(name='Level2', parent=level1)
        
        # Should be able to create up to 3 levels
        try:
            level3 = BrandCategory.objects.create(name='Level3', parent=level2)
            level3.clean()  # Should not raise
        except ValidationError:
            self.fail("Should allow 3 levels of hierarchy")
        
        # Should prevent more than 3 levels (if implemented)
        # Note: This depends on your business rules
        # Uncomment if you implement hierarchy depth limits:
        # with self.assertRaises(ValidationError):
        #     level4 = BrandCategory.objects.create(name='Level4', parent=level3)
        #     level4.clean()
    
    def test_advertiser_type_consistency(self):
        """Test advertiser type consistency rules."""
        # Agency-managed advertiser must have agency
        with self.assertRaises(ValidationError):
            advertiser = Advertiser(
                name='Invalid Advertiser',
                code='INVALID001',
                advertiser_type='agency_managed',
                agency=None,  # Missing agency
                email='invalid@test.com'
            )
            advertiser.clean()
        
        # Individual advertiser should not have agency
        with self.assertRaises(ValidationError):
            advertiser = Advertiser(
                name='Invalid Individual',
                code='INVALID002',
                advertiser_type='individual',
                agency=self.agency,  # Should not have agency
                email='invalid2@test.com'
            )
            advertiser.clean()
    
    def test_contact_type_uniqueness_per_advertiser(self):
        """Test that each advertiser can have only one primary contact."""
        # Create first primary contact
        AdvertiserContact.objects.create(
            advertiser=self.advertiser,
            contact_type='primary',
            first_name='John',
            last_name='Doe',
            email='john@test.com'
        )
        
        # Attempt to create second primary contact should fail
        with self.assertRaises(IntegrityError):
            AdvertiserContact.objects.create(
                advertiser=self.advertiser,
                contact_type='primary',
                first_name='Jane',
                last_name='Smith',
                email='jane@test.com'
            )
    
    def test_brand_code_uniqueness_global(self):
        """Test that brand codes are globally unique."""
        Brand.objects.create(
            name='Brand 1',
            code='UNIQUE001',
            advertiser=self.advertiser
        )
        
        # Create another advertiser
        other_advertiser = Advertiser.objects.create(
            name='Other Advertiser',
            code='OTHER001',
            advertiser_type='individual',
            email='other@test.com'
        )
        
        # Attempt to create brand with same code should fail
        with self.assertRaises(IntegrityError):
            Brand.objects.create(
                name='Brand 2',
                code='UNIQUE001',  # Duplicate code
                advertiser=other_advertiser
            )


class EdgeCaseTest(TestCase):
    """Test edge cases and boundary conditions."""
    
    def test_empty_string_handling(self):
        """Test handling of empty strings in optional fields."""
        advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='EDGE001',
            advertiser_type='individual',
            email='edge@test.com',
            phone='',  # Empty string
            website='',  # Empty string
            description=''  # Empty string
        )
        
        self.assertEqual(advertiser.phone, '')
        self.assertEqual(advertiser.website, '')
        self.assertEqual(advertiser.description, '')
    
    def test_null_vs_empty_string_handling(self):
        """Test consistent handling of null vs empty string."""
        advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='NULL001',
            advertiser_type='individual',
            email='null@test.com',
            phone=None,  # Null
            website=None,  # Null
            description=None  # Null
        )
        
        self.assertIsNone(advertiser.phone)
        self.assertIsNone(advertiser.website)
        self.assertIsNone(advertiser.description)
    
    def test_maximum_field_length_handling(self):
        """Test handling of maximum field lengths."""
        # Test maximum name length (200 characters)
        long_name = 'A' * 200
        advertiser = Advertiser.objects.create(
            name=long_name,
            code='LONG001',
            advertiser_type='individual',
            email='long@test.com'
        )
        self.assertEqual(len(advertiser.name), 200)
        
        # Test exceeding maximum length should be handled by validation
        with self.assertRaises(ValidationError):
            advertiser = Advertiser(
                name='A' * 201,  # Exceeds maximum
                code='TOOLONG001',
                advertiser_type='individual',
                email='toolong@test.com'
            )
            advertiser.full_clean()
    
    def test_decimal_precision_handling(self):
        """Test decimal field precision and rounding."""
        agency = Agency.objects.create(
            name='Precision Agency',
            code='PREC001',
            email='precision@agency.com',
            commission_rate=Decimal('15.123456789')  # High precision
        )
        
        # Should be rounded to 2 decimal places
        self.assertEqual(agency.commission_rate, Decimal('15.12'))
    
    def test_unicode_character_handling(self):
        """Test handling of unicode characters in text fields."""
        unicode_name = "Tëst Àdvërtísër 测试 🚀"
        advertiser = Advertiser.objects.create(
            name=unicode_name,
            code='UNI001',
            advertiser_type='individual',
            email='unicode@test.com'
        )
        
        self.assertEqual(advertiser.name, unicode_name)
        self.assertEqual(str(advertiser), unicode_name)
    
    def test_timezone_handling(self):
        """Test timezone handling in datetime fields."""
        from django.utils import timezone
        
        # Create advertiser and check created_at timezone
        advertiser = Advertiser.objects.create(
            name='Timezone Test',
            code='TZ001',
            advertiser_type='individual',
            email='timezone@test.com'
        )
        
        # Should be timezone-aware
        self.assertIsNotNone(advertiser.created_at.tzinfo)
        
        # Should be close to current time
        time_diff = timezone.now() - advertiser.created_at
        self.assertLess(time_diff.total_seconds(), 60)  # Within 1 minute
    
    def test_concurrent_modification_handling(self):
        """Test handling of concurrent modifications."""
        advertiser = Advertiser.objects.create(
            name='Concurrent Test',
            code='CONC001',
            advertiser_type='individual',
            email='concurrent@test.com'
        )
        
        # Simulate concurrent modification
        advertiser1 = Advertiser.objects.get(pk=advertiser.pk)
        advertiser2 = Advertiser.objects.get(pk=advertiser.pk)
        
        advertiser1.name = 'Modified by User 1'
        advertiser1.save()
        
        advertiser2.name = 'Modified by User 2'
        advertiser2.save()
        
        # Last save wins (no optimistic locking by default)
        advertiser.refresh_from_db()
        self.assertEqual(advertiser.name, 'Modified by User 2')
        self.assertEqual(len(advertiser_detail_response.data['brands']), 1)
        self.assertEqual(len(advertiser_detail_response.data['contacts']), 1)
    
    def test_nested_api_endpoints(self):
        """Test nested API endpoints."""
        # Create agency and advertiser
        agency = Agency.objects.create(
            name='Test Agency',
            code='AGY001',
            email='agency@test.com'
        )
        
        advertiser = Advertiser.objects.create(
            name='Test Advertiser',
            code='ADV001',
            advertiser_type='agency_managed',
            agency=agency
        )
        
        # Test agency advertisers endpoint
        url = reverse('advertisers:agency-advertisers-list', kwargs={'agency_pk': agency.pk})
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 1)
        self.assertEqual(response.data['results'][0]['name'], 'Test Advertiser')