"""Test notification system"""
import pytest
from datetime import datetime, timedelta
from app import create_app
from app.extensions import db
from app.models import User, Contract, Job
from app.models.notification import Notification, NotificationPreference, NotificationTemplate
from app.services.notification_service import NotificationService
from app.utils.notification_helpers import NotificationTriggers


@pytest.fixture
def app():
    """Create and configure test app"""
    app = create_app()
    app.config.update({
        'TESTING': True,
        'SQLALCHEMY_DATABASE_URI': 'sqlite:///:memory:',
        'SECRET_KEY': 'test-secret-key',
        'MAIL_SUPPRESS_SEND': True,  # Suppress actual email sending in tests
        'SMS_SUPPRESS_SEND': True    # Suppress SMS sending in tests
    })
    
    with app.app_context():
        db.create_all()
        yield app
        db.session.remove()
        db.drop_all()


@pytest.fixture
def sample_users(app):
    """Create sample users for testing"""
    contractor = User(
        email='contractor@test.com',
        first_name='Test',
        last_name='Contractor',
        role='contractor',
        phone_number='0400000001',
        location='Sydney, NSW',
        abn_number='12345678901',
        is_active=True,
        privacy_consent=True,
        terms_accepted=True,
        terms_accepted_date=datetime.utcnow()
    )
    contractor.set_password('password123')
    
    worker = User(
        email='worker@test.com',
        first_name='Test',
        last_name='Worker',
        role='worker',
        phone_number='0400000002',
        location='Melbourne, VIC',
        abn_number='12345678902',
        is_active=True,
        privacy_consent=True,
        terms_accepted=True,
        terms_accepted_date=datetime.utcnow()
    )
    worker.set_password('password123')
    
    db.session.add_all([contractor, worker])
    db.session.commit()
    
    return {
        'contractor': contractor,
        'worker': worker
    }


@pytest.fixture
def notification_templates(app):
    """Create sample notification templates"""
    templates = [
        NotificationTemplate(
            name='job_match_found',
            subject='New Job Match Found',
            email_template='A new job matches your skills: {{job_title}}',
            sms_template='New job match: {{job_title}}',
            push_template='{{job_title}} - New match!',
            is_active=True
        ),
        NotificationTemplate(
            name='rating_received',
            subject='You received a new rating',
            email_template='{{reviewer_name}} rated you {{rating}} stars',
            sms_template='New {{rating}}-star rating from {{reviewer_name}}',
            push_template='{{rating}}⭐ rating received!',
            is_active=True
        ),
        NotificationTemplate(
            name='payment_received',
            subject='Payment Received',
            email_template='Payment of ${{amount}} received for {{job_title}}',
            sms_template='${{amount}} payment received',
            push_template='Payment received: ${{amount}}',
            is_active=True
        )
    ]
    
    db.session.add_all(templates)
    db.session.commit()
    
    return templates


class TestNotificationSystem:
    """Test notification system functionality"""
    
    def test_create_notification_basic(self, app, sample_users):
        """Test basic notification creation"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create notification
            notification = service.create_notification(
                user_id=user.id,
                type='job_match_found',
                title='New Job Available',
                message='A construction job matches your profile',
                data={'job_id': 123, 'job_title': 'Concrete Work'}
            )
            
            assert notification is not None
            assert notification.user_id == user.id
            assert notification.type == 'job_match_found'
            assert notification.title == 'New Job Available'
            assert notification.message == 'A construction job matches your profile'
            assert notification.status == 'pending'
    
    def test_send_email_notification(self, app, sample_users, notification_templates):
        """Test sending email notification"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Send email notification
            success = service.send_notification(
                user_id=user.id,
                type='job_match_found',
                title='New Job Match',
                message='Check out this new opportunity',
                channels=['email'],
                data={'job_title': 'Plumbing Work', 'location': 'Sydney'}
            )
            
            assert success is True
            
            # Check notification was created
            notification = Notification.query.filter_by(user_id=user.id).first()
            assert notification is not None
            assert 'email' in notification.channels
            assert notification.email_sent is True
    
    def test_send_sms_notification(self, app, sample_users, notification_templates):
        """Test sending SMS notification"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Send SMS notification
            success = service.send_notification(
                user_id=user.id,
                type='rating_received',
                title='New Rating',
                message='You received a 5-star rating!',
                channels=['sms'],
                data={'reviewer_name': 'John Smith', 'rating': 5}
            )
            
            assert success is True
            
            # Check notification was created
            notification = Notification.query.filter_by(user_id=user.id).first()
            assert notification is not None
            assert 'sms' in notification.channels
            assert notification.sms_sent is True
    
    def test_send_push_notification(self, app, sample_users, notification_templates):
        """Test sending push notification"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Send push notification
            success = service.send_notification(
                user_id=user.id,
                type='payment_received',
                title='Payment Received',
                message='Your payment has been processed',
                channels=['push'],
                data={'amount': '1500.00', 'job_title': 'Kitchen Renovation'}
            )
            
            assert success is True
            
            # Check notification was created
            notification = Notification.query.filter_by(user_id=user.id).first()
            assert notification is not None
            assert 'push' in notification.channels
            assert notification.push_sent is True
    
    def test_notification_preferences_respected(self, app, sample_users):
        """Test that user preferences are respected"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Set user preferences - disable email, enable SMS
            preferences = NotificationPreference(
                user_id=user.id,
                type='job_match_found',
                email_enabled=False,
                sms_enabled=True,
                push_enabled=True,
                frequency='immediate'
            )
            db.session.add(preferences)
            db.session.commit()
            
            # Try to send notification via email and SMS
            success = service.send_notification(
                user_id=user.id,
                type='job_match_found',
                title='New Job',
                message='Job available',
                channels=['email', 'sms'],
                data={'job_title': 'Test Job'}
            )
            
            assert success is True
            
            notification = Notification.query.filter_by(user_id=user.id).first()
            assert notification.email_sent is False  # Email disabled
            assert notification.sms_sent is True     # SMS enabled
    
    def test_batch_notifications(self, app, sample_users):
        """Test sending notifications to multiple users"""
        with app.app_context():
            service = NotificationService()
            users = list(sample_users.values())
            
            # Send batch notification
            success = service.send_bulk_notifications(
                user_ids=[user.id for user in users],
                type='system_update',
                title='System Maintenance',
                message='Scheduled maintenance tonight',
                channels=['email'],
                data={'maintenance_time': '2AM-4AM'}
            )
            
            assert success is True
            
            # Check notifications were created for both users
            notifications = Notification.query.all()
            assert len(notifications) == 2
            
            user_ids = [n.user_id for n in notifications]
            assert sample_users['contractor'].id in user_ids
            assert sample_users['worker'].id in user_ids
    
    def test_notification_triggers_job_match(self, app, sample_users):
        """Test notification trigger for job matches"""
        with app.app_context():
            triggers = NotificationTriggers()
            worker = sample_users['worker']
            
            # Create a job
            job = Job(
                title='Test Construction Job',
                description='Construction work available',
                contractor_id=sample_users['contractor'].id,
                location='Sydney, NSW',
                budget_min=1000.00,
                budget_max=2000.00,
                status='open'
            )
            db.session.add(job)
            db.session.commit()
            
            # Trigger job match notification
            success = triggers.job_match_found(
                worker_id=worker.id,
                job_id=job.id,
                match_score=0.85
            )
            
            assert success is True
            
            # Check notification was created
            notification = Notification.query.filter_by(
                user_id=worker.id,
                type='job_match_found'
            ).first()
            
            assert notification is not None
            assert str(job.id) in notification.data
    
    def test_notification_triggers_rating_received(self, app, sample_users):
        """Test notification trigger for ratings"""
        with app.app_context():
            triggers = NotificationTriggers()
            worker = sample_users['worker']
            contractor = sample_users['contractor']
            
            # Trigger rating notification
            success = triggers.rating_received(
                rated_user_id=worker.id,
                rating_user_id=contractor.id,
                rating_value=5,
                contract_id=123
            )
            
            assert success is True
            
            # Check notification was created
            notification = Notification.query.filter_by(
                user_id=worker.id,
                type='rating_received'
            ).first()
            
            assert notification is not None
            assert 'rating_value' in notification.data
            assert notification.data['rating_value'] == 5
    
    def test_notification_triggers_payment_received(self, app, sample_users):
        """Test notification trigger for payments"""
        with app.app_context():
            triggers = NotificationTriggers()
            worker = sample_users['worker']
            
            # Trigger payment notification
            success = triggers.payment_received(
                recipient_id=worker.id,
                amount=1500.00,
                contract_id=123,
                payment_method='bank_transfer'
            )
            
            assert success is True
            
            # Check notification was created
            notification = Notification.query.filter_by(
                user_id=worker.id,
                type='payment_received'
            ).first()
            
            assert notification is not None
            assert 'amount' in notification.data
            assert float(notification.data['amount']) == 1500.00
    
    def test_quiet_hours_respected(self, app, sample_users):
        """Test that quiet hours are respected"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Set quiet hours preference
            preferences = NotificationPreference(
                user_id=user.id,
                type='job_match_found',
                quiet_hours_start='22:00',
                quiet_hours_end='08:00',
                quiet_hours_timezone='Australia/Sydney'
            )
            db.session.add(preferences)
            db.session.commit()
            
            # Mock current time to be during quiet hours (e.g., 2 AM)
            # This would typically involve patching datetime.now()
            # For this test, we'll just verify the logic exists
            
            success = service.send_notification(
                user_id=user.id,
                type='job_match_found',
                title='Late Night Job',
                message='This should be delayed',
                channels=['sms']
            )
            
            # Should still succeed but might be scheduled for later
            assert success is True
    
    def test_notification_templates_rendering(self, app, sample_users, notification_templates):
        """Test that notification templates render correctly"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Send notification with template variables
            success = service.send_notification(
                user_id=user.id,
                type='rating_received',
                title='Rating Notification',
                message='Template test',
                channels=['email'],
                data={
                    'reviewer_name': 'John Smith',
                    'rating': 5,
                    'job_title': 'Kitchen Renovation'
                }
            )
            
            assert success is True
            
            notification = Notification.query.filter_by(user_id=user.id).first()
            
            # Check that template variables were rendered
            # This depends on how the rendered content is stored
            assert 'John Smith' in str(notification.data) or 'John Smith' in notification.message
    
    def test_get_user_notifications(self, app, sample_users):
        """Test retrieving user notifications"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create multiple notifications
            for i in range(3):
                service.create_notification(
                    user_id=user.id,
                    type='job_match_found',
                    title=f'Job {i+1}',
                    message=f'Job opportunity {i+1}',
                    data={'job_id': i+1}
                )
            
            # Get notifications
            notifications = service.get_user_notifications(user.id, limit=10)
            
            assert len(notifications) == 3
            
            # Should be ordered by most recent first
            assert notifications[0].title == 'Job 3'
            assert notifications[1].title == 'Job 2'
            assert notifications[2].title == 'Job 1'
    
    def test_mark_notification_as_read(self, app, sample_users):
        """Test marking notifications as read"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create notification
            notification = service.create_notification(
                user_id=user.id,
                type='job_match_found',
                title='Test Job',
                message='Test message'
            )
            
            assert notification.is_read is False
            
            # Mark as read
            success = service.mark_as_read(notification.id, user.id)
            
            assert success is True
            
            # Check status updated
            db.session.refresh(notification)
            assert notification.is_read is True
            assert notification.read_at is not None
    
    def test_notification_expiry(self, app, sample_users):
        """Test notification expiry handling"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create notification with expiry
            notification = service.create_notification(
                user_id=user.id,
                type='job_match_found',
                title='Expiring Job',
                message='This job will expire soon',
                expires_at=datetime.utcnow() + timedelta(hours=24)
            )
            
            assert notification.expires_at is not None
            assert notification.is_expired() is False
            
            # Test with expired notification
            expired_notification = service.create_notification(
                user_id=user.id,
                type='job_match_found',
                title='Expired Job',
                message='This job has expired',
                expires_at=datetime.utcnow() - timedelta(hours=1)
            )
            
            assert expired_notification.is_expired() is True
    
    def test_notification_delivery_failure_handling(self, app, sample_users):
        """Test handling of notification delivery failures"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create notification
            notification = service.create_notification(
                user_id=user.id,
                type='job_match_found',
                title='Test Job',
                message='Test message'
            )
            
            # Simulate delivery failure
            service.mark_delivery_failed(
                notification.id,
                channel='email',
                error_message='SMTP server unavailable'
            )
            
            db.session.refresh(notification)
            assert notification.status == 'failed'
            assert notification.error_message == 'SMTP server unavailable'
    
    def test_notification_retry_mechanism(self, app, sample_users):
        """Test notification retry logic"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create failed notification
            notification = service.create_notification(
                user_id=user.id,
                type='job_match_found',
                title='Retry Test',
                message='Should be retried'
            )
            notification.status = 'failed'
            notification.retry_count = 1
            db.session.commit()
            
            # Attempt retry
            success = service.retry_failed_notification(notification.id)
            
            # Should attempt retry (success depends on implementation)
            assert success is True or success is False
            
            db.session.refresh(notification)
            assert notification.retry_count >= 1
    
    def test_unsubscribe_functionality(self, app, sample_users):
        """Test unsubscribe from notifications"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create preference
            preference = NotificationPreference(
                user_id=user.id,
                type='job_match_found',
                email_enabled=True
            )
            db.session.add(preference)
            db.session.commit()
            
            # Unsubscribe from email notifications
            success = service.update_notification_preferences(
                user.id,
                'job_match_found',
                email_enabled=False
            )
            
            assert success is True
            
            # Check preference updated
            db.session.refresh(preference)
            assert preference.email_enabled is False
    
    def test_notification_analytics(self, app, sample_users):
        """Test notification analytics and statistics"""
        with app.app_context():
            service = NotificationService()
            user = sample_users['worker']
            
            # Create various notifications
            notifications_data = [
                ('job_match_found', 'sent'),
                ('rating_received', 'sent'), 
                ('payment_received', 'failed'),
                ('job_match_found', 'read')
            ]
            
            for notif_type, status in notifications_data:
                notification = service.create_notification(
                    user_id=user.id,
                    type=notif_type,
                    title='Test',
                    message='Test'
                )
                notification.status = status
                if status == 'read':
                    notification.is_read = True
                    notification.read_at = datetime.utcnow()
            
            db.session.commit()
            
            # Get analytics
            stats = service.get_notification_analytics(user.id)
            
            assert 'total_sent' in stats
            assert 'total_read' in stats
            assert 'total_failed' in stats
            assert stats['total_sent'] >= 2
            assert stats['total_failed'] >= 1
