"""Signals for Reporting and Analytics app."""

import logging
from datetime import datetime, timedelta
from typing import Dict, Any

from django.db.models.signals import post_save, post_delete, pre_save
from django.dispatch import receiver, Signal
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.utils import timezone
from django.core.cache import cache
from django.conf import settings

from .models import (
    SfrAnalytics,
    MarketShare,
    VerificationRecord,
    PredictionModel,
    SfrPrediction,
    AdbreakPrediction,
    ActivityLog,
    RealTimeAdbreak,
)
from .tasks import (
    calculate_market_share,
    update_prediction_accuracy,
    generate_predictions,
)

logger = logging.getLogger(__name__)

# Custom signals
analytics_data_updated = Signal()
verification_status_changed = Signal()
prediction_accuracy_updated = Signal()
model_performance_alert = Signal()


@receiver(post_save, sender=SfrAnalytics)
def handle_sfr_analytics_created(sender, instance, created, **kwargs):
    """
    Handle SFR analytics data creation/update.
    """
    if created:
        logger.info(f"New SFR analytics record created: {instance.id}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='data_import',
            activity_date=instance.measurement_date,
            description=f"SFR analytics data imported for {instance.channel} - {instance.indicator}",
            metadata={
                'analytics_id': instance.id,
                'channel_id': instance.channel.id if instance.channel else None,
                'indicator': instance.indicator,
                'value': float(instance.value),
                'data_source': instance.data_source,
            }
        )
        
        # Trigger market share calculation if this is audience share data
        if instance.indicator == 'audience_share':
            # Delay the task to allow for batch processing
            calculate_market_share.apply_async(
                args=[instance.measurement_date.isoformat(), instance.region.id if instance.region else None],
                countdown=300  # Wait 5 minutes to batch multiple updates
            )
        
        # Invalidate related caches
        _invalidate_analytics_cache(instance)
        
        # Emit custom signal
        analytics_data_updated.send(
            sender=sender,
            instance=instance,
            action='created'
        )
    
    else:
        logger.info(f"SFR analytics record updated: {instance.id}")
        
        # Log activity for significant changes
        if hasattr(instance, '_original_value'):
            old_value = getattr(instance, '_original_value')
            if abs(instance.value - old_value) > (old_value * 0.1):  # 10% change
                ActivityLog.objects.create(
                    activity_type='data_update',
                    activity_date=instance.measurement_date,
                    description=f"Significant change in SFR analytics: {old_value} -> {instance.value}",
                    metadata={
                        'analytics_id': instance.id,
                        'old_value': float(old_value),
                        'new_value': float(instance.value),
                        'change_percentage': float((instance.value - old_value) / old_value * 100),
                    }
                )
        
        # Invalidate caches
        _invalidate_analytics_cache(instance)
        
        # Emit custom signal
        analytics_data_updated.send(
            sender=sender,
            instance=instance,
            action='updated'
        )


@receiver(pre_save, sender=SfrAnalytics)
def store_original_analytics_value(sender, instance, **kwargs):
    """
    Store original value before update for comparison.
    """
    if instance.pk:
        try:
            original = SfrAnalytics.objects.get(pk=instance.pk)
            instance._original_value = original.value
        except SfrAnalytics.DoesNotExist:
            pass


@receiver(post_save, sender=MarketShare)
def handle_market_share_updated(sender, instance, created, **kwargs):
    """
    Handle market share data creation/update.
    """
    if created:
        logger.info(f"New market share record created: {instance.id}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='calculation',
            activity_date=instance.measurement_date,
            description=f"Market share calculated for {instance.channel}: {instance.market_share_percentage:.2f}%",
            metadata={
                'market_share_id': instance.id,
                'channel_id': instance.channel.id if instance.channel else None,
                'market_share': float(instance.market_share_percentage),
                'ranking': instance.ranking,
            }
        )
        
        # Check for significant market share changes
        _check_market_share_alerts(instance)
        
        # Invalidate market share caches
        _invalidate_market_share_cache(instance)


@receiver(post_save, sender=VerificationRecord)
def handle_verification_record_updated(sender, instance, created, **kwargs):
    """
    Handle verification record creation/update.
    """
    if created:
        logger.info(f"New verification record created: {instance.id}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='verification',
            activity_date=instance.broadcast_date,
            description=f"Verification record created for traffic ID {instance.traffic_id}",
            metadata={
                'verification_id': instance.id,
                'traffic_id': instance.traffic_id,
                'status': instance.status,
                'network_name': instance.network_name,
                'zone_name': instance.zone_name,
            }
        )
    
    else:
        # Check if status changed
        if hasattr(instance, '_original_status') and instance._original_status != instance.status:
            logger.info(f"Verification status changed: {instance.id} - {instance._original_status} -> {instance.status}")
            
            # Log status change
            ActivityLog.objects.create(
                activity_type='status_change',
                activity_date=instance.broadcast_date,
                description=f"Verification status changed from {instance._original_status} to {instance.status}",
                metadata={
                    'verification_id': instance.id,
                    'old_status': instance._original_status,
                    'new_status': instance.status,
                    'verified_by': instance.verified_by.username if instance.verified_by else None,
                }
            )
            
            # Emit custom signal
            verification_status_changed.send(
                sender=sender,
                instance=instance,
                old_status=instance._original_status,
                new_status=instance.status
            )
            
            # Send notifications for failed verifications
            if instance.status == 'failed':
                _send_verification_failure_alert(instance)
    
    # Invalidate verification caches
    _invalidate_verification_cache(instance)


@receiver(pre_save, sender=VerificationRecord)
def store_original_verification_status(sender, instance, **kwargs):
    """
    Store original status before update for comparison.
    """
    if instance.pk:
        try:
            original = VerificationRecord.objects.get(pk=instance.pk)
            instance._original_status = original.status
        except VerificationRecord.DoesNotExist:
            pass


@receiver(post_save, sender=SfrPrediction)
def handle_sfr_prediction_updated(sender, instance, created, **kwargs):
    """
    Handle SFR prediction creation/update.
    """
    if created:
        logger.info(f"New SFR prediction created: {instance.id}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='prediction',
            activity_date=instance.prediction_date,
            description=f"SFR prediction generated for {instance.channel} - {instance.indicator}",
            metadata={
                'prediction_id': instance.id,
                'model_id': instance.model.id,
                'channel_id': instance.channel.id if instance.channel else None,
                'indicator': instance.indicator,
                'predicted_value': float(instance.predicted_value),
                'confidence_score': float(instance.confidence_score),
            }
        )
    
    else:
        # Check if actual value was added
        if (hasattr(instance, '_original_actual_value') and 
            instance._original_actual_value is None and 
            instance.actual_value is not None):
            
            logger.info(f"Actual value added to SFR prediction: {instance.id}")
            
            # Calculate accuracy
            accuracy = instance.accuracy
            
            # Log accuracy update
            ActivityLog.objects.create(
                activity_type='accuracy_update',
                activity_date=instance.prediction_date,
                description=f"Prediction accuracy calculated: {accuracy:.2f}%",
                metadata={
                    'prediction_id': instance.id,
                    'predicted_value': float(instance.predicted_value),
                    'actual_value': float(instance.actual_value),
                    'accuracy': float(accuracy),
                    'confidence_score': float(instance.confidence_score),
                }
            )
            
            # Emit custom signal
            prediction_accuracy_updated.send(
                sender=sender,
                instance=instance,
                accuracy=accuracy
            )
            
            # Check for poor predictions
            if accuracy < 70:  # Less than 70% accuracy
                _send_poor_prediction_alert(instance, accuracy)


@receiver(pre_save, sender=SfrPrediction)
def store_original_prediction_actual_value(sender, instance, **kwargs):
    """
    Store original actual value before update for comparison.
    """
    if instance.pk:
        try:
            original = SfrPrediction.objects.get(pk=instance.pk)
            instance._original_actual_value = original.actual_value
        except SfrPrediction.DoesNotExist:
            pass


@receiver(post_save, sender=AdbreakPrediction)
def handle_adbreak_prediction_updated(sender, instance, created, **kwargs):
    """
    Handle ad break prediction creation/update.
    """
    if created:
        logger.info(f"New ad break prediction created: {instance.id}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='prediction',
            activity_date=instance.prediction_date,
            description=f"Ad break prediction generated for {instance.channel}",
            metadata={
                'prediction_id': instance.id,
                'model_id': instance.model.id,
                'channel_id': instance.channel.id if instance.channel else None,
                'predicted_duration': instance.predicted_duration_seconds,
                'predicted_ad_count': instance.predicted_ad_count,
                'confidence_score': float(instance.confidence_score),
            }
        )


@receiver(post_save, sender=PredictionModel)
def handle_prediction_model_updated(sender, instance, created, **kwargs):
    """
    Handle prediction model creation/update.
    """
    if created:
        logger.info(f"New prediction model created: {instance.name}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='model_creation',
            activity_date=timezone.now().date(),
            description=f"Prediction model created: {instance.name}",
            metadata={
                'model_id': instance.id,
                'model_name': instance.name,
                'model_type': instance.model_type,
                'version': instance.version,
            }
        )
    
    else:
        # Check if model was activated/deactivated
        if hasattr(instance, '_original_is_active') and instance._original_is_active != instance.is_active:
            status = 'activated' if instance.is_active else 'deactivated'
            logger.info(f"Prediction model {status}: {instance.name}")
            
            # Log status change
            ActivityLog.objects.create(
                activity_type='model_status_change',
                activity_date=timezone.now().date(),
                description=f"Prediction model {status}: {instance.name}",
                metadata={
                    'model_id': instance.id,
                    'model_name': instance.name,
                    'old_status': instance._original_is_active,
                    'new_status': instance.is_active,
                }
            )
            
            # Generate new predictions if model was activated
            if instance.is_active:
                tomorrow = timezone.now().date() + timedelta(days=1)
                generate_predictions.apply_async(
                    args=[instance.id, tomorrow.isoformat(), instance.model_type],
                    countdown=60  # Wait 1 minute
                )


@receiver(pre_save, sender=PredictionModel)
def store_original_model_status(sender, instance, **kwargs):
    """
    Store original status before update for comparison.
    """
    if instance.pk:
        try:
            original = PredictionModel.objects.get(pk=instance.pk)
            instance._original_is_active = original.is_active
        except PredictionModel.DoesNotExist:
            pass


@receiver(post_save, sender=RealTimeAdbreak)
def handle_real_time_adbreak_updated(sender, instance, created, **kwargs):
    """
    Handle real-time ad break creation/update.
    """
    if created:
        logger.info(f"New real-time ad break created: {instance.id}")
        
        # Log activity
        ActivityLog.objects.create(
            activity_type='adbreak_start',
            activity_date=instance.start_time.date(),
            description=f"Ad break started on {instance.channel}",
            metadata={
                'adbreak_id': instance.id,
                'channel_id': instance.channel.id if instance.channel else None,
                'start_time': instance.start_time.isoformat(),
                'status': instance.status,
            }
        )
    
    else:
        # Check if status changed to completed
        if (hasattr(instance, '_original_status') and 
            instance._original_status != 'completed' and 
            instance.status == 'completed'):
            
            logger.info(f"Ad break completed: {instance.id}")
            
            # Log completion
            ActivityLog.objects.create(
                activity_type='adbreak_complete',
                activity_date=instance.start_time.date(),
                description=f"Ad break completed on {instance.channel}",
                metadata={
                    'adbreak_id': instance.id,
                    'channel_id': instance.channel.id if instance.channel else None,
                    'duration_seconds': instance.duration_seconds,
                    'ad_count': instance.ad_count,
                    'total_revenue': float(instance.total_revenue or 0),
                    'viewer_count': instance.viewer_count,
                }
            )
            
            # Update prediction accuracy
            update_prediction_accuracy.apply_async(countdown=60)


@receiver(pre_save, sender=RealTimeAdbreak)
def store_original_adbreak_status(sender, instance, **kwargs):
    """
    Store original status before update for comparison.
    """
    if instance.pk:
        try:
            original = RealTimeAdbreak.objects.get(pk=instance.pk)
            instance._original_status = original.status
        except RealTimeAdbreak.DoesNotExist:
            pass


@receiver(user_logged_in)
def handle_user_login(sender, request, user, **kwargs):
    """
    Log user login activity.
    """
    ActivityLog.objects.create(
        activity_type='user_login',
        activity_date=timezone.now().date(),
        user=user,
        description=f"User {user.username} logged in",
        ip_address=_get_client_ip(request),
        metadata={
            'user_id': user.id,
            'username': user.username,
            'user_agent': request.META.get('HTTP_USER_AGENT', ''),
        }
    )


@receiver(user_logged_out)
def handle_user_logout(sender, request, user, **kwargs):
    """
    Log user logout activity.
    """
    if user:
        ActivityLog.objects.create(
            activity_type='user_logout',
            activity_date=timezone.now().date(),
            user=user,
            description=f"User {user.username} logged out",
            ip_address=_get_client_ip(request),
            metadata={
                'user_id': user.id,
                'username': user.username,
            }
        )


# Helper functions

def _get_client_ip(request):
    """Get client IP address from request."""
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip


def _invalidate_analytics_cache(instance):
    """Invalidate analytics-related cache keys."""
    cache_keys = [
        f"analytics_summary_{instance.measurement_date}",
        f"channel_analytics_{instance.channel.id if instance.channel else 'none'}_{instance.measurement_date}",
        f"region_analytics_{instance.region.id if instance.region else 'none'}_{instance.measurement_date}",
        "analytics_dashboard_data",
    ]
    cache.delete_many(cache_keys)


def _invalidate_market_share_cache(instance):
    """Invalidate market share-related cache keys."""
    cache_keys = [
        f"market_share_{instance.measurement_date}",
        f"channel_market_share_{instance.channel.id if instance.channel else 'none'}_{instance.measurement_date}",
        "market_share_rankings",
    ]
    cache.delete_many(cache_keys)


def _invalidate_verification_cache(instance):
    """Invalidate verification-related cache keys."""
    cache_keys = [
        f"verification_summary_{instance.broadcast_date}",
        "verification_dashboard_data",
        "pending_verifications_count",
    ]
    cache.delete_many(cache_keys)


def _check_market_share_alerts(instance):
    """Check for market share alerts and notifications."""
    # Check for significant market share changes
    previous_data = MarketShare.objects.filter(
        channel=instance.channel,
        region=instance.region,
        target=instance.target,
        measurement_date__lt=instance.measurement_date
    ).order_by('-measurement_date').first()
    
    if previous_data:
        change = instance.market_share_percentage - previous_data.market_share_percentage
        if abs(change) > 5:  # 5% change threshold
            direction = 'increased' if change > 0 else 'decreased'
            ActivityLog.objects.create(
                activity_type='alert',
                activity_date=instance.measurement_date,
                description=f"Significant market share change: {instance.channel} {direction} by {abs(change):.2f}%",
                metadata={
                    'channel_id': instance.channel.id if instance.channel else None,
                    'old_market_share': float(previous_data.market_share_percentage),
                    'new_market_share': float(instance.market_share_percentage),
                    'change': float(change),
                    'alert_type': 'market_share_change',
                }
            )


def _send_verification_failure_alert(instance):
    """Send alert for verification failures."""
    # This would integrate with your notification system
    logger.warning(f"Verification failed for traffic ID {instance.traffic_id}")
    
    # Log alert
    ActivityLog.objects.create(
        activity_type='alert',
        activity_date=instance.broadcast_date,
        description=f"Verification failed for traffic ID {instance.traffic_id}",
        metadata={
            'verification_id': instance.id,
            'traffic_id': instance.traffic_id,
            'alert_type': 'verification_failure',
            'network_name': instance.network_name,
            'zone_name': instance.zone_name,
        }
    )


def _send_poor_prediction_alert(instance, accuracy):
    """Send alert for poor prediction accuracy."""
    logger.warning(f"Poor prediction accuracy: {accuracy:.2f}% for prediction {instance.id}")
    
    # Log alert
    ActivityLog.objects.create(
        activity_type='alert',
        activity_date=instance.prediction_date,
        description=f"Poor prediction accuracy: {accuracy:.2f}%",
        metadata={
            'prediction_id': instance.id,
            'model_id': instance.model.id,
            'accuracy': float(accuracy),
            'alert_type': 'poor_prediction',
            'threshold': 70.0,
        }
    )
    
    # Emit custom signal
    model_performance_alert.send(
        sender=SfrPrediction,
        instance=instance,
        accuracy=accuracy,
        alert_type='poor_prediction'
    )