"""
Integration tests for the stream processing system.
"""

import tempfile
import shutil
from unittest.mock import patch, MagicMock
from django.test import TestCase, TransactionTestCase
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework.test import APIClient
from rest_framework import status

from apps.streams.models import Channel, StreamSession, HLSSegment
from apps.jingles.models import JingleTemplate, JingleDetection, AdBreak
from apps.notifications.models import NotificationChannel, NotificationTemplate, NotificationRule
from apps.streams.services import StreamCaptureService
from apps.jingles.services import JingleDetector, AdBreakAnalyzer
from apps.notifications.services import NotificationService

User = get_user_model()


class StreamProcessingIntegrationTest(TransactionTestCase):
    """Integration tests for complete stream processing workflow."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.temp_dir = tempfile.mkdtemp()
        
        # Create channel
        self.channel = Channel.objects.create(
            name='Integration Test Channel',
            slug='integration-test',
            hls_url='https://example.com/stream.m3u8',
            output_directory=self.temp_dir,
            created_by=self.user
        )
        
        # Create jingle template
        self.jingle_template = JingleTemplate.objects.create(
            name='Test Jingle',
            slug='test-jingle',
            image_path='/tmp/test_jingle.jpg',
            similarity_threshold=0.8,
            created_by=self.user
        )
        
        # Create notification channel
        self.notification_channel = NotificationChannel.objects.create(
            name='Test Notifications',
            channel_type='telegram',
            configuration={'bot_token': 'token', 'chat_id': '123'},
            created_by=self.user
        )
        
        # Create notification template
        self.notification_template = NotificationTemplate.objects.create(
            name='Ad Break Template',
            template_type='ad_break_detected',
            subject_template='Ad Break Detected',
            body_template='Ad break detected in {{channel_name}}',
            created_by=self.user
        )
        
        # Create notification rule
        self.notification_rule = NotificationRule.objects.create(
            name='Ad Break Rule',
            event_type='ad_break_detected',
            template=self.notification_template,
            created_by=self.user
        )
        self.notification_rule.channels.add(self.notification_channel)
    
    def tearDown(self):
        shutil.rmtree(self.temp_dir, ignore_errors=True)
    
    @patch('subprocess.Popen')
    @patch('apps.jingles.services.JingleDetector.detect_jingle')
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_complete_stream_processing_workflow(self, mock_telegram, mock_detect, mock_popen):
        """Test complete workflow from stream start to ad break detection and notification."""
        # Mock FFmpeg process
        mock_process = MagicMock()
        mock_process.pid = 12345
        mock_process.poll.return_value = None
        mock_popen.return_value = mock_process
        
        # Mock jingle detection
        mock_detect.return_value = (
            'Test Jingle',
            '/tmp/frame.jpg',
            0.75,
            self.jingle_template
        )
        
        # Mock notification sending
        mock_telegram.return_value = True
        
        # Step 1: Start stream capture
        capture_service = StreamCaptureService()
        session = capture_service.start_capture(self.channel)
        
        self.assertIsNotNone(session)
        self.assertEqual(session.status, 'active')
        
        # Step 2: Process segment with jingle detection
        segment = HLSSegment.objects.create(
            session=session,
            sequence_number=1,
            filename='segment_001.ts',
            file_path='/tmp/segment_001.ts',
            duration=6.0
        )
        
        detector = JingleDetector()
        detection = detector.process_detection('/tmp/segment_001.ts', session)
        
        self.assertIsNotNone(detection)
        self.assertEqual(detection.jingle_template, self.jingle_template)
        
        # Step 3: Analyze detection for ad break
        analyzer = AdBreakAnalyzer()
        analysis_result = analyzer.analyze_detection(detection)
        
        self.assertEqual(analysis_result['action'], 'start_ad_break')
        ad_break = analysis_result['ad_break']
        self.assertEqual(ad_break.status, 'active')
        
        # Step 4: Send notification
        notification_service = NotificationService()
        notification_result = notification_service.send_notification(
            template_type='ad_break_detected',
            context={'channel_name': self.channel.name},
            channels=[self.notification_channel]
        )
        
        self.assertTrue(notification_result)
        mock_telegram.assert_called()
        
        # Verify complete workflow
        self.assertEqual(StreamSession.objects.filter(status='active').count(), 1)
        self.assertEqual(JingleDetection.objects.count(), 1)
        self.assertEqual(AdBreak.objects.filter(status='active').count(), 1)
    
    @patch('subprocess.Popen')
    def test_stream_failure_recovery(self, mock_popen):
        """Test stream failure and recovery workflow."""
        # Mock process that fails
        mock_process = MagicMock()
        mock_process.pid = 12345
        mock_process.poll.return_value = 1  # Failed
        mock_popen.return_value = mock_process
        
        capture_service = StreamCaptureService()
        
        # First attempt should fail
        session = capture_service.start_capture(self.channel)
        self.assertIsNone(session)
        
        # Mock successful retry
        mock_process.poll.return_value = None  # Success
        session = capture_service.capture_with_retry(self.channel)
        
        self.assertIsNotNone(session)
        self.assertEqual(session.status, 'active')


class APIIntegrationTest(TestCase):
    """Integration tests for API endpoints."""
    
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.client.force_authenticate(user=self.user)
        
        self.channel = Channel.objects.create(
            name='API Test Channel',
            slug='api-test',
            hls_url='https://example.com/stream.m3u8',
            output_directory='/tmp/api-test',
            created_by=self.user
        )
    
    @patch('apps.streams.tasks.start_stream_capture.delay')
    @patch('apps.streams.tasks.stop_stream_capture.delay')
    def test_stream_control_api_workflow(self, mock_stop_task, mock_start_task):
        """Test complete stream control via API."""
        mock_start_task.return_value = MagicMock(id='start-task-id')
        mock_stop_task.return_value = MagicMock(id='stop-task-id')
        
        # Start stream via API
        start_url = reverse('channel-start-stream', kwargs={'pk': self.channel.pk})
        start_response = self.client.post(start_url)
        
        self.assertEqual(start_response.status_code, status.HTTP_200_OK)
        self.assertIn('task_id', start_response.data)
        mock_start_task.assert_called_once()
        
        # Create active session (simulating successful start)
        session = StreamSession.objects.create(
            channel=self.channel,
            status='active',
            started_at=timezone.now()
        )
        
        # Stop stream via API
        stop_url = reverse('channel-stop-stream', kwargs={'pk': self.channel.pk})
        stop_response = self.client.post(stop_url)
        
        self.assertEqual(stop_response.status_code, status.HTTP_200_OK)
        self.assertIn('task_id', stop_response.data)
        mock_stop_task.assert_called_once()
    
    def test_notification_workflow_via_api(self):
        """Test complete notification setup and sending via API."""
        # Create notification channel via API
        channel_url = reverse('notificationchannel-list')
        channel_data = {
            'name': 'API Test Channel',
            'channel_type': 'telegram',
            'configuration': {'bot_token': 'token', 'chat_id': '123'}
        }
        
        channel_response = self.client.post(channel_url, channel_data, format='json')
        self.assertEqual(channel_response.status_code, status.HTTP_201_CREATED)
        
        # Create notification template via API
        template_url = reverse('notificationtemplate-list')
        template_data = {
            'name': 'API Test Template',
            'template_type': 'test_event',
            'subject_template': 'Test {{event}}',
            'body_template': 'Test event {{event}} occurred'
        }
        
        template_response = self.client.post(template_url, template_data)
        self.assertEqual(template_response.status_code, status.HTTP_201_CREATED)
        
        # Create notification rule via API
        rule_url = reverse('notificationrule-list')
        rule_data = {
            'name': 'API Test Rule',
            'event_type': 'test_event',
            'template': template_response.data['id'],
            'channels': [channel_response.data['id']]
        }
        
        rule_response = self.client.post(rule_url, rule_data, format='json')
        self.assertEqual(rule_response.status_code, status.HTTP_201_CREATED)
        
        # Send custom notification via API
        with patch('apps.notifications.services.TelegramService.send_message') as mock_send:
            mock_send.return_value = True
            
            send_url = reverse('notification-send')
            send_data = {
                'template_type': 'test_event',
                'context': {'event': 'API Integration Test'},
                'channels': [channel_response.data['id']]
            }
            
            send_response = self.client.post(send_url, send_data, format='json')
            self.assertEqual(send_response.status_code, status.HTTP_200_OK)
            mock_send.assert_called_once()


class PerformanceIntegrationTest(TestCase):
    """Integration tests for performance and scalability."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
    
    def test_bulk_channel_operations(self):
        """Test performance with multiple channels."""
        # Create multiple channels
        channels = []
        for i in range(10):
            channel = Channel.objects.create(
                name=f'Channel {i}',
                slug=f'channel-{i}',
                hls_url=f'https://example.com/stream{i}.m3u8',
                output_directory=f'/tmp/channel{i}',
                created_by=self.user
            )
            channels.append(channel)
        
        # Test listing performance
        url = reverse('channel-list')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 10)
    
    def test_bulk_notification_creation(self):
        """Test performance with multiple notifications."""
        # Create notification channel
        channel = NotificationChannel.objects.create(
            name='Bulk Test Channel',
            channel_type='telegram',
            configuration={'bot_token': 'token', 'chat_id': '123'},
            created_by=self.user
        )
        
        template = NotificationTemplate.objects.create(
            name='Bulk Template',
            template_type='bulk_test',
            subject_template='Bulk Test',
            body_template='Bulk test message',
            created_by=self.user
        )
        
        # Create multiple notifications
        with patch('apps.notifications.services.TelegramService.send_message') as mock_send:
            mock_send.return_value = True
            
            service = NotificationService()
            
            for i in range(20):
                result = service.send_notification(
                    template_type='bulk_test',
                    context={'index': i},
                    channels=[channel]
                )
                self.assertTrue(result)
        
        # Verify all notifications were created
        from apps.notifications.models import Notification
        notifications = Notification.objects.filter(template=template)
        self.assertEqual(notifications.count(), 20)
    
    def test_concurrent_stream_sessions(self):
        """Test handling multiple concurrent stream sessions."""
        channels = []
        sessions = []
        
        # Create multiple channels and sessions
        for i in range(5):
            channel = Channel.objects.create(
                name=f'Concurrent Channel {i}',
                slug=f'concurrent-{i}',
                hls_url=f'https://example.com/stream{i}.m3u8',
                output_directory=f'/tmp/concurrent{i}',
                created_by=self.user
            )
            channels.append(channel)
            
            session = StreamSession.objects.create(
                channel=channel,
                status='active',
                started_at=timezone.now()
            )
            sessions.append(session)
        
        # Test API performance with multiple active sessions
        url = reverse('streamsession-list')
        response = self.client.get(url, {'status': 'active'})
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['results']), 5)


class SecurityIntegrationTest(TestCase):
    """Integration tests for security features."""
    
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
    
    def test_authentication_required_across_apis(self):
        """Test that authentication is required for all API endpoints."""
        endpoints = [
            reverse('channel-list'),
            reverse('streamsession-list'),
            reverse('notificationchannel-list'),
            reverse('notificationtemplate-list'),
            reverse('notification-list'),
            reverse('notificationrule-list')
        ]
        
        for endpoint in endpoints:
            response = self.client.get(endpoint)
            self.assertEqual(
                response.status_code, 
                status.HTTP_401_UNAUTHORIZED,
                f"Endpoint {endpoint} should require authentication"
            )
    
    def test_user_isolation_across_resources(self):
        """Test that users can only access their own resources."""
        user1 = User.objects.create_user(
            username='user1',
            email='user1@example.com',
            password='testpass123'
        )
        user2 = User.objects.create_user(
            username='user2',
            email='user2@example.com',
            password='testpass123'
        )
        
        # User1 creates resources
        self.client.force_authenticate(user=user1)
        
        channel_data = {
            'name': 'User1 Channel',
            'slug': 'user1-channel',
            'hls_url': 'https://example.com/stream.m3u8',
            'output_directory': '/tmp/user1'
        }
        
        channel_response = self.client.post(reverse('channel-list'), channel_data)
        self.assertEqual(channel_response.status_code, status.HTTP_201_CREATED)
        
        # User2 should not see User1's resources
        self.client.force_authenticate(user=user2)
        
        list_response = self.client.get(reverse('channel-list'))
        self.assertEqual(list_response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(list_response.data['results']), 0)
        
        # User2 should not be able to access User1's channel
        detail_response = self.client.get(
            reverse('channel-detail', kwargs={'pk': channel_response.data['id']})
        )
        self.assertEqual(detail_response.status_code, status.HTTP_404_NOT_FOUND)
    
    def test_input_validation_across_apis(self):
        """Test input validation across different API endpoints."""
        self.client.force_authenticate(user=self.user)
        
        # Test invalid channel data
        invalid_channel_data = {
            'name': '',  # Empty name
            'slug': 'invalid-channel',
            'hls_url': 'not-a-valid-url',  # Invalid URL
            'output_directory': '/tmp/test'
        }
        
        response = self.client.post(reverse('channel-list'), invalid_channel_data)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        
        # Test invalid notification channel data
        invalid_notification_data = {
            'name': 'Invalid Channel',
            'channel_type': 'telegram',
            'configuration': {
                'invalid_field': 'value'
                # Missing required fields
            }
        }
        
        response = self.client.post(
            reverse('notificationchannel-list'), 
            invalid_notification_data, 
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)


class ErrorHandlingIntegrationTest(TestCase):
    """Integration tests for error handling and recovery."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
        
        self.channel = Channel.objects.create(
            name='Error Test Channel',
            slug='error-test',
            hls_url='https://example.com/stream.m3u8',
            output_directory='/tmp/error-test',
            created_by=self.user
        )
    
    @patch('apps.streams.services.StreamCaptureService.start_capture')
    def test_stream_start_error_handling(self, mock_capture):
        """Test error handling when stream start fails."""
        mock_capture.side_effect = Exception("FFmpeg error")
        
        url = reverse('channel-start-stream', kwargs={'pk': self.channel.pk})
        
        with patch('apps.streams.tasks.start_stream_capture.delay') as mock_task:
            mock_task.side_effect = Exception("Task failed")
            
            response = self.client.post(url)
            
            # Should handle error gracefully
            self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
    
    def test_invalid_resource_access(self):
        """Test accessing non-existent resources."""
        # Try to access non-existent channel
        fake_uuid = '00000000-0000-0000-0000-000000000000'
        
        response = self.client.get(
            reverse('channel-detail', kwargs={'pk': fake_uuid})
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        
        # Try to start stream on non-existent channel
        response = self.client.post(
            reverse('channel-start-stream', kwargs={'pk': fake_uuid})
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
    
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_notification_failure_recovery(self, mock_send):
        """Test notification failure and retry mechanism."""
        # Create notification setup
        channel = NotificationChannel.objects.create(
            name='Error Test Channel',
            channel_type='telegram',
            configuration={'bot_token': 'token', 'chat_id': '123'},
            created_by=self.user
        )
        
        template = NotificationTemplate.objects.create(
            name='Error Test Template',
            template_type='error_test',
            subject_template='Error Test',
            body_template='Error test message',
            created_by=self.user
        )
        
        # First attempt fails
        mock_send.side_effect = Exception("Telegram API error")
        
        service = NotificationService()
        result = service.send_notification(
            template_type='error_test',
            context={'test': 'data'},
            channels=[channel]
        )
        
        self.assertFalse(result)
        
        # Check notification was created with failed status
        from apps.notifications.models import Notification
        notification = Notification.objects.first()
        self.assertEqual(notification.status, 'failed')
        
        # Test retry via API
        mock_send.side_effect = None  # Clear exception
        mock_send.return_value = True
        
        retry_url = reverse('notification-retry', kwargs={'pk': notification.pk})
        
        with patch('apps.notifications.tasks.send_notification.delay') as mock_retry_task:
            mock_retry_task.return_value = MagicMock(id='retry-task-id')
            
            retry_response = self.client.post(retry_url)
            self.assertEqual(retry_response.status_code, status.HTTP_200_OK)
            mock_retry_task.assert_called_once()
