"""
Unit tests for notifications app services.
"""

import json
from unittest.mock import patch, MagicMock
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.utils import timezone

from apps.notifications.models import (
    NotificationChannel, NotificationTemplate, Notification
)
from apps.notifications.services import (
    NotificationService, TelegramService, EmailService, WebhookService
)

User = get_user_model()


class NotificationServiceTest(TestCase):
    """Test cases for NotificationService."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        self.telegram_channel = NotificationChannel.objects.create(
            name='Telegram Channel',
            channel_type='telegram',
            configuration={'bot_token': 'token', 'chat_id': '123'},
            created_by=self.user
        )
        
        self.email_channel = NotificationChannel.objects.create(
            name='Email Channel',
            channel_type='email',
            configuration={
                'smtp_host': 'smtp.test.com',
                'smtp_port': 587,
                'username': 'test@test.com',
                'password': 'password'
            },
            created_by=self.user
        )
        
        self.template = NotificationTemplate.objects.create(
            name='Test Template',
            template_type='stream_started',
            subject_template='Stream {{channel_name}} Started',
            body_template='Stream {{channel_name}} has started at {{start_time}}',
            created_by=self.user
        )
        
        self.service = NotificationService()
    
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_send_notification_telegram_success(self, mock_send):
        """Test successful Telegram notification sending."""
        mock_send.return_value = True
        
        context = {
            'channel_name': 'Test Channel',
            'start_time': '2024-01-01 12:00:00'
        }
        
        result = self.service.send_notification(
            template_type='stream_started',
            context=context,
            channels=[self.telegram_channel]
        )
        
        self.assertTrue(result)
        mock_send.assert_called_once()
        
        # Check notification was created
        notification = Notification.objects.first()
        self.assertIsNotNone(notification)
        self.assertEqual(notification.channel, self.telegram_channel)
        self.assertEqual(notification.status, 'completed')
    
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_send_notification_telegram_failure(self, mock_send):
        """Test failed Telegram notification sending."""
        mock_send.side_effect = Exception("Telegram API error")
        
        context = {'channel_name': 'Test Channel'}
        
        result = self.service.send_notification(
            template_type='stream_started',
            context=context,
            channels=[self.telegram_channel]
        )
        
        self.assertFalse(result)
        
        # Check notification was created with failed status
        notification = Notification.objects.first()
        self.assertIsNotNone(notification)
        self.assertEqual(notification.status, 'failed')
        self.assertIn('Telegram API error', notification.error_message)
    
    @patch('apps.notifications.services.EmailService.send_email')
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_send_notification_multiple_channels(self, mock_telegram, mock_email):
        """Test sending notification to multiple channels."""
        mock_telegram.return_value = True
        mock_email.return_value = True
        
        context = {'channel_name': 'Test Channel'}
        
        result = self.service.send_notification(
            template_type='stream_started',
            context=context,
            channels=[self.telegram_channel, self.email_channel]
        )
        
        self.assertTrue(result)
        mock_telegram.assert_called_once()
        mock_email.assert_called_once()
        
        # Check both notifications were created
        notifications = Notification.objects.all()
        self.assertEqual(notifications.count(), 2)
    
    def test_send_notification_invalid_template(self):
        """Test sending notification with invalid template type."""
        context = {'channel_name': 'Test Channel'}
        
        result = self.service.send_notification(
            template_type='nonexistent_template',
            context=context,
            channels=[self.telegram_channel]
        )
        
        self.assertFalse(result)
        
        # No notification should be created
        self.assertEqual(Notification.objects.count(), 0)


class TelegramServiceTest(TestCase):
    """Test cases for TelegramService."""
    
    def setUp(self):
        self.service = TelegramService()
        self.config = {
            'bot_token': 'test_token',
            'chat_id': '123456789'
        }
    
    @patch('requests.post')
    def test_send_message_success(self, mock_post):
        """Test successful Telegram message sending."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {'ok': True, 'result': {'message_id': 123}}
        mock_post.return_value = mock_response
        
        result = self.service.send_message(
            config=self.config,
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertTrue(result)
        mock_post.assert_called_once()
        
        # Check API call parameters
        call_args = mock_post.call_args
        self.assertIn('sendMessage', call_args[0][0])
        self.assertEqual(call_args[1]['json']['chat_id'], '123456789')
        self.assertIn('Test Subject', call_args[1]['json']['text'])
    
    @patch('requests.post')
    def test_send_message_api_error(self, mock_post):
        """Test Telegram API error handling."""
        mock_response = MagicMock()
        mock_response.status_code = 400
        mock_response.json.return_value = {'ok': False, 'description': 'Bad Request'}
        mock_post.return_value = mock_response
        
        result = self.service.send_message(
            config=self.config,
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertFalse(result)
    
    @patch('requests.post')
    def test_send_message_network_error(self, mock_post):
        """Test network error handling."""
        mock_post.side_effect = Exception("Network error")
        
        result = self.service.send_message(
            config=self.config,
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertFalse(result)
    
    @patch('requests.get')
    def test_get_bot_info_success(self, mock_get):
        """Test getting bot information."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            'ok': True,
            'result': {
                'id': 123456789,
                'is_bot': True,
                'first_name': 'Test Bot',
                'username': 'test_bot'
            }
        }
        mock_get.return_value = mock_response
        
        bot_info = self.service.get_bot_info('test_token')
        
        self.assertIsNotNone(bot_info)
        self.assertEqual(bot_info['username'], 'test_bot')
        self.assertTrue(bot_info['is_bot'])


class EmailServiceTest(TestCase):
    """Test cases for EmailService."""
    
    def setUp(self):
        self.service = EmailService()
        self.config = {
            'smtp_host': 'smtp.test.com',
            'smtp_port': 587,
            'username': 'test@test.com',
            'password': 'password',
            'use_tls': True
        }
    
    @patch('smtplib.SMTP')
    def test_send_email_success(self, mock_smtp_class):
        """Test successful email sending."""
        mock_smtp = MagicMock()
        mock_smtp_class.return_value = mock_smtp
        
        result = self.service.send_email(
            config=self.config,
            recipient='recipient@example.com',
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertTrue(result)
        
        # Verify SMTP calls
        mock_smtp.starttls.assert_called_once()
        mock_smtp.login.assert_called_once_with('test@test.com', 'password')
        mock_smtp.send_message.assert_called_once()
        mock_smtp.quit.assert_called_once()
    
    @patch('smtplib.SMTP')
    def test_send_email_smtp_error(self, mock_smtp_class):
        """Test SMTP error handling."""
        mock_smtp_class.side_effect = Exception("SMTP connection failed")
        
        result = self.service.send_email(
            config=self.config,
            recipient='recipient@example.com',
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertFalse(result)
    
    @patch('smtplib.SMTP')
    def test_send_email_without_tls(self, mock_smtp_class):
        """Test email sending without TLS."""
        mock_smtp = MagicMock()
        mock_smtp_class.return_value = mock_smtp
        
        config_no_tls = self.config.copy()
        config_no_tls['use_tls'] = False
        
        result = self.service.send_email(
            config=config_no_tls,
            recipient='recipient@example.com',
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertTrue(result)
        
        # Verify TLS was not called
        mock_smtp.starttls.assert_not_called()


class WebhookServiceTest(TestCase):
    """Test cases for WebhookService."""
    
    def setUp(self):
        self.service = WebhookService()
        self.config = {
            'url': 'https://api.example.com/webhook',
            'method': 'POST',
            'headers': {'Authorization': 'Bearer token123'}
        }
    
    @patch('requests.request')
    def test_send_webhook_success(self, mock_request):
        """Test successful webhook sending."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.text = 'OK'
        mock_request.return_value = mock_response
        
        result = self.service.send_webhook(
            config=self.config,
            subject='Test Subject',
            message='Test Message',
            context={'key': 'value'}
        )
        
        self.assertTrue(result)
        
        # Verify request parameters
        mock_request.assert_called_once()
        call_args = mock_request.call_args
        
        self.assertEqual(call_args[0][0], 'POST')  # method
        self.assertEqual(call_args[0][1], 'https://api.example.com/webhook')  # url
        self.assertIn('Authorization', call_args[1]['headers'])
    
    @patch('requests.request')
    def test_send_webhook_http_error(self, mock_request):
        """Test webhook HTTP error handling."""
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_response.text = 'Not Found'
        mock_request.return_value = mock_response
        
        result = self.service.send_webhook(
            config=self.config,
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertFalse(result)
    
    @patch('requests.request')
    def test_send_webhook_network_error(self, mock_request):
        """Test webhook network error handling."""
        mock_request.side_effect = Exception("Connection timeout")
        
        result = self.service.send_webhook(
            config=self.config,
            subject='Test Subject',
            message='Test Message'
        )
        
        self.assertFalse(result)
    
    def test_send_webhook_get_method(self):
        """Test webhook with GET method."""
        config_get = self.config.copy()
        config_get['method'] = 'GET'
        
        with patch('requests.request') as mock_request:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_request.return_value = mock_response
            
            result = self.service.send_webhook(
                config=config_get,
                subject='Test Subject',
                message='Test Message'
            )
            
            self.assertTrue(result)
            
            # Verify GET method was used
            call_args = mock_request.call_args
            self.assertEqual(call_args[0][0], 'GET')


class NotificationServiceIntegrationTest(TestCase):
    """Integration tests for notification service with all channel types."""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        
        # Create channels for all types
        self.telegram_channel = NotificationChannel.objects.create(
            name='Telegram',
            channel_type='telegram',
            configuration={'bot_token': 'token', 'chat_id': '123'},
            created_by=self.user
        )
        
        self.email_channel = NotificationChannel.objects.create(
            name='Email',
            channel_type='email',
            configuration={
                'smtp_host': 'smtp.test.com',
                'smtp_port': 587,
                'username': 'test@test.com',
                'password': 'password'
            },
            created_by=self.user
        )
        
        self.webhook_channel = NotificationChannel.objects.create(
            name='Webhook',
            channel_type='webhook',
            configuration={
                'url': 'https://api.example.com/webhook',
                'method': 'POST'
            },
            created_by=self.user
        )
        
        self.template = NotificationTemplate.objects.create(
            name='Multi Channel Template',
            template_type='ad_break_detected',
            subject_template='Ad Break in {{channel_name}}',
            body_template='Ad break detected in {{channel_name}} at {{detection_time}}',
            created_by=self.user
        )
        
        self.service = NotificationService()
    
    @patch('apps.notifications.services.WebhookService.send_webhook')
    @patch('apps.notifications.services.EmailService.send_email')
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_send_to_all_channel_types(self, mock_telegram, mock_email, mock_webhook):
        """Test sending notification to all channel types."""
        mock_telegram.return_value = True
        mock_email.return_value = True
        mock_webhook.return_value = True
        
        context = {
            'channel_name': 'Test Stream',
            'detection_time': '2024-01-01 12:00:00'
        }
        
        result = self.service.send_notification(
            template_type='ad_break_detected',
            context=context,
            channels=[self.telegram_channel, self.email_channel, self.webhook_channel]
        )
        
        self.assertTrue(result)
        
        # All services should be called
        mock_telegram.assert_called_once()
        mock_email.assert_called_once()
        mock_webhook.assert_called_once()
        
        # All notifications should be created
        notifications = Notification.objects.all()
        self.assertEqual(notifications.count(), 3)
        
        # All should be completed
        completed_notifications = notifications.filter(status='completed')
        self.assertEqual(completed_notifications.count(), 3)
    
    @patch('apps.notifications.services.TelegramService.send_message')
    def test_send_notification_partial_failure(self, mock_telegram):
        """Test notification sending with partial failures."""
        # Telegram succeeds, email fails
        mock_telegram.return_value = True
        
        with patch('apps.notifications.services.EmailService.send_email') as mock_email:
            mock_email.side_effect = Exception("SMTP error")
            
            context = {'channel_name': 'Test Stream'}
            
            result = self.service.send_notification(
                template_type='ad_break_detected',
                context=context,
                channels=[self.telegram_channel, self.email_channel]
            )
            
            # Should return False due to partial failure
            self.assertFalse(result)
            
            # Check notification statuses
            notifications = Notification.objects.all()
            self.assertEqual(notifications.count(), 2)
            
            telegram_notification = notifications.filter(channel=self.telegram_channel).first()
            email_notification = notifications.filter(channel=self.email_channel).first()
            
            self.assertEqual(telegram_notification.status, 'completed')
            self.assertEqual(email_notification.status, 'failed')
    
    def test_get_service_for_channel_type(self):
        """Test service selection for different channel types."""
        telegram_service = self.service.get_service_for_channel_type('telegram')
        self.assertIsInstance(telegram_service, TelegramService)
        
        email_service = self.service.get_service_for_channel_type('email')
        self.assertIsInstance(email_service, EmailService)
        
        webhook_service = self.service.get_service_for_channel_type('webhook')
        self.assertIsInstance(webhook_service, WebhookService)
        
        # Test invalid channel type
        invalid_service = self.service.get_service_for_channel_type('invalid')
        self.assertIsNone(invalid_service)
    
    def test_validate_channel_configuration(self):
        """Test channel configuration validation."""
        # Valid Telegram config
        valid_telegram = {
            'bot_token': 'token',
            'chat_id': '123'
        }
        self.assertTrue(self.service.validate_channel_configuration('telegram', valid_telegram))
        
        # Invalid Telegram config (missing chat_id)
        invalid_telegram = {
            'bot_token': 'token'
        }
        self.assertFalse(self.service.validate_channel_configuration('telegram', invalid_telegram))
        
        # Valid email config
        valid_email = {
            'smtp_host': 'smtp.test.com',
            'smtp_port': 587,
            'username': 'test@test.com',
            'password': 'password'
        }
        self.assertTrue(self.service.validate_channel_configuration('email', valid_email))
        
        # Invalid email config (missing smtp_host)
        invalid_email = {
            'smtp_port': 587,
            'username': 'test@test.com'
        }
        self.assertFalse(self.service.validate_channel_configuration('email', invalid_email))
        
        # Valid webhook config
        valid_webhook = {
            'url': 'https://api.example.com/webhook',
            'method': 'POST'
        }
        self.assertTrue(self.service.validate_channel_configuration('webhook', valid_webhook))
        
        # Invalid webhook config (missing url)
        invalid_webhook = {
            'method': 'POST'
        }
        self.assertFalse(self.service.validate_channel_configuration('webhook', invalid_webhook))
