"""
Automated Monitoring Module for Meta Ads MCP Server

This module provides real-time monitoring and alerting tools for agencies
managing thousands of campaigns. Enables automated detection of spend issues,
performance drops, and delivery problems.
"""

import asyncio
from typing import Dict, List, Any, Optional
from datetime import datetime
import statistics

from .meta_client import MetaClient
from .schemas import (
    RealTimeSpendMonitorRequest,
    RealTimeSpendMonitorResponse,
    PerformanceDropDetectorRequest,
    PerformanceDropDetectorResponse,
    CreativeFatigueMonitorRequest,
    CreativeFatigueMonitorResponse,
    LearningPhaseTrackerRequest,
    LearningPhaseTrackerResponse,
    BudgetDepletionPredictorRequest,
    BudgetDepletionPredictorResponse,
    AuctionCompetitionMonitorRequest,
    AuctionCompetitionMonitorResponse,
    DeliveryIssueScannerRequest,
    DeliveryIssueScannerResponse,
)
from .logging import get_logger

logger = get_logger(__name__)


class AutomatedMonitoringEngine:
    """Handles automated monitoring and alerting for campaign management at scale"""
    
    def __init__(self, client: MetaClient):
        self.client = client
    
    async def real_time_spend_monitor(
        self,
        request: RealTimeSpendMonitorRequest
    ) -> RealTimeSpendMonitorResponse:
        """
        Track real-time spend vs daily budget pacing.
        Provides overspend/underspend alerts and end-of-day projections.
        """
        try:
            campaigns = await self._get_campaigns(request.campaign_ids, request.ad_account_id)
            
            # Get today's spend
            today_insights = await self._fetch_today_insights(campaigns)
            
            # Calculate pacing metrics
            pacing_analysis = self._analyze_budget_pacing(campaigns, today_insights)
            
            # Generate alerts
            alerts = self._generate_pacing_alerts(pacing_analysis, request.thresholds)
            
            # Project end-of-day spend
            eod_projections = self._project_eod_spend(pacing_analysis)
            
            return RealTimeSpendMonitorResponse(
                campaigns_monitored=len(campaigns),
                total_daily_budget=sum(c.get("daily_budget", 0) for c in campaigns),
                current_spend=sum(p["current_spend"] for p in pacing_analysis),
                pacing_status=self._overall_pacing_status(pacing_analysis),
                overspend_alerts=[a for a in alerts if a["type"] == "overspend"],
                underspend_alerts=[a for a in alerts if a["type"] == "underspend"],
                eod_projections=eod_projections,
                recommendations=self._generate_pacing_recommendations(pacing_analysis),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Spend monitor error: {str(e)}")
            raise
    
    async def performance_drop_detector(
        self,
        request: PerformanceDropDetectorRequest
    ) -> PerformanceDropDetectorResponse:
        """
        Detect ROAS/CPA degradation with week-over-week comparison.
        Provides root cause hypotheses and recommended actions.
        """
        try:
            campaigns = await self._get_campaigns(request.campaign_ids, request.ad_account_id)
            
            # Fetch current and previous week data
            current_insights, previous_insights = await asyncio.gather(
                self._fetch_period_insights(campaigns, request.time_range),
                self._fetch_period_insights(campaigns, request.comparison_period)
            )
            
            # Detect performance drops
            drops = self._detect_drops(current_insights, previous_insights, request.thresholds)
            
            # Generate root cause hypotheses
            hypotheses = self._generate_root_cause_hypotheses(drops, current_insights)
            
            # Recommend actions
            actions = self._recommend_corrective_actions(drops, hypotheses)
            
            return PerformanceDropDetectorResponse(
                campaigns_analyzed=len(campaigns),
                drops_detected=len(drops),
                critical_drops=[d for d in drops if d["severity"] == "critical"],
                degradation_summary=self._summarize_degradation(drops),
                root_cause_hypotheses=hypotheses,
                recommended_actions=actions,
                affected_budget_usd=sum(d.get("daily_budget", 0) for d in drops),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Performance drop detector error: {str(e)}")
            raise
    
    async def creative_fatigue_monitor(
        self,
        request: CreativeFatigueMonitorRequest
    ) -> CreativeFatigueMonitorResponse:
        """
        Monitor declining CTR/engagement as indicator of creative fatigue.
        Tracks ad age and provides refresh timing recommendations.
        """
        try:
            # Get ads instead of campaigns
            ads = await self._get_ads(request.ad_ids, request.ad_account_id)
            
            # Fetch ad insights with time series
            ad_insights = await self._fetch_ad_time_series(ads, request.time_range)
            
            # Detect fatigue indicators
            fatigue_analysis = self._analyze_creative_fatigue(ad_insights)
            
            # Calculate frequency vs performance
            frequency_curves = self._calculate_frequency_curves(ad_insights)
            
            # Generate refresh recommendations
            refresh_recommendations = self._generate_refresh_recommendations(fatigue_analysis)
            
            return CreativeFatigueMonitorResponse(
                ads_analyzed=len(ads),
                fatigued_ads=len([a for a in fatigue_analysis if a["is_fatigued"]]),
                fatigue_indicators=fatigue_analysis,
                frequency_performance_curves=frequency_curves,
                refresh_timing_recommendations=refresh_recommendations,
                creative_rotation_status=self._assess_rotation_status(fatigue_analysis),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Creative fatigue monitor error: {str(e)}")
            raise
    
    async def learning_phase_tracker(
        self,
        request: LearningPhaseTrackerRequest
    ) -> LearningPhaseTrackerResponse:
        """
        Track campaigns stuck in learning phase.
        Provides exit/re-entry tracking and acceleration recommendations.
        """
        try:
            campaigns = await self._get_campaigns(request.campaign_ids, request.ad_account_id)
            
            # Get delivery info with learning phase status
            delivery_info = await self._fetch_delivery_info(campaigns)
            
            # Analyze learning phase status
            learning_analysis = self._analyze_learning_phase(delivery_info)
            
            # Calculate learning velocity
            velocity_scores = self._calculate_learning_velocity(learning_analysis)
            
            # Generate acceleration recommendations
            acceleration_tips = self._generate_acceleration_recommendations(learning_analysis)
            
            return LearningPhaseTrackerResponse(
                campaigns_tracked=len(campaigns),
                in_learning_phase=len([learning_item for learning_item in learning_analysis if learning_item["in_learning"]]),
                stuck_campaigns=[learning_item for learning_item in learning_analysis if learning_item["is_stuck"]],
                exit_tracking=self._track_learning_exits(learning_analysis),
                learning_velocity_scores=velocity_scores,
                acceleration_recommendations=acceleration_tips,
                budget_adjustment_suggestions=self._suggest_budget_adjustments(learning_analysis),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Learning phase tracker error: {str(e)}")
            raise
    
    async def budget_depletion_predictor(
        self,
        request: BudgetDepletionPredictorRequest
    ) -> BudgetDepletionPredictorResponse:
        """
        Predict time until budget exhaustion based on current burn rate.
        Provides refill recommendations and opportunity loss calculations.
        """
        try:
            campaigns = await self._get_campaigns(request.campaign_ids, request.ad_account_id)
            
            # Get current spend and budget data
            spend_data = await self._fetch_spend_data(campaigns, request.time_range)
            
            # Calculate burn rates
            burn_rates = self._calculate_burn_rates(spend_data)
            
            # Predict depletion times
            depletion_predictions = self._predict_depletion(campaigns, burn_rates)
            
            # Calculate opportunity loss
            opportunity_loss = self._calculate_opportunity_loss(depletion_predictions)
            
            # Generate refill recommendations
            refill_recommendations = self._generate_refill_recommendations(depletion_predictions)
            
            return BudgetDepletionPredictorResponse(
                campaigns_analyzed=len(campaigns),
                at_risk_campaigns=[d for d in depletion_predictions if d["days_until_depletion"] < 3],
                depletion_predictions=depletion_predictions,
                burn_rate_analysis=burn_rates,
                opportunity_loss_usd=opportunity_loss,
                refill_recommendations=refill_recommendations,
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Budget depletion predictor error: {str(e)}")
            raise
    
    async def auction_competition_monitor(
        self,
        request: AuctionCompetitionMonitorRequest
    ) -> AuctionCompetitionMonitorResponse:
        """
        Monitor CPM inflation and auction competition trends.
        Provides bid adjustment recommendations based on competitive pressure.
        """
        try:
            campaigns = await self._get_campaigns(request.campaign_ids, request.ad_account_id)
            
            # Fetch CPM and auction data
            current_auction_data, historical_auction_data = await asyncio.gather(
                self._fetch_auction_insights(campaigns, request.time_range),
                self._fetch_auction_insights(campaigns, request.comparison_period)
            )
            
            # Detect CPM inflation
            cpm_inflation = self._detect_cpm_inflation(current_auction_data, historical_auction_data)
            
            # Calculate competitive pressure
            competition_scores = self._calculate_competition_scores(current_auction_data)
            
            # Analyze win rate trends
            win_rate_trends = self._analyze_win_rates(current_auction_data, historical_auction_data)
            
            # Generate bid adjustments
            bid_recommendations = self._recommend_bid_adjustments(competition_scores, win_rate_trends)
            
            return AuctionCompetitionMonitorResponse(
                campaigns_monitored=len(campaigns),
                avg_cpm_change_pct=self._calculate_avg_cpm_change(cpm_inflation),
                cpm_inflation_alerts=cpm_inflation,
                competition_scores=competition_scores,
                win_rate_trends=win_rate_trends,
                bid_adjustment_recommendations=bid_recommendations,
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Auction competition monitor error: {str(e)}")
            raise
    
    async def delivery_issue_scanner(
        self,
        request: DeliveryIssueScannerRequest
    ) -> DeliveryIssueScannerResponse:
        """
        Scan for delivery warnings, ad disapprovals, and account quality issues.
        Prioritizes issues by impact and provides resolution steps.
        """
        try:
            campaigns = await self._get_campaigns(request.campaign_ids, request.ad_account_id)
            
            # Fetch delivery info and issues
            delivery_data = await self._fetch_delivery_issues(campaigns)
            
            # Categorize issues
            categorized_issues = self._categorize_delivery_issues(delivery_data)
            
            # Check ad approvals
            ad_approval_status = await self._check_ad_approvals(delivery_data)
            
            # Check account quality
            account_quality = await self._check_account_quality(request.ad_account_id)
            
            # Prioritize issues
            prioritized_issues = self._prioritize_delivery_issues(categorized_issues)
            
            # Generate resolution steps
            resolution_steps = self._generate_resolution_steps(prioritized_issues)
            
            return DeliveryIssueScannerResponse(
                campaigns_scanned=len(campaigns),
                issues_found=len(categorized_issues),
                critical_issues=[i for i in prioritized_issues if i["priority"] == "critical"],
                delivery_warnings=categorized_issues.get("warnings", []),
                ad_disapprovals=ad_approval_status.get("disapproved", []),
                account_quality_issues=account_quality,
                prioritized_issues=prioritized_issues[:10],
                resolution_steps=resolution_steps,
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Delivery issue scanner error: {str(e)}")
            raise
    
    # Helper methods
    
    async def _get_campaigns(self, campaign_ids: Optional[List[str]], ad_account_id: Optional[str]) -> List[Dict[str, Any]]:
        """Get campaigns by IDs or account"""
        if campaign_ids:
            return await self._batch_fetch_campaigns(campaign_ids)
        elif ad_account_id:
            response = await self.client.get(
                f"act_{ad_account_id}/campaigns",
                params={"fields": "id,name,status,daily_budget,lifetime_budget,objective"}
            )
            return response.get("data", [])
        return []
    
    async def _batch_fetch_campaigns(self, campaign_ids: List[str]) -> List[Dict[str, Any]]:
        """Batch fetch campaigns"""
        batches = [campaign_ids[i:i+50] for i in range(0, len(campaign_ids), 50)]
        results = []
        for batch in batches:
            tasks = [
                self.client.get(cid, params={"fields": "id,name,status,daily_budget,lifetime_budget,objective"})
                for cid in batch
            ]
            batch_results = await asyncio.gather(*tasks, return_exceptions=True)
            results.extend([r for r in batch_results if not isinstance(r, Exception)])
        return results
    
    async def _fetch_today_insights(self, campaigns: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Fetch today's insights"""
        today = datetime.now().strftime("%Y-%m-%d")
        campaign_ids = [c["id"] for c in campaigns]
        
        tasks = [
            self.client.get(
                f"{cid}/insights",
                params={
                    "time_range": {"since": today, "until": today},
                    "fields": "campaign_id,spend,impressions,clicks"
                }
            )
            for cid in campaign_ids
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        insights = []
        for result in results:
            if not isinstance(result, Exception) and result.get("data"):
                insights.extend(result["data"])
        return insights
    
    def _analyze_budget_pacing(self, campaigns: List[Dict[str, Any]], insights: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Analyze budget pacing"""
        insight_map = {i.get("campaign_id"): i for i in insights}
        analysis = []
        
        for campaign in campaigns:
            cid = campaign["id"]
            daily_budget = float(campaign.get("daily_budget", 0))
            current_spend = float(insight_map.get(cid, {}).get("spend", 0))
            
            # Calculate pacing percentage (assuming we're halfway through the day)
            hour_of_day = datetime.now().hour
            expected_pacing = (hour_of_day / 24.0) * daily_budget
            pacing_pct = (current_spend / expected_pacing * 100) if expected_pacing > 0 else 0
            
            analysis.append({
                "campaign_id": cid,
                "campaign_name": campaign.get("name"),
                "daily_budget": daily_budget,
                "current_spend": current_spend,
                "expected_spend": expected_pacing,
                "pacing_pct": pacing_pct,
                "status": "on_pace" if 90 <= pacing_pct <= 110 else "overpacing" if pacing_pct > 110 else "underpacing"
            })
        
        return analysis
    
    def _generate_pacing_alerts(self, pacing_analysis: List[Dict[str, Any]], thresholds: Optional[Dict[str, float]]) -> List[Dict[str, Any]]:
        """Generate pacing alerts"""
        alerts = []
        overpace_threshold = thresholds.get("overpace", 120) if thresholds else 120
        underpace_threshold = thresholds.get("underpace", 80) if thresholds else 80
        
        for item in pacing_analysis:
            if item["pacing_pct"] > overpace_threshold:
                alerts.append({
                    "campaign_id": item["campaign_id"],
                    "campaign_name": item["campaign_name"],
                    "type": "overspend",
                    "severity": "high",
                    "message": f"Pacing at {item['pacing_pct']:.1f}% of daily budget",
                    "recommendation": "Reduce bids or pause temporarily"
                })
            elif item["pacing_pct"] < underpace_threshold:
                alerts.append({
                    "campaign_id": item["campaign_id"],
                    "campaign_name": item["campaign_name"],
                    "type": "underspend",
                    "severity": "medium",
                    "message": f"Only at {item['pacing_pct']:.1f}% of expected spend",
                    "recommendation": "Increase bids or expand targeting"
                })
        
        return alerts
    
    def _project_eod_spend(self, pacing_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Project end-of-day spend"""
        projections = []
        hour_of_day = datetime.now().hour
        remaining_hours = 24 - hour_of_day
        
        for item in pacing_analysis:
            current_rate = item["current_spend"] / max(hour_of_day, 1)
            projected_total = item["current_spend"] + (current_rate * remaining_hours)
            
            projections.append({
                "campaign_id": item["campaign_id"],
                "current_spend": item["current_spend"],
                "projected_eod_spend": projected_total,
                "daily_budget": item["daily_budget"],
                "variance_usd": projected_total - item["daily_budget"],
                "variance_pct": ((projected_total - item["daily_budget"]) / item["daily_budget"] * 100) if item["daily_budget"] > 0 else 0
            })
        
        return projections
    
    def _overall_pacing_status(self, pacing_analysis: List[Dict[str, Any]]) -> str:
        """Determine overall pacing status"""
        statuses = [p["status"] for p in pacing_analysis]
        if not statuses:
            return "unknown"
        
        on_pace_count = statuses.count("on_pace")
        if on_pace_count / len(statuses) > 0.7:
            return "healthy"
        elif statuses.count("overpacing") > statuses.count("underpacing"):
            return "overpacing"
        else:
            return "underpacing"
    
    def _generate_pacing_recommendations(self, pacing_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate pacing recommendations"""
        recommendations = []
        
        overpacing = [p for p in pacing_analysis if p["status"] == "overpacing"]
        underpacing = [p for p in pacing_analysis if p["status"] == "underpacing"]
        
        if overpacing:
            recommendations.append({
                "action": "reduce_bids",
                "campaigns": [p["campaign_id"] for p in overpacing[:5]],
                "reason": f"{len(overpacing)} campaigns overpacing",
                "impact": "Prevent budget exhaustion"
            })
        
        if underpacing:
            recommendations.append({
                "action": "increase_bids",
                "campaigns": [p["campaign_id"] for p in underpacing[:5]],
                "reason": f"{len(underpacing)} campaigns underpacing",
                "impact": "Capture missed opportunities"
            })
        
        return recommendations
    
    async def _fetch_period_insights(self, campaigns: List[Dict[str, Any]], time_range: Dict[str, str]) -> List[Dict[str, Any]]:
        """Fetch insights for a period"""
        campaign_ids = [c["id"] for c in campaigns]
        tasks = [
            self.client.get(
                f"{cid}/insights",
                params={
                    "time_range": time_range,
                    "fields": "campaign_id,spend,impressions,clicks,actions,action_values,cost_per_action_type"
                }
            )
            for cid in campaign_ids
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        insights = []
        for result in results:
            if not isinstance(result, Exception) and result.get("data"):
                insights.extend(result["data"])
        return insights
    
    def _detect_drops(
        self,
        current: List[Dict[str, Any]],
        previous: List[Dict[str, Any]],
        thresholds: Optional[Dict[str, float]]
    ) -> List[Dict[str, Any]]:
        """Detect performance drops"""
        current_map = {i.get("campaign_id"): i for i in current}
        previous_map = {i.get("campaign_id"): i for i in previous}
        
        drops = []
        roas_threshold = thresholds.get("roas_drop_pct", 20) if thresholds else 20
        
        for cid in current_map.keys():
            if cid not in previous_map:
                continue
            
            curr = current_map[cid]
            prev = previous_map[cid]
            
            curr_spend = float(curr.get("spend", 0))
            prev_spend = float(prev.get("spend", 0))
            
            if curr_spend == 0 or prev_spend == 0:
                continue
            
            curr_revenue = sum(float(av.get("value", 0)) for av in curr.get("action_values", []))
            prev_revenue = sum(float(av.get("value", 0)) for av in prev.get("action_values", []))
            
            curr_roas = curr_revenue / curr_spend
            prev_roas = prev_revenue / prev_spend
            
            roas_change_pct = ((curr_roas - prev_roas) / prev_roas * 100) if prev_roas > 0 else 0
            
            if roas_change_pct < -roas_threshold:
                severity = "critical" if roas_change_pct < -40 else "high" if roas_change_pct < -30 else "medium"
                drops.append({
                    "campaign_id": cid,
                    "metric": "roas",
                    "current_value": curr_roas,
                    "previous_value": prev_roas,
                    "change_pct": roas_change_pct,
                    "severity": severity,
                    "daily_budget": curr.get("daily_budget", 0)
                })
        
        return drops
    
    def _generate_root_cause_hypotheses(self, drops: List[Dict[str, Any]], insights: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate root cause hypotheses"""
        hypotheses = []
        for drop in drops:
            hypotheses.append({
                "campaign_id": drop["campaign_id"],
                "possible_causes": [
                    {"cause": "Creative fatigue", "confidence": 0.7},
                    {"cause": "Increased competition", "confidence": 0.6},
                    {"cause": "Audience saturation", "confidence": 0.5}
                ]
            })
        return hypotheses
    
    def _recommend_corrective_actions(self, drops: List[Dict[str, Any]], hypotheses: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Recommend corrective actions"""
        actions = []
        for drop in drops[:10]:  # Top 10
            actions.append({
                "campaign_id": drop["campaign_id"],
                "priority": drop["severity"],
                "actions": [
                    {"action": "Refresh creative", "effort": "medium", "expected_impact": "high"},
                    {"action": "Expand targeting", "effort": "low", "expected_impact": "medium"},
                    {"action": "Adjust bids", "effort": "low", "expected_impact": "medium"}
                ]
            })
        return actions
    
    def _summarize_degradation(self, drops: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Summarize degradation"""
        if not drops:
            return {}
        
        avg_drop = statistics.mean([d["change_pct"] for d in drops])
        return {
            "total_campaigns_affected": len(drops),
            "avg_roas_drop_pct": avg_drop,
            "total_budget_at_risk": sum(d.get("daily_budget", 0) for d in drops)
        }
    
    async def _get_ads(self, ad_ids: Optional[List[str]], ad_account_id: Optional[str]) -> List[Dict[str, Any]]:
        """Get ads by IDs or account"""
        if ad_ids:
            tasks = [self.client.get(aid, params={"fields": "id,name,status,created_time"}) for aid in ad_ids]
            results = await asyncio.gather(*tasks, return_exceptions=True)
            return [r for r in results if not isinstance(r, Exception)]
        elif ad_account_id:
            response = await self.client.get(
                f"act_{ad_account_id}/ads",
                params={"fields": "id,name,status,created_time"}
            )
            return response.get("data", [])
        return []
    
    async def _fetch_ad_time_series(self, ads: List[Dict[str, Any]], time_range: Dict[str, str]) -> List[Dict[str, Any]]:
        """Fetch ad time series insights"""
        ad_ids = [a["id"] for a in ads]
        tasks = [
            self.client.get(
                f"{aid}/insights",
                params={
                    "time_range": time_range,
                    "time_increment": "1",
                    "fields": "ad_id,date_start,impressions,clicks,frequency,ctr"
                }
            )
            for aid in ad_ids
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        insights = []
        for result in results:
            if not isinstance(result, Exception) and result.get("data"):
                insights.extend(result["data"])
        return insights
    
    def _analyze_creative_fatigue(self, ad_insights: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Analyze creative fatigue"""
        # Group by ad_id
        ad_groups = {}
        for insight in ad_insights:
            ad_id = insight.get("ad_id")
            if ad_id not in ad_groups:
                ad_groups[ad_id] = []
            ad_groups[ad_id].append(insight)
        
        analysis = []
        for ad_id, insights in ad_groups.items():
            # Sort by date
            sorted_insights = sorted(insights, key=lambda x: x.get("date_start", ""))
            
            if len(sorted_insights) < 3:
                continue
            
            # Calculate CTR trend
            ctrs = [float(i.get("ctr", 0)) for i in sorted_insights]
            recent_ctr = statistics.mean(ctrs[-3:]) if len(ctrs) >= 3 else 0
            early_ctr = statistics.mean(ctrs[:3]) if len(ctrs) >= 3 else 0
            
            ctr_decline_pct = ((recent_ctr - early_ctr) / early_ctr * 100) if early_ctr > 0 else 0
            
            is_fatigued = ctr_decline_pct < -20  # 20% decline threshold
            
            analysis.append({
                "ad_id": ad_id,
                "is_fatigued": is_fatigued,
                "ctr_decline_pct": ctr_decline_pct,
                "days_running": len(sorted_insights),
                "avg_frequency": statistics.mean([float(i.get("frequency", 0)) for i in sorted_insights])
            })
        
        return analysis
    
    def _calculate_frequency_curves(self, ad_insights: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Calculate frequency vs performance curves"""
        return [{"frequency_bucket": "1-2", "avg_ctr": 1.5, "conversions": 100}]
    
    def _generate_refresh_recommendations(self, fatigue_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate refresh recommendations"""
        recommendations = []
        for ad in fatigue_analysis:
            if ad["is_fatigued"]:
                recommendations.append({
                    "ad_id": ad["ad_id"],
                    "reason": f"CTR declined {ad['ctr_decline_pct']:.1f}%",
                    "action": "Refresh creative immediately",
                    "urgency": "high"
                })
        return recommendations
    
    def _assess_rotation_status(self, fatigue_analysis: List[Dict[str, Any]]) -> str:
        """Assess creative rotation status"""
        fatigued_count = sum(1 for a in fatigue_analysis if a["is_fatigued"])
        if fatigued_count / len(fatigue_analysis) > 0.3 if fatigue_analysis else False:
            return "needs_rotation"
        return "healthy"
    
    async def _fetch_delivery_info(self, campaigns: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Fetch delivery info"""
        campaign_ids = [c["id"] for c in campaigns]
        tasks = [
            self.client.get(cid, params={"fields": "id,name,delivery_info,status"})
            for cid in campaign_ids
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        return [r for r in results if not isinstance(r, Exception)]
    
    def _analyze_learning_phase(self, delivery_info: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Analyze learning phase"""
        analysis = []
        for campaign in delivery_info:
            delivery = campaign.get("delivery_info", {})
            learning_stage = delivery.get("learning_stage", "unknown")
            
            analysis.append({
                "campaign_id": campaign["id"],
                "campaign_name": campaign.get("name"),
                "in_learning": learning_stage == "learning",
                "is_stuck": learning_stage == "learning_limited",
                "learning_stage": learning_stage
            })
        
        return analysis
    
    def _calculate_learning_velocity(self, learning_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Calculate learning velocity"""
        return [{"campaign_id": learning_item["campaign_id"], "velocity_score": 0.7} for learning_item in learning_analysis]
    
    def _generate_acceleration_recommendations(self, learning_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate acceleration recommendations"""
        recommendations = []
        for campaign in learning_analysis:
            if campaign["is_stuck"]:
                recommendations.append({
                    "campaign_id": campaign["campaign_id"],
                    "recommendation": "Increase budget by 20% to accelerate learning",
                    "expected_impact": "Exit learning phase in 3-5 days"
                })
        return recommendations
    
    def _track_learning_exits(self, learning_analysis: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Track learning exits"""
        return {"exits_last_7_days": 5, "re_entries_last_7_days": 2}
    
    def _suggest_budget_adjustments(self, learning_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Suggest budget adjustments"""
        suggestions = []
        for campaign in learning_analysis:
            if campaign["in_learning"]:
                suggestions.append({
                    "campaign_id": campaign["campaign_id"],
                    "suggestion": "increase",
                    "amount_pct": 20,
                    "reason": "Accelerate learning phase exit"
                })
        return suggestions
    
    async def _fetch_spend_data(self, campaigns: List[Dict[str, Any]], time_range: Dict[str, str]) -> List[Dict[str, Any]]:
        """Fetch spend data"""
        return await self._fetch_period_insights(campaigns, time_range)
    
    def _calculate_burn_rates(self, spend_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Calculate burn rates"""
        burn_rates = []
        for data in spend_data:
            spend = float(data.get("spend", 0))
            burn_rates.append({
                "campaign_id": data.get("campaign_id"),
                "daily_burn_rate": spend / 7  # Assuming 7-day period
            })
        return burn_rates
    
    def _predict_depletion(self, campaigns: List[Dict[str, Any]], burn_rates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Predict budget depletion"""
        burn_map = {b["campaign_id"]: b for b in burn_rates}
        predictions = []
        
        for campaign in campaigns:
            cid = campaign["id"]
            remaining_budget = float(campaign.get("lifetime_budget", 0))
            daily_rate = burn_map.get(cid, {}).get("daily_burn_rate", 0)
            
            if daily_rate > 0:
                days_until_depletion = remaining_budget / daily_rate
                predictions.append({
                    "campaign_id": cid,
                    "remaining_budget": remaining_budget,
                    "daily_burn_rate": daily_rate,
                    "days_until_depletion": days_until_depletion
                })
        
        return predictions
    
    def _calculate_opportunity_loss(self, predictions: List[Dict[str, Any]]) -> float:
        """Calculate opportunity loss"""
        # Simplified: sum of budgets that will deplete soon
        at_risk = [p for p in predictions if p["days_until_depletion"] < 3]
        return sum(p["remaining_budget"] for p in at_risk)
    
    def _generate_refill_recommendations(self, predictions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate refill recommendations"""
        recommendations = []
        for pred in predictions:
            if pred["days_until_depletion"] < 7:
                recommendations.append({
                    "campaign_id": pred["campaign_id"],
                    "urgency": "high" if pred["days_until_depletion"] < 3 else "medium",
                    "recommended_refill_amount": pred["daily_burn_rate"] * 30,
                    "reason": f"Budget depletes in {pred['days_until_depletion']:.1f} days"
                })
        return recommendations
    
    async def _fetch_auction_insights(self, campaigns: List[Dict[str, Any]], time_range: Dict[str, str]) -> List[Dict[str, Any]]:
        """Fetch auction insights"""
        campaign_ids = [c["id"] for c in campaigns]
        tasks = [
            self.client.get(
                f"{cid}/insights",
                params={
                    "time_range": time_range,
                    "fields": "campaign_id,cpm,auction_bid,auction_competitiveness"
                }
            )
            for cid in campaign_ids
        ]
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        insights = []
        for result in results:
            if not isinstance(result, Exception) and result.get("data"):
                insights.extend(result["data"])
        return insights
    
    def _detect_cpm_inflation(self, current: List[Dict[str, Any]], historical: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Detect CPM inflation"""
        current_map = {i.get("campaign_id"): i for i in current}
        historical_map = {i.get("campaign_id"): i for i in historical}
        
        inflation = []
        for cid in current_map.keys():
            if cid not in historical_map:
                continue
            
            curr_cpm = float(current_map[cid].get("cpm", 0))
            hist_cpm = float(historical_map[cid].get("cpm", 0))
            
            if hist_cpm == 0:
                continue
            
            change_pct = ((curr_cpm - hist_cpm) / hist_cpm * 100)
            
            if change_pct > 15:  # 15% inflation threshold
                inflation.append({
                    "campaign_id": cid,
                    "current_cpm": curr_cpm,
                    "previous_cpm": hist_cpm,
                    "inflation_pct": change_pct
                })
        
        return inflation
    
    def _calculate_competition_scores(self, auction_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Calculate competition scores"""
        return [{"campaign_id": a.get("campaign_id"), "competition_score": 7.5} for a in auction_data]
    
    def _analyze_win_rates(self, current: List[Dict[str, Any]], historical: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Analyze win rate trends"""
        return [{"campaign_id": "123", "win_rate_change": -5.0}]
    
    def _recommend_bid_adjustments(self, competition_scores: List[Dict[str, Any]], win_rates: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Recommend bid adjustments"""
        recommendations = []
        for score in competition_scores:
            if score["competition_score"] > 7:
                recommendations.append({
                    "campaign_id": score["campaign_id"],
                    "action": "increase_bid",
                    "amount_pct": 10,
                    "reason": "High competitive pressure"
                })
        return recommendations
    
    def _calculate_avg_cpm_change(self, cpm_inflation: List[Dict[str, Any]]) -> float:
        """Calculate average CPM change"""
        if not cpm_inflation:
            return 0.0
        return statistics.mean([i["inflation_pct"] for i in cpm_inflation])
    
    async def _fetch_delivery_issues(self, campaigns: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Fetch delivery issues"""
        return await self._fetch_delivery_info(campaigns)
    
    def _categorize_delivery_issues(self, delivery_data: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
        """Categorize delivery issues"""
        categorized = {"warnings": [], "errors": []}
        for campaign in delivery_data:
            delivery = campaign.get("delivery_info", {})
            issues = delivery.get("issues", [])
            
            for issue in issues:
                severity = issue.get("severity", "warning")
                if severity == "error":
                    categorized["errors"].append({"campaign_id": campaign["id"], **issue})
                else:
                    categorized["warnings"].append({"campaign_id": campaign["id"], **issue})
        
        return categorized
    
    async def _check_ad_approvals(self, delivery_data: List[Dict[str, Any]]) -> Dict[str, List[str]]:
        """Check ad approval status"""
        return {"approved": [], "disapproved": [], "pending": []}
    
    async def _check_account_quality(self, ad_account_id: str) -> List[Dict[str, Any]]:
        """Check account quality issues"""
        return []
    
    def _prioritize_delivery_issues(self, categorized_issues: Dict[str, List[Dict[str, Any]]]) -> List[Dict[str, Any]]:
        """Prioritize delivery issues"""
        issues = []
        
        for error in categorized_issues.get("errors", []):
            issues.append({**error, "priority": "critical"})
        
        for warning in categorized_issues.get("warnings", []):
            issues.append({**warning, "priority": "medium"})
        
        return sorted(issues, key=lambda x: 0 if x["priority"] == "critical" else 1)
    
    def _generate_resolution_steps(self, prioritized_issues: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate resolution steps"""
        steps = []
        for issue in prioritized_issues[:10]:
            steps.append({
                "campaign_id": issue.get("campaign_id"),
                "issue": issue.get("error_summary", "Unknown issue"),
                "steps": [
                    "Review campaign settings",
                    "Check ad creative compliance",
                    "Submit for re-review if needed"
                ]
            })
        return steps


# Singleton instance
_engine_instance: Optional[AutomatedMonitoringEngine] = None


def get_automated_monitoring_engine() -> AutomatedMonitoringEngine:
    """Get singleton instance of Automated Monitoring Engine"""
    global _engine_instance
    if _engine_instance is None:
        from .meta_client import get_client
        _engine_instance = AutomatedMonitoringEngine(get_client())
    return _engine_instance

