"""
SEO Monitoring Service

This module provides services for monitoring SEO metrics and triggering alerts.
"""

from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
from django.utils import timezone
from django.core.cache import cache
from django.db.models import Q, Avg, Sum, Count

from .seo_monitoring import (
    KeywordRanking,
    OrganicTraffic,
    ConversionMetric,
    CoreWebVital,
    CrawlError,
    SEOAlert,
)
from .seo_dashboard_service import SEODashboardService
from .seo_reporting import SEOReportEmailer


class SEOMonitoringService:
    """Service for monitoring SEO metrics and managing alerts."""
    
    # Cache keys
    CACHE_KEY_MONITORING_STATE = 'seo_monitoring:state'
    CACHE_TIMEOUT = 3600  # 1 hour
    
    # Alert thresholds (can be configured)
    RANKING_DROP_THRESHOLD = 5  # positions
    TRAFFIC_DROP_THRESHOLD = 20.0  # percentage
    CRAWL_ERROR_THRESHOLD = 10  # number of new errors
    CWV_LCP_THRESHOLD = 2.5  # seconds
    CWV_FID_THRESHOLD = 100  # milliseconds
    CWV_CLS_THRESHOLD = 0.1  # score
    
    @staticmethod
    def monitor_ranking_changes(
        threshold: int = RANKING_DROP_THRESHOLD,
        lookback_days: int = 1,
    ) -> List[SEOAlert]:
        """
        Monitor keyword ranking changes and create alerts for significant drops.
        
        Args:
            threshold: Minimum rank drop to trigger alert
            lookback_days: Number of days to look back
            
        Returns:
            List of created alerts
        """
        alerts = []
        
        try:
            end_date = timezone.now().date()
            start_date = end_date - timedelta(days=lookback_days)
            
            # Get latest rankings
            latest_rankings = KeywordRanking.objects.filter(
                tracked_date__date=end_date
            ).values('keyword', 'current_rank', 'search_volume', 'target_url')
            
            for latest in latest_rankings:
                keyword = latest['keyword']
                
                # Get previous ranking
                previous = KeywordRanking.objects.filter(
                    keyword=keyword,
                    tracked_date__date__lt=end_date,
                    tracked_date__date__gte=start_date
                ).order_by('-tracked_date').first()
                
                if previous:
                    rank_change = previous.current_rank - latest['current_rank']
                    
                    # Check if drop exceeds threshold
                    if rank_change < -threshold:
                        severity = 'critical' if rank_change < -20 else 'high'
                        
                        # Check if alert already exists
                        existing_alert = SEOAlert.objects.filter(
                            alert_type='ranking_drop',
                            affected_keyword=keyword,
                            is_active=True,
                        ).first()
                        
                        if not existing_alert:
                            alert = SEODashboardService.create_alert(
                                alert_type='ranking_drop',
                                severity=severity,
                                title=f"Ranking drop for '{keyword}'",
                                description=f"Keyword '{keyword}' dropped from rank {previous.current_rank} to {latest['current_rank']} ({abs(rank_change)} positions)",
                                affected_keyword=keyword,
                                affected_url=latest['target_url'],
                                metric_value=latest['current_rank'],
                                threshold_value=previous.current_rank,
                            )
                            alerts.append(alert)
        
        except Exception as e:
            print(f"Error monitoring ranking changes: {str(e)}")
        
        return alerts
    
    @staticmethod
    def monitor_traffic_changes(
        threshold_percentage: float = TRAFFIC_DROP_THRESHOLD,
        lookback_days: int = 1,
    ) -> List[SEOAlert]:
        """
        Monitor organic traffic changes and create alerts for significant drops.
        
        Args:
            threshold_percentage: Percentage drop to trigger alert
            lookback_days: Number of days to look back
            
        Returns:
            List of created alerts
        """
        alerts = []
        
        try:
            today = timezone.now().date()
            yesterday = today - timedelta(days=lookback_days)
            
            today_traffic = OrganicTraffic.objects.filter(date=today).first()
            yesterday_traffic = OrganicTraffic.objects.filter(date=yesterday).first()
            
            if today_traffic and yesterday_traffic and yesterday_traffic.sessions > 0:
                percentage_change = (
                    (today_traffic.sessions - yesterday_traffic.sessions) / 
                    yesterday_traffic.sessions * 100
                )
                
                # Check if drop exceeds threshold
                if percentage_change < -threshold_percentage:
                    severity = 'critical' if percentage_change < -50 else 'high'
                    
                    # Check if alert already exists
                    existing_alert = SEOAlert.objects.filter(
                        alert_type='traffic_drop',
                        is_active=True,
                    ).first()
                    
                    if not existing_alert:
                        alert = SEODashboardService.create_alert(
                            alert_type='traffic_drop',
                            severity=severity,
                            title='Significant organic traffic drop detected',
                            description=f'Organic traffic dropped by {abs(percentage_change):.1f}% compared to {lookback_days} day(s) ago. Sessions: {yesterday_traffic.sessions:,} → {today_traffic.sessions:,}',
                            metric_value=today_traffic.sessions,
                            threshold_value=yesterday_traffic.sessions,
                        )
                        alerts.append(alert)
        
        except Exception as e:
            print(f"Error monitoring traffic changes: {str(e)}")
        
        return alerts
    
    @staticmethod
    def monitor_crawl_errors(
        threshold: int = CRAWL_ERROR_THRESHOLD,
        lookback_days: int = 1,
    ) -> List[SEOAlert]:
        """
        Monitor crawl errors and create alerts for new errors.
        
        Args:
            threshold: Number of new errors to trigger alert
            lookback_days: Number of days to look back
            
        Returns:
            List of created alerts
        """
        alerts = []
        
        try:
            cutoff_date = timezone.now() - timedelta(days=lookback_days)
            
            # Count new errors
            new_errors = CrawlError.objects.filter(
                first_detected__gte=cutoff_date,
                is_resolved=False
            ).count()
            
            # Check if new errors exceed threshold
            if new_errors >= threshold:
                # Check if alert already exists
                existing_alert = SEOAlert.objects.filter(
                    alert_type='crawl_error',
                    is_active=True,
                ).first()
                
                if not existing_alert:
                    alert = SEODashboardService.create_alert(
                        alert_type='crawl_error',
                        severity='high',
                        title=f'{new_errors} new crawl errors detected',
                        description=f'{new_errors} new crawl errors have been detected in the last {lookback_days} day(s). Please review and fix these errors.',
                        metric_value=new_errors,
                        threshold_value=threshold,
                    )
                    alerts.append(alert)
        
        except Exception as e:
            print(f"Error monitoring crawl errors: {str(e)}")
        
        return alerts
    
    @staticmethod
    def monitor_core_web_vitals(
        lcp_threshold: float = CWV_LCP_THRESHOLD,
        fid_threshold: float = CWV_FID_THRESHOLD,
        cls_threshold: float = CWV_CLS_THRESHOLD,
    ) -> List[SEOAlert]:
        """
        Monitor Core Web Vitals and create alerts for poor performance.
        
        Args:
            lcp_threshold: LCP threshold in seconds
            fid_threshold: FID threshold in milliseconds
            cls_threshold: CLS threshold score
            
        Returns:
            List of created alerts
        """
        alerts = []
        
        try:
            today = timezone.now().date()
            
            # Get today's Core Web Vitals
            vitals = CoreWebVital.objects.filter(date=today)
            
            for vital in vitals:
                # Check LCP
                if vital.lcp > lcp_threshold and vital.lcp_status == 'poor':
                    existing_alert = SEOAlert.objects.filter(
                        alert_type='core_web_vitals',
                        affected_url=vital.page_url,
                        is_active=True,
                    ).filter(Q(description__contains='LCP')).first()
                    
                    if not existing_alert:
                        alert = SEODashboardService.create_alert(
                            alert_type='core_web_vitals',
                            severity='high',
                            title=f'Poor LCP on {vital.page_url}',
                            description=f'LCP is {vital.lcp:.2f}s (threshold: {lcp_threshold}s). This may impact user experience and rankings.',
                            affected_url=vital.page_url,
                            metric_value=vital.lcp,
                            threshold_value=lcp_threshold,
                        )
                        alerts.append(alert)
                
                # Check FID
                if vital.fid > fid_threshold and vital.fid_status == 'poor':
                    existing_alert = SEOAlert.objects.filter(
                        alert_type='core_web_vitals',
                        affected_url=vital.page_url,
                        is_active=True,
                    ).filter(Q(description__contains='FID')).first()
                    
                    if not existing_alert:
                        alert = SEODashboardService.create_alert(
                            alert_type='core_web_vitals',
                            severity='medium',
                            title=f'Poor FID on {vital.page_url}',
                            description=f'FID is {vital.fid:.0f}ms (threshold: {fid_threshold}ms). This may impact user experience.',
                            affected_url=vital.page_url,
                            metric_value=vital.fid,
                            threshold_value=fid_threshold,
                        )
                        alerts.append(alert)
                
                # Check CLS
                if vital.cls > cls_threshold and vital.cls_status == 'poor':
                    existing_alert = SEOAlert.objects.filter(
                        alert_type='core_web_vitals',
                        affected_url=vital.page_url,
                        is_active=True,
                    ).filter(Q(description__contains='CLS')).first()
                    
                    if not existing_alert:
                        alert = SEODashboardService.create_alert(
                            alert_type='core_web_vitals',
                            severity='medium',
                            title=f'Poor CLS on {vital.page_url}',
                            description=f'CLS is {vital.cls:.2f} (threshold: {cls_threshold}). This may impact user experience.',
                            affected_url=vital.page_url,
                            metric_value=vital.cls,
                            threshold_value=cls_threshold,
                        )
                        alerts.append(alert)
        
        except Exception as e:
            print(f"Error monitoring Core Web Vitals: {str(e)}")
        
        return alerts
    
    @staticmethod
    def run_full_monitoring_check(
        ranking_threshold: int = RANKING_DROP_THRESHOLD,
        traffic_threshold: float = TRAFFIC_DROP_THRESHOLD,
        crawl_error_threshold: int = CRAWL_ERROR_THRESHOLD,
    ) -> Dict:
        """
        Run a full monitoring check and return all alerts created.
        
        Args:
            ranking_threshold: Ranking drop threshold
            traffic_threshold: Traffic drop threshold
            crawl_error_threshold: Crawl error threshold
            
        Returns:
            Dictionary with monitoring results
        """
        results = {
            'timestamp': timezone.now().isoformat(),
            'ranking_alerts': [],
            'traffic_alerts': [],
            'crawl_error_alerts': [],
            'cwv_alerts': [],
            'total_alerts': 0,
        }
        
        try:
            # Check ranking changes
            ranking_alerts = SEOMonitoringService.monitor_ranking_changes(
                threshold=ranking_threshold
            )
            results['ranking_alerts'] = [
                {
                    'id': alert.id,
                    'title': alert.title,
                    'severity': alert.severity,
                }
                for alert in ranking_alerts
            ]
            
            # Check traffic changes
            traffic_alerts = SEOMonitoringService.monitor_traffic_changes(
                threshold_percentage=traffic_threshold
            )
            results['traffic_alerts'] = [
                {
                    'id': alert.id,
                    'title': alert.title,
                    'severity': alert.severity,
                }
                for alert in traffic_alerts
            ]
            
            # Check crawl errors
            crawl_error_alerts = SEOMonitoringService.monitor_crawl_errors(
                threshold=crawl_error_threshold
            )
            results['crawl_error_alerts'] = [
                {
                    'id': alert.id,
                    'title': alert.title,
                    'severity': alert.severity,
                }
                for alert in crawl_error_alerts
            ]
            
            # Check Core Web Vitals
            cwv_alerts = SEOMonitoringService.monitor_core_web_vitals()
            results['cwv_alerts'] = [
                {
                    'id': alert.id,
                    'title': alert.title,
                    'severity': alert.severity,
                }
                for alert in cwv_alerts
            ]
            
            # Calculate total
            results['total_alerts'] = (
                len(ranking_alerts) +
                len(traffic_alerts) +
                len(crawl_error_alerts) +
                len(cwv_alerts)
            )
        
        except Exception as e:
            results['error'] = str(e)
        
        return results
    
    @staticmethod
    def get_monitoring_status() -> Dict:
        """
        Get the current monitoring status.
        
        Returns:
            Dictionary with monitoring status
        """
        active_alerts = SEOAlert.objects.filter(is_active=True)
        
        return {
            'total_active_alerts': active_alerts.count(),
            'critical_alerts': active_alerts.filter(severity='critical').count(),
            'high_alerts': active_alerts.filter(severity='high').count(),
            'medium_alerts': active_alerts.filter(severity='medium').count(),
            'low_alerts': active_alerts.filter(severity='low').count(),
            'last_check': timezone.now().isoformat(),
        }
    
    @staticmethod
    def acknowledge_alert(alert_id: int) -> bool:
        """
        Acknowledge an alert.
        
        Args:
            alert_id: ID of the alert to acknowledge
            
        Returns:
            True if acknowledged successfully
        """
        try:
            alert = SEOAlert.objects.get(id=alert_id)
            alert.acknowledge()
            return True
        except SEOAlert.DoesNotExist:
            return False
    
    @staticmethod
    def resolve_alert(alert_id: int) -> bool:
        """
        Resolve an alert.
        
        Args:
            alert_id: ID of the alert to resolve
            
        Returns:
            True if resolved successfully
        """
        try:
            alert = SEOAlert.objects.get(id=alert_id)
            alert.resolve()
            return True
        except SEOAlert.DoesNotExist:
            return False
