"""
Notification analytics and effectiveness measurement service
"""
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.db.models import Q, Count, Avg, Sum, F, Case, When, IntegerField
from django.db.models.functions import TruncDate, TruncHour, TruncWeek, TruncMonth
from .models import Notification
from .notification_models import (
    NotificationDelivery, NotificationEscalation, NotificationBatch,
    NotificationPreference, NotificationRule, NotificationTemplate
)
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import json

User = get_user_model()


class NotificationAnalyticsService:
    """Service for analyzing notification delivery and effectiveness"""
    
    def __init__(self):
        self.default_period_days = 30
    
    def get_delivery_analytics(self, days: int = None, user_id: str = None, 
                             notification_type: str = None) -> Dict[str, Any]:
        """
        Get comprehensive delivery analytics
        
        Args:
            days: Number of days to analyze (default: 30)
            user_id: Filter by specific user
            notification_type: Filter by notification type
            
        Returns:
            Dictionary with delivery analytics
        """
        days = days or self.default_period_days
        start_date = timezone.now() - timedelta(days=days)
        
        # Base queryset
        deliveries = NotificationDelivery.objects.filter(created_at__gte=start_date)
        
        if user_id:
            deliveries = deliveries.filter(recipient_id=user_id)
        
        if notification_type:
            deliveries = deliveries.filter(notification__notification_type=notification_type)
        
        # Overall statistics
        total_deliveries = deliveries.count()
        
        # Status breakdown
        status_stats = deliveries.values('status').annotate(
            count=Count('id'),
            percentage=Count('id') * 100.0 / total_deliveries if total_deliveries > 0 else 0
        ).order_by('-count')
        
        # Channel breakdown
        channel_stats = deliveries.values('channel').annotate(
            total=Count('id'),
            sent=Count('id', filter=Q(status='sent')),
            delivered=Count('id', filter=Q(status='delivered')),
            read=Count('id', filter=Q(status='read')),
            failed=Count('id', filter=Q(status='failed')),
            delivery_rate=Case(
                When(total=0, then=0),
                default=Count('id', filter=Q(status__in=['delivered', 'read'])) * 100.0 / Count('id')
            ),
            read_rate=Case(
                When(total=0, then=0),
                default=Count('id', filter=Q(status='read')) * 100.0 / Count('id')
            )
        ).order_by('-total')
        
        # Time-based analysis
        daily_stats = deliveries.annotate(
            date=TruncDate('created_at')
        ).values('date').annotate(
            total=Count('id'),
            delivered=Count('id', filter=Q(status__in=['delivered', 'read'])),
            failed=Count('id', filter=Q(status='failed'))
        ).order_by('date')
        
        # Response time analysis (time from sent to read)
        read_deliveries = deliveries.filter(
            status='read',
            sent_at__isnull=False,
            read_at__isnull=False
        )
        
        avg_response_time = None
        if read_deliveries.exists():
            response_times = []
            for delivery in read_deliveries:
                if delivery.sent_at and delivery.read_at:
                    response_time = (delivery.read_at - delivery.sent_at).total_seconds() / 60  # minutes
                    response_times.append(response_time)
            
            if response_times:
                avg_response_time = sum(response_times) / len(response_times)
        
        return {
            'period_days': days,
            'total_deliveries': total_deliveries,
            'status_breakdown': list(status_stats),
            'channel_breakdown': list(channel_stats),
            'daily_trends': list(daily_stats),
            'average_response_time_minutes': avg_response_time,
            'overall_delivery_rate': (
                deliveries.filter(status__in=['delivered', 'read']).count() / total_deliveries * 100
                if total_deliveries > 0 else 0
            ),
            'overall_read_rate': (
                deliveries.filter(status='read').count() / total_deliveries * 100
                if total_deliveries > 0 else 0
            )
        }
    
    def get_notification_effectiveness(self, days: int = None) -> Dict[str, Any]:
        """
        Analyze notification effectiveness by type and content
        
        Args:
            days: Number of days to analyze
            
        Returns:
            Dictionary with effectiveness metrics
        """
        days = days or self.default_period_days
        start_date = timezone.now() - timedelta(days=days)
        
        notifications = Notification.objects.filter(created_at__gte=start_date)
        
        # Effectiveness by notification type
        type_effectiveness = notifications.values('notification_type').annotate(
            total=Count('id'),
            read=Count('id', filter=Q(read_at__isnull=False)),
            read_rate=Case(
                When(total=0, then=0),
                default=Count('id', filter=Q(read_at__isnull=False)) * 100.0 / Count('id')
            ),
            avg_read_time=Avg(
                Case(
                    When(read_at__isnull=False, then=F('read_at') - F('created_at')),
                    default=None
                )
            )
        ).order_by('-read_rate')
        
        # Priority effectiveness
        priority_effectiveness = notifications.values('priority').annotate(
            total=Count('id'),
            read=Count('id', filter=Q(read_at__isnull=False)),
            read_rate=Case(
                When(total=0, then=0),
                default=Count('id', filter=Q(read_at__isnull=False)) * 100.0 / Count('id')
            )
        ).order_by('-read_rate')
        
        # Action-required notifications effectiveness
        action_required_stats = notifications.filter(action_required=True).aggregate(
            total=Count('id'),
            read=Count('id', filter=Q(read_at__isnull=False)),
            with_action_url=Count('id', filter=Q(action_url__isnull=False))
        )
        
        if action_required_stats['total'] > 0:
            action_required_stats['read_rate'] = (
                action_required_stats['read'] / action_required_stats['total'] * 100
            )
        else:
            action_required_stats['read_rate'] = 0
        
        # Template effectiveness (if templates are used)
        template_effectiveness = []
        templates = NotificationTemplate.objects.filter(is_active=True)
        
        for template in templates:
            template_notifications = notifications.filter(notification_type=template.notification_type)
            if template_notifications.exists():
                total = template_notifications.count()
                read = template_notifications.filter(read_at__isnull=False).count()
                template_effectiveness.append({
                    'template_name': template.name,
                    'notification_type': template.notification_type,
                    'channel': template.channel,
                    'total': total,
                    'read': read,
                    'read_rate': (read / total * 100) if total > 0 else 0
                })
        
        return {
            'period_days': days,
            'type_effectiveness': list(type_effectiveness),
            'priority_effectiveness': list(priority_effectiveness),
            'action_required_stats': action_required_stats,
            'template_effectiveness': template_effectiveness
        }
    
    def get_user_engagement_analytics(self, days: int = None) -> Dict[str, Any]:
        """
        Analyze user engagement with notifications
        
        Args:
            days: Number of days to analyze
            
        Returns:
            Dictionary with user engagement metrics
        """
        days = days or self.default_period_days
        start_date = timezone.now() - timedelta(days=days)
        
        # User engagement by role
        role_engagement = Notification.objects.filter(
            created_at__gte=start_date,
            user__isnull=False
        ).values('user__role').annotate(
            total_notifications=Count('id'),
            read_notifications=Count('id', filter=Q(read_at__isnull=False)),
            unread_notifications=Count('id', filter=Q(read_at__isnull=True)),
            read_rate=Case(
                When(total_notifications=0, then=0),
                default=Count('id', filter=Q(read_at__isnull=False)) * 100.0 / Count('id')
            ),
            avg_read_time=Avg(
                Case(
                    When(read_at__isnull=False, then=F('read_at') - F('created_at')),
                    default=None
                )
            )
        ).order_by('-read_rate')
        
        # Most engaged users
        top_users = Notification.objects.filter(
            created_at__gte=start_date,
            user__isnull=False
        ).values(
            'user__id', 'user__first_name', 'user__last_name', 'user__role'
        ).annotate(
            total_notifications=Count('id'),
            read_notifications=Count('id', filter=Q(read_at__isnull=False)),
            read_rate=Case(
                When(total_notifications=0, then=0),
                default=Count('id', filter=Q(read_at__isnull=False)) * 100.0 / Count('id')
            )
        ).filter(total_notifications__gte=5).order_by('-read_rate')[:10]
        
        # Least engaged users (potential issues)
        low_engagement_users = Notification.objects.filter(
            created_at__gte=start_date,
            user__isnull=False
        ).values(
            'user__id', 'user__first_name', 'user__last_name', 'user__role'
        ).annotate(
            total_notifications=Count('id'),
            read_notifications=Count('id', filter=Q(read_at__isnull=False)),
            read_rate=Case(
                When(total_notifications=0, then=0),
                default=Count('id', filter=Q(read_at__isnull=False)) * 100.0 / Count('id')
            )
        ).filter(total_notifications__gte=5, read_rate__lt=30).order_by('read_rate')[:10]
        
        # Notification preferences adoption
        total_users = User.objects.filter(is_active=True).count()
        users_with_preferences = NotificationPreference.objects.values('user').distinct().count()
        preference_adoption_rate = (users_with_preferences / total_users * 100) if total_users > 0 else 0
        
        return {
            'period_days': days,
            'role_engagement': list(role_engagement),
            'top_engaged_users': list(top_users),
            'low_engagement_users': list(low_engagement_users),
            'preference_adoption_rate': preference_adoption_rate,
            'total_active_users': total_users,
            'users_with_preferences': users_with_preferences
        }
    
    def get_escalation_analytics(self, days: int = None) -> Dict[str, Any]:
        """
        Analyze notification escalation patterns and effectiveness
        
        Args:
            days: Number of days to analyze
            
        Returns:
            Dictionary with escalation analytics
        """
        days = days or self.default_period_days
        start_date = timezone.now() - timedelta(days=days)
        
        escalations = NotificationEscalation.objects.filter(escalated_at__gte=start_date)
        
        # Overall escalation statistics
        total_escalations = escalations.count()
        acknowledged_escalations = escalations.filter(acknowledged_at__isnull=False).count()
        resolved_escalations = escalations.filter(resolved_at__isnull=False).count()
        
        # Escalation by rule
        rule_escalations = escalations.values(
            'rule__name', 'rule__notification_type'
        ).annotate(
            total=Count('id'),
            acknowledged=Count('id', filter=Q(acknowledged_at__isnull=False)),
            resolved=Count('id', filter=Q(resolved_at__isnull=False)),
            avg_acknowledgment_time=Avg(
                Case(
                    When(acknowledged_at__isnull=False, then=F('acknowledged_at') - F('escalated_at')),
                    default=None
                )
            ),
            avg_resolution_time=Avg(
                Case(
                    When(resolved_at__isnull=False, then=F('resolved_at') - F('escalated_at')),
                    default=None
                )
            )
        ).order_by('-total')
        
        # Escalation by user role
        role_escalations = escalations.values('escalated_to__role').annotate(
            total=Count('id'),
            acknowledged=Count('id', filter=Q(acknowledged_at__isnull=False)),
            resolved=Count('id', filter=Q(resolved_at__isnull=False)),
            acknowledgment_rate=Case(
                When(total=0, then=0),
                default=Count('id', filter=Q(acknowledged_at__isnull=False)) * 100.0 / Count('id')
            ),
            resolution_rate=Case(
                When(total=0, then=0),
                default=Count('id', filter=Q(resolved_at__isnull=False)) * 100.0 / Count('id')
            )
        ).order_by('-total')
        
        # Time-based escalation trends
        daily_escalations = escalations.annotate(
            date=TruncDate('escalated_at')
        ).values('date').annotate(
            total=Count('id'),
            acknowledged=Count('id', filter=Q(acknowledged_at__isnull=False)),
            resolved=Count('id', filter=Q(resolved_at__isnull=False))
        ).order_by('date')
        
        return {
            'period_days': days,
            'total_escalations': total_escalations,
            'acknowledged_escalations': acknowledged_escalations,
            'resolved_escalations': resolved_escalations,
            'acknowledgment_rate': (acknowledged_escalations / total_escalations * 100) if total_escalations > 0 else 0,
            'resolution_rate': (resolved_escalations / total_escalations * 100) if total_escalations > 0 else 0,
            'rule_escalations': list(rule_escalations),
            'role_escalations': list(role_escalations),
            'daily_trends': list(daily_escalations)
        }
    
    def get_batch_processing_analytics(self, days: int = None) -> Dict[str, Any]:
        """
        Analyze batch notification processing performance
        
        Args:
            days: Number of days to analyze
            
        Returns:
            Dictionary with batch processing analytics
        """
        days = days or self.default_period_days
        start_date = timezone.now() - timedelta(days=days)
        
        batches = NotificationBatch.objects.filter(created_at__gte=start_date)
        
        # Overall batch statistics
        total_batches = batches.count()
        completed_batches = batches.filter(status='completed').count()
        failed_batches = batches.filter(status='failed').count()
        
        # Batch status breakdown
        status_breakdown = batches.values('status').annotate(
            count=Count('id'),
            percentage=Count('id') * 100.0 / total_batches if total_batches > 0 else 0
        ).order_by('-count')
        
        # Processing performance
        completed_batch_stats = batches.filter(
            status='completed',
            started_at__isnull=False,
            completed_at__isnull=False
        ).aggregate(
            avg_processing_time=Avg(F('completed_at') - F('started_at')),
            avg_recipients=Avg('total_recipients'),
            avg_success_rate=Avg(
                Case(
                    When(total_recipients=0, then=0),
                    default=F('sent_count') * 100.0 / F('total_recipients')
                )
            )
        )
        
        # Batch performance by notification type
        type_performance = batches.filter(status='completed').values('notification_type').annotate(
            total_batches=Count('id'),
            total_recipients=Sum('total_recipients'),
            total_sent=Sum('sent_count'),
            total_failed=Sum('failed_count'),
            avg_success_rate=Avg(
                Case(
                    When(total_recipients=0, then=0),
                    default=F('sent_count') * 100.0 / F('total_recipients')
                )
            )
        ).order_by('-total_batches')
        
        # Recent batch performance
        recent_batches = batches.order_by('-created_at')[:10].values(
            'id', 'name', 'notification_type', 'status', 'total_recipients',
            'sent_count', 'failed_count', 'created_at', 'completed_at'
        )
        
        return {
            'period_days': days,
            'total_batches': total_batches,
            'completed_batches': completed_batches,
            'failed_batches': failed_batches,
            'success_rate': (completed_batches / total_batches * 100) if total_batches > 0 else 0,
            'status_breakdown': list(status_breakdown),
            'performance_stats': completed_batch_stats,
            'type_performance': list(type_performance),
            'recent_batches': list(recent_batches)
        }
    
    def get_comprehensive_report(self, days: int = None) -> Dict[str, Any]:
        """
        Generate comprehensive notification analytics report
        
        Args:
            days: Number of days to analyze
            
        Returns:
            Dictionary with comprehensive analytics
        """
        days = days or self.default_period_days
        
        return {
            'report_period_days': days,
            'generated_at': timezone.now().isoformat(),
            'delivery_analytics': self.get_delivery_analytics(days),
            'effectiveness_analytics': self.get_notification_effectiveness(days),
            'user_engagement': self.get_user_engagement_analytics(days),
            'escalation_analytics': self.get_escalation_analytics(days),
            'batch_processing': self.get_batch_processing_analytics(days)
        }
    
    def get_optimization_recommendations(self, days: int = None) -> List[Dict[str, Any]]:
        """
        Generate optimization recommendations based on analytics
        
        Args:
            days: Number of days to analyze
            
        Returns:
            List of optimization recommendations
        """
        days = days or self.default_period_days
        recommendations = []
        
        # Get analytics data
        delivery_analytics = self.get_delivery_analytics(days)
        effectiveness = self.get_notification_effectiveness(days)
        engagement = self.get_user_engagement_analytics(days)
        
        # Low delivery rate recommendations
        if delivery_analytics['overall_delivery_rate'] < 80:
            recommendations.append({
                'type': 'delivery_improvement',
                'priority': 'high',
                'title': 'Improve Delivery Rate',
                'description': f"Overall delivery rate is {delivery_analytics['overall_delivery_rate']:.1f}%. Consider reviewing failed deliveries and channel configurations.",
                'action': 'Review failed deliveries and channel settings'
            })
        
        # Low read rate recommendations
        if delivery_analytics['overall_read_rate'] < 50:
            recommendations.append({
                'type': 'engagement_improvement',
                'priority': 'medium',
                'title': 'Improve Read Rate',
                'description': f"Overall read rate is {delivery_analytics['overall_read_rate']:.1f}%. Consider improving notification content and timing.",
                'action': 'Review notification templates and timing'
            })
        
        # Low preference adoption
        if engagement['preference_adoption_rate'] < 50:
            recommendations.append({
                'type': 'preference_adoption',
                'priority': 'medium',
                'title': 'Increase Preference Adoption',
                'description': f"Only {engagement['preference_adoption_rate']:.1f}% of users have set notification preferences. Consider promoting preference management.",
                'action': 'Promote notification preference management to users'
            })
        
        # Channel-specific recommendations
        for channel_stat in delivery_analytics['channel_breakdown']:
            if channel_stat['delivery_rate'] < 70:
                recommendations.append({
                    'type': 'channel_optimization',
                    'priority': 'medium',
                    'title': f'Optimize {channel_stat["channel"].title()} Channel',
                    'description': f"{channel_stat['channel'].title()} channel has {channel_stat['delivery_rate']:.1f}% delivery rate.",
                    'action': f'Review {channel_stat["channel"]} channel configuration and delivery issues'
                })
        
        # Low engagement users
        if len(engagement['low_engagement_users']) > 0:
            recommendations.append({
                'type': 'user_engagement',
                'priority': 'low',
                'title': 'Address Low User Engagement',
                'description': f"{len(engagement['low_engagement_users'])} users have low notification engagement rates.",
                'action': 'Reach out to low-engagement users to understand notification preferences'
            })
        
        return recommendations