"""
Unit tests for jingles app models.
"""

import tempfile
import shutil
import os
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.utils import timezone
from datetime import timedelta

from apps.streams.models import Channel, StreamSession
from apps.jingles.models import (
    JingleTemplate, JingleDetection, AdBreak, DetectionStatistics
)

User = get_user_model()


class JingleTemplateModelTest(TestCase):
    """Test cases for JingleTemplate model."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.temp_dir = tempfile.mkdtemp()
        self.test_image_path = os.path.join(self.temp_dir, 'test_jingle.jpg')
        
        # Create a dummy image file
        with open(self.test_image_path, 'wb') as f:
            f.write(b'fake_image_data')
    
    def tearDown(self):
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_jingle_template_creation(self):
        """Test basic jingle template creation."""
        template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path=self.test_image_path,
            similarity_threshold=0.8,
            category='commercial',
            created_by=self.user
        )
        
        self.assertEqual(template.name, 'Test Jingle')
        self.assertEqual(template.slug, 'test-jingle')
        self.assertEqual(template.similarity_threshold, 0.8)
        self.assertEqual(template.category, 'commercial')
        self.assertTrue(template.is_active)  # Default value
    
    def test_jingle_template_unique_constraints(self):
        """Test unique constraints on name and slug."""
        JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path=self.test_image_path,
            created_by=self.user
        )
        
        # Test duplicate name
        with self.assertRaises(Exception):
            JingleTemplate.objects.create(
                name='Test Jingle',
                slug='test-jingle-2',
                image_path=self.test_image_path,
                created_by=self.user
            )
        
        # Test duplicate slug
        with self.assertRaises(Exception):
            JingleTemplate.objects.create(
                name='Test Jingle 2',
                slug='test-jingle',
                image_path=self.test_image_path,
                created_by=self.user
            )
    
    def test_similarity_threshold_validation(self):
        """Test similarity threshold validation."""
        # Test invalid threshold (negative)
        with self.assertRaises(ValidationError):
            template = JingleTemplate(
                name='Invalid Jingle',
                slug='invalid-jingle',
                image_path=self.test_image_path,
                similarity_threshold=-0.1,  # Invalid
                created_by=self.user
            )
            template.full_clean()
        
        # Test invalid threshold (> 1)
        with self.assertRaises(ValidationError):
            template = JingleTemplate(
                name='Invalid Jingle',
                slug='invalid-jingle',
                image_path=self.test_image_path,
                similarity_threshold=1.5,  # Invalid
                created_by=self.user
            )
            template.full_clean()
    
    def test_image_exists_method(self):
        """Test image existence checking."""
        template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path=self.test_image_path,
            created_by=self.user
        )
        
        self.assertTrue(template.image_exists())
        
        # Test with non-existent image
        template.image_path = '/non/existent/path.jpg'
        self.assertFalse(template.image_exists())
    
    def test_delete_image_method(self):
        """Test image deletion."""
        template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path=self.test_image_path,
            created_by=self.user
        )
        
        self.assertTrue(os.path.exists(self.test_image_path))
        
        result = template.delete_image()
        
        self.assertTrue(result)
        self.assertFalse(os.path.exists(self.test_image_path))


class JingleDetectionModelTest(TestCase):
    """Test cases for JingleDetection model."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.temp_dir = tempfile.mkdtemp()
        
        self.channel = Channel.objects.create(
            name='Test Channel',
            slug='test-channel',
            hls_url='https://example.com/stream.m3u8',
            output_directory=self.temp_dir,
            created_by=self.user
        )
        
        self.session = StreamSession.objects.create(
            channel=self.channel,
            status='active',
            started_at=timezone.now()
        )
        
        self.template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path='/tmp/test_jingle.jpg',
            created_by=self.user
        )
    
    def tearDown(self):
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_detection_creation(self):
        """Test basic detection creation."""
        detection = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_001.ts',
            detection_time=timezone.now(),
            confidence_score=0.85,
            frame_path='/tmp/frame_001.jpg'
        )
        
        self.assertEqual(detection.session, self.session)
        self.assertEqual(detection.jingle_template, self.template)
        self.assertEqual(detection.confidence_score, 0.85)
        self.assertFalse(detection.is_confirmed)  # Default value
        self.assertFalse(detection.is_false_positive)  # Default value
    
    def test_detection_confidence_validation(self):
        """Test confidence score validation."""
        # Test invalid confidence (negative)
        with self.assertRaises(ValidationError):
            detection = JingleDetection(
                session=self.session,
                jingle_template=self.template,
                segment_filename='segment_001.ts',
                detection_time=timezone.now(),
                confidence_score=-0.1,  # Invalid
                frame_path='/tmp/frame_001.jpg'
            )
            detection.full_clean()
        
        # Test invalid confidence (> 1)
        with self.assertRaises(ValidationError):
            detection = JingleDetection(
                session=self.session,
                jingle_template=self.template,
                segment_filename='segment_001.ts',
                detection_time=timezone.now(),
                confidence_score=1.5,  # Invalid
                frame_path='/tmp/frame_001.jpg'
            )
            detection.full_clean()
    
    def test_detection_str_representation(self):
        """Test string representation of detection."""
        detection = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_001.ts',
            detection_time=timezone.now(),
            confidence_score=0.85,
            frame_path='/tmp/frame_001.jpg'
        )
        
        expected_str = f"Detection {detection.id} - {self.template.name} (0.85)"
        self.assertEqual(str(detection), expected_str)


class AdBreakModelTest(TestCase):
    """Test cases for AdBreak model."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.temp_dir = tempfile.mkdtemp()
        
        self.channel = Channel.objects.create(
            name='Test Channel',
            slug='test-channel',
            hls_url='https://example.com/stream.m3u8',
            output_directory=self.temp_dir,
            created_by=self.user
        )
        
        self.session = StreamSession.objects.create(
            channel=self.channel,
            status='active',
            started_at=timezone.now()
        )
        
        self.template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path='/tmp/test_jingle.jpg',
            created_by=self.user
        )
        
        self.start_detection = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_001.ts',
            detection_time=timezone.now(),
            confidence_score=0.85,
            frame_path='/tmp/frame_001.jpg'
        )
    
    def tearDown(self):
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    def test_ad_break_creation(self):
        """Test basic ad break creation."""
        ad_break = AdBreak.objects.create(
            session=self.session,
            start_detection=self.start_detection,
            start_time=timezone.now(),
            status='active'
        )
        
        self.assertEqual(ad_break.session, self.session)
        self.assertEqual(ad_break.start_detection, self.start_detection)
        self.assertEqual(ad_break.status, 'active')
        self.assertIsNone(ad_break.end_detection)
        self.assertIsNone(ad_break.end_time)
    
    def test_ad_break_duration_calculation(self):
        """Test ad break duration calculation."""
        start_time = timezone.now()
        ad_break = AdBreak.objects.create(
            session=self.session,
            start_detection=self.start_detection,
            start_time=start_time,
            status='active'
        )
        
        # Active ad break - duration should be from start to now
        duration = ad_break.duration()
        self.assertIsNotNone(duration)
        self.assertGreater(duration.total_seconds(), 0)
        
        # Completed ad break
        end_time = start_time + timedelta(minutes=3)
        ad_break.end_time = end_time
        ad_break.status = 'completed'
        ad_break.save()
        
        duration = ad_break.duration()
        self.assertEqual(duration, timedelta(minutes=3))
    
    def test_ad_break_str_representation(self):
        """Test string representation of ad break."""
        ad_break = AdBreak.objects.create(
            session=self.session,
            start_detection=self.start_detection,
            start_time=timezone.now(),
            status='active'
        )
        
        expected_str = f"AdBreak {ad_break.id} - {self.session.channel.name} (active)"
        self.assertEqual(str(ad_break), expected_str)
    
    def test_ad_break_completion(self):
        """Test ad break completion with end detection."""
        ad_break = AdBreak.objects.create(
            session=self.session,
            start_detection=self.start_detection,
            start_time=timezone.now(),
            status='active'
        )
        
        # Create end detection
        end_detection = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_010.ts',
            detection_time=timezone.now() + timedelta(minutes=3),
            confidence_score=0.90,
            frame_path='/tmp/frame_010.jpg'
        )
        
        # Complete the ad break
        ad_break.end_detection = end_detection
        ad_break.end_time = end_detection.detection_time
        ad_break.status = 'completed'
        ad_break.save()
        
        self.assertEqual(ad_break.status, 'completed')
        self.assertEqual(ad_break.end_detection, end_detection)
        self.assertIsNotNone(ad_break.duration())


class DetectionStatisticsModelTest(TestCase):
    """Test cases for DetectionStatistics model."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        self.template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path='/tmp/test_jingle.jpg',
            created_by=self.user
        )
    
    def test_statistics_creation(self):
        """Test detection statistics creation."""
        stats = DetectionStatistics.objects.create(
            jingle_template=self.template,
            date=timezone.now().date(),
            total_detections=10,
            confirmed_detections=8,
            false_positives=2,
            average_confidence=0.85
        )
        
        self.assertEqual(stats.jingle_template, self.template)
        self.assertEqual(stats.total_detections, 10)
        self.assertEqual(stats.confirmed_detections, 8)
        self.assertEqual(stats.false_positives, 2)
        self.assertEqual(stats.average_confidence, 0.85)
    
    def test_statistics_accuracy_rate(self):
        """Test accuracy rate calculation."""
        stats = DetectionStatistics.objects.create(
            jingle_template=self.template,
            date=timezone.now().date(),
            total_detections=10,
            confirmed_detections=8,
            false_positives=2,
            average_confidence=0.85
        )
        
        # Accuracy rate should be confirmed / total
        expected_accuracy = (8 / 10) * 100
        self.assertEqual(stats.accuracy_rate(), expected_accuracy)
    
    def test_statistics_accuracy_rate_zero_detections(self):
        """Test accuracy rate with zero detections."""
        stats = DetectionStatistics.objects.create(
            jingle_template=self.template,
            date=timezone.now().date(),
            total_detections=0,
            confirmed_detections=0,
            false_positives=0,
            average_confidence=0.0
        )
        
        self.assertEqual(stats.accuracy_rate(), 0)
    
    def test_statistics_unique_constraint(self):
        """Test unique constraint on template and date."""
        date = timezone.now().date()
        
        DetectionStatistics.objects.create(
            jingle_template=self.template,
            date=date,
            total_detections=5,
            confirmed_detections=4,
            false_positives=1,
            average_confidence=0.80
        )
        
        # Should not be able to create another stat for same template and date
        with self.assertRaises(Exception):
            DetectionStatistics.objects.create(
                jingle_template=self.template,
                date=date,
                total_detections=3,
                confirmed_detections=2,
                false_positives=1,
                average_confidence=0.75
            )
    
    def test_statistics_str_representation(self):
        """Test string representation of statistics."""
        date = timezone.now().date()
        stats = DetectionStatistics.objects.create(
            jingle_template=self.template,
            date=date,
            total_detections=10,
            confirmed_detections=8,
            false_positives=2,
            average_confidence=0.85
        )
        
        expected_str = f"{self.template.name} - {date} (10 detections)"
        self.assertEqual(str(stats), expected_str)


class JingleDetectionQueryTest(TestCase):
    """Test cases for JingleDetection queries and relationships."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        self.channel = Channel.objects.create(
            name='Test Channel',
            slug='test-channel',
            hls_url='https://example.com/stream.m3u8',
            output_directory='/tmp/test',
            created_by=self.user
        )
        
        self.session = StreamSession.objects.create(
            channel=self.channel,
            status='active',
            started_at=timezone.now()
        )
        
        self.template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path='/tmp/test_jingle.jpg',
            created_by=self.user
        )
    
    def test_detection_filtering_by_confidence(self):
        """Test filtering detections by confidence score."""
        # Create detections with different confidence scores
        high_confidence = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_001.ts',
            detection_time=timezone.now(),
            confidence_score=0.95,
            frame_path='/tmp/frame_001.jpg'
        )
        
        low_confidence = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_002.ts',
            detection_time=timezone.now(),
            confidence_score=0.65,
            frame_path='/tmp/frame_002.jpg'
        )
        
        # Filter high confidence detections
        high_conf_detections = JingleDetection.objects.filter(
            confidence_score__gte=0.9
        )
        self.assertEqual(high_conf_detections.count(), 1)
        self.assertEqual(high_conf_detections.first(), high_confidence)
        
        # Filter low confidence detections
        low_conf_detections = JingleDetection.objects.filter(
            confidence_score__lt=0.8
        )
        self.assertEqual(low_conf_detections.count(), 1)
        self.assertEqual(low_conf_detections.first(), low_confidence)
    
    def test_detection_filtering_by_template(self):
        """Test filtering detections by template."""
        # Create another template
        other_template = JingleTemplate.objects.create(
            name='Other Jingle',
            slug='other-jingle',
            image_path='/tmp/other_jingle.jpg',
            created_by=self.user
        )
        
        # Create detections for both templates
        detection1 = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_001.ts',
            detection_time=timezone.now(),
            confidence_score=0.85,
            frame_path='/tmp/frame_001.jpg'
        )
        
        detection2 = JingleDetection.objects.create(
            session=self.session,
            jingle_template=other_template,
            segment_filename='segment_002.ts',
            detection_time=timezone.now(),
            confidence_score=0.90,
            frame_path='/tmp/frame_002.jpg'
        )
        
        # Filter by template
        template_detections = JingleDetection.objects.filter(
            jingle_template=self.template
        )
        self.assertEqual(template_detections.count(), 1)
        self.assertEqual(template_detections.first(), detection1)
    
    def test_detection_time_ordering(self):
        """Test detection ordering by time."""
        now = timezone.now()
        
        # Create detections at different times
        detection2 = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_002.ts',
            detection_time=now + timedelta(minutes=5),
            confidence_score=0.85,
            frame_path='/tmp/frame_002.jpg'
        )
        
        detection1 = JingleDetection.objects.create(
            session=self.session,
            jingle_template=self.template,
            segment_filename='segment_001.ts',
            detection_time=now,
            confidence_score=0.90,
            frame_path='/tmp/frame_001.jpg'
        )
        
        # Should be ordered by detection time (newest first)
        detections = list(JingleDetection.objects.all())
        self.assertEqual(detections[0], detection2)
        self.assertEqual(detections[1], detection1)
