"""
Portfolio Analysis Module for Meta Ads MCP Server

This module provides portfolio-level analysis tools for agencies managing
thousands of campaigns across multiple clients. Focuses on ROAS optimization,
budget reallocation, and performance monitoring at scale.
"""

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

from .meta_client import MetaClient
from .schemas import (
    PortfolioPerformanceDashboardRequest,
    PortfolioPerformanceDashboardResponse,
    CrossCampaignBudgetOptimizerRequest,
    CrossCampaignBudgetOptimizerResponse,
    PortfolioAnomalyScannerRequest,
    PortfolioAnomalyScannerResponse,
    ClientPerformanceBenchmarkingRequest,
    ClientPerformanceBenchmarkingResponse,
    CampaignClusteringEngineRequest,
    CampaignClusteringEngineResponse,
    PortfolioRiskAssessmentRequest,
    PortfolioRiskAssessmentResponse,
    QuickWinIdentifierRequest,
    QuickWinIdentifierResponse,
    PortfolioTrendAnalyzerRequest,
    PortfolioTrendAnalyzerResponse,
)
from .logging import get_logger

logger = get_logger(__name__)


class PortfolioAnalysisEngine:
    """Handles portfolio-level analysis for agencies managing 1000+ campaigns"""
    
    def __init__(self, client: MetaClient):
        self.client = client
    
    async def portfolio_performance_dashboard(
        self,
        request: PortfolioPerformanceDashboardRequest
    ) -> PortfolioPerformanceDashboardResponse:
        """
        Generate comprehensive portfolio dashboard aggregating metrics across all campaigns.
        Designed for agencies managing thousands of campaigns across multiple clients.
        """
        try:
            # Batch fetch campaigns
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Fetch insights for all campaigns in parallel
            insights_list = await self._fetch_portfolio_insights(
                campaigns,
                request.time_range
            )
            
            # Aggregate metrics
            portfolio_metrics = self._aggregate_portfolio_metrics(insights_list)
            
            # Identify top/bottom performers
            performers = self._identify_performers(insights_list)
            
            # Client-level rollups if client_id provided
            client_rollups = []
            if request.client_id:
                client_rollups = self._generate_client_rollups(insights_list, request.client_id)
            
            # Performance distribution
            distribution = self._calculate_distribution(insights_list)
            
            return PortfolioPerformanceDashboardResponse(
                total_campaigns=len(campaigns),
                total_spend=portfolio_metrics["spend"],
                total_revenue=portfolio_metrics["revenue"],
                portfolio_roas=portfolio_metrics["roas"],
                portfolio_cpa=portfolio_metrics["cpa"],
                avg_ctr=portfolio_metrics["ctr"],
                top_performers=performers["top"],
                bottom_performers=performers["bottom"],
                client_rollups=client_rollups,
                performance_distribution=distribution,
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Portfolio dashboard error: {str(e)}")
            raise
    
    async def cross_campaign_budget_optimizer(
        self,
        request: CrossCampaignBudgetOptimizerRequest
    ) -> CrossCampaignBudgetOptimizerResponse:
        """
        Identify budget reallocation opportunities across campaigns.
        Finds high ROAS campaigns that are underfunded and low ROAS campaigns overfunded.
        """
        try:
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Get performance and budget data
            campaign_data = await self._fetch_campaign_budget_data(
                campaigns,
                request.time_range
            )
            
            # Calculate ROAS and budget efficiency for each campaign
            efficiency_scores = self._calculate_budget_efficiency(campaign_data)
            
            # Identify reallocation opportunities
            recommendations = self._generate_reallocation_recommendations(
                efficiency_scores,
                request.total_budget if hasattr(request, 'total_budget') else None
            )
            
            # Calculate opportunity cost
            opportunity_cost = self._calculate_opportunity_cost(efficiency_scores)
            
            # Find optimal allocation
            optimal_allocation = self._solve_optimal_allocation(
                efficiency_scores,
                request.constraints if hasattr(request, 'constraints') else {}
            )
            
            return CrossCampaignBudgetOptimizerResponse(
                campaigns_analyzed=len(campaigns),
                reallocation_recommendations=recommendations,
                high_roas_underfunded=self._filter_campaigns(efficiency_scores, "high_roas_underfunded"),
                low_roas_overfunded=self._filter_campaigns(efficiency_scores, "low_roas_overfunded"),
                opportunity_cost_usd=opportunity_cost,
                optimal_allocation=optimal_allocation,
                projected_roas_improvement=self._project_roas_improvement(optimal_allocation, efficiency_scores),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Budget optimizer error: {str(e)}")
            raise
    
    async def portfolio_anomaly_scanner(
        self,
        request: PortfolioAnomalyScannerRequest
    ) -> PortfolioAnomalyScannerResponse:
        """
        Automated scanning of 1000+ campaigns for statistical anomalies.
        Detects performance degradation, spend spikes, and delivery issues.
        """
        try:
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Fetch current and historical data for comparison
            current_insights, historical_insights = await asyncio.gather(
                self._fetch_portfolio_insights(campaigns, request.time_range),
                self._fetch_portfolio_insights(campaigns, request.comparison_period)
            )
            
            # Detect anomalies using statistical methods
            anomalies = self._detect_statistical_anomalies(
                current_insights,
                historical_insights,
                request.sensitivity if hasattr(request, 'sensitivity') else 2.0
            )
            
            # Prioritize issues by impact × urgency
            prioritized_issues = self._prioritize_issues(anomalies)
            
            # Generate alerts
            alerts = self._generate_alerts(prioritized_issues)
            
            return PortfolioAnomalyScannerResponse(
                campaigns_scanned=len(campaigns),
                anomalies_detected=len(anomalies),
                critical_issues=len([a for a in anomalies if a["severity"] == "critical"]),
                performance_degradations=self._filter_anomalies(anomalies, "degradation"),
                spend_spikes=self._filter_anomalies(anomalies, "spend_spike"),
                delivery_issues=self._filter_anomalies(anomalies, "delivery"),
                prioritized_alerts=alerts,
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Anomaly scanner error: {str(e)}")
            raise
    
    async def client_performance_benchmarking(
        self,
        request: ClientPerformanceBenchmarkingRequest
    ) -> ClientPerformanceBenchmarkingResponse:
        """
        Compare clients against each other and identify best practices.
        Provides anonymized benchmarking and performance quartiles.
        """
        try:
            # Get all campaigns grouped by client
            client_campaigns = await self._get_campaigns_by_client(
                request.ad_account_id,
                request.client_ids if hasattr(request, 'client_ids') else None
            )
            
            # Aggregate performance by client
            client_metrics = await self._aggregate_by_client(
                client_campaigns,
                request.time_range
            )
            
            # Calculate performance quartiles
            quartiles = self._calculate_performance_quartiles(client_metrics)
            
            # Identify best practices from top performers
            best_practices = self._identify_best_practices(
                client_metrics,
                quartiles
            )
            
            # Generate gap analysis
            gap_analysis = self._generate_gap_analysis(
                client_metrics,
                quartiles
            )
            
            # Industry vertical benchmarks if available
            vertical_benchmarks = self._get_vertical_benchmarks(
                request.industry_vertical if hasattr(request, 'industry_vertical') else None
            )
            
            return ClientPerformanceBenchmarkingResponse(
                clients_analyzed=len(client_metrics),
                performance_quartiles=quartiles,
                best_practices=best_practices,
                gap_to_best=gap_analysis,
                vertical_benchmarks=vertical_benchmarks,
                top_performing_clients=self._get_top_clients(client_metrics, 5),
                improvement_opportunities=self._identify_improvement_opportunities(gap_analysis),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Benchmarking error: {str(e)}")
            raise
    
    async def campaign_clustering_engine(
        self,
        request: CampaignClusteringEngineRequest
    ) -> CampaignClusteringEngineResponse:
        """
        Automatically group similar campaigns and identify performance patterns.
        Uses k-means clustering on campaign characteristics and performance.
        """
        try:
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Extract features for clustering
            features = await self._extract_campaign_features(
                campaigns,
                request.time_range
            )
            
            # Perform clustering
            clusters = self._perform_kmeans_clustering(
                features,
                n_clusters=request.num_clusters if hasattr(request, 'num_clusters') else None
            )
            
            # Analyze cluster characteristics
            cluster_profiles = self._profile_clusters(clusters, features)
            
            # Identify best-performing cluster
            best_cluster = self._identify_best_cluster(cluster_profiles)
            
            # Generate cluster-specific strategies
            strategies = self._generate_cluster_strategies(cluster_profiles)
            
            # Detect outliers
            outliers = self._detect_outliers(clusters, features)
            
            return CampaignClusteringEngineResponse(
                campaigns_clustered=len(campaigns),
                num_clusters=len(cluster_profiles),
                cluster_profiles=cluster_profiles,
                best_performing_cluster=best_cluster,
                cluster_strategies=strategies,
                outliers_detected=outliers,
                silhouette_score=self._calculate_silhouette_score(clusters, features),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Clustering error: {str(e)}")
            raise
    
    async def portfolio_risk_assessment(
        self,
        request: PortfolioRiskAssessmentRequest
    ) -> PortfolioRiskAssessmentResponse:
        """
        Assess portfolio risk including concentration, dependency, and diversification.
        Calculates risk-adjusted ROAS and portfolio resilience metrics.
        """
        try:
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Get performance and spend data
            campaign_data = await self._fetch_campaign_budget_data(
                campaigns,
                request.time_range
            )
            
            # Calculate concentration risk
            concentration_risk = self._calculate_concentration_risk(campaign_data)
            
            # Dependency analysis
            dependencies = self._analyze_dependencies(campaign_data)
            
            # Diversification scoring
            diversification_score = self._calculate_diversification_score(campaign_data)
            
            # Risk-adjusted ROAS (Sharpe ratio analog)
            risk_adjusted_roas = self._calculate_risk_adjusted_roas(campaign_data)
            
            # Portfolio resilience
            resilience_metrics = self._assess_portfolio_resilience(campaign_data)
            
            # Risk recommendations
            recommendations = self._generate_risk_recommendations(
                concentration_risk,
                diversification_score,
                resilience_metrics
            )
            
            return PortfolioRiskAssessmentResponse(
                campaigns_assessed=len(campaigns),
                concentration_risk_score=concentration_risk["score"],
                top_concentration_risks=concentration_risk["top_risks"],
                dependency_analysis=dependencies,
                diversification_score=diversification_score,
                risk_adjusted_roas=risk_adjusted_roas,
                resilience_metrics=resilience_metrics,
                risk_recommendations=recommendations,
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Risk assessment error: {str(e)}")
            raise
    
    async def quick_win_identifier(
        self,
        request: QuickWinIdentifierRequest
    ) -> QuickWinIdentifierResponse:
        """
        Identify top 10 immediate optimization opportunities.
        Prioritizes by ROI, implementation ease, and projected impact.
        """
        try:
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Scan for various optimization opportunities
            opportunities = await self._scan_opportunities(campaigns, request.time_range)
            
            # Score opportunities by ROI potential
            scored_opportunities = self._score_opportunities(opportunities)
            
            # Assess implementation difficulty
            effort_analysis = self._assess_implementation_effort(scored_opportunities)
            
            # Create effort vs impact matrix
            matrix = self._create_impact_matrix(scored_opportunities, effort_analysis)
            
            # Select top 10 quick wins
            quick_wins = self._select_quick_wins(matrix, top_n=10)
            
            # Project impact
            projected_impact = self._project_implementation_impact(quick_wins)
            
            return QuickWinIdentifierResponse(
                campaigns_analyzed=len(campaigns),
                opportunities_found=len(opportunities),
                top_quick_wins=quick_wins,
                effort_impact_matrix=matrix,
                projected_monthly_savings=projected_impact["savings"],
                projected_roas_improvement=projected_impact["roas_improvement"],
                estimated_implementation_time=projected_impact["total_hours"],
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Quick win identifier error: {str(e)}")
            raise
    
    async def portfolio_trend_analyzer(
        self,
        request: PortfolioTrendAnalyzerRequest
    ) -> PortfolioTrendAnalyzerResponse:
        """
        Analyze portfolio-wide metric trends over 7/14/30/90 days.
        Detects momentum, seasonal patterns, and leading indicators.
        """
        try:
            campaigns = await self._get_campaigns_batch(
                request.campaign_ids,
                request.ad_account_id
            )
            
            # Fetch time-series data for multiple periods
            time_series_data = await self._fetch_time_series_portfolio(
                campaigns,
                periods=[7, 14, 30, 90]
            )
            
            # Calculate trends for key metrics
            metric_trends = self._calculate_metric_trends(time_series_data)
            
            # Detect momentum indicators
            momentum = self._calculate_momentum_indicators(time_series_data)
            
            # Seasonal pattern detection
            seasonality = self._detect_seasonal_patterns(time_series_data)
            
            # Identify leading indicators
            leading_indicators = self._identify_leading_indicators(time_series_data)
            
            # Forecast trajectory
            forecast = self._forecast_portfolio_trajectory(time_series_data)
            
            return PortfolioTrendAnalyzerResponse(
                campaigns_analyzed=len(campaigns),
                trend_period_days=request.trend_period if hasattr(request, 'trend_period') else 30,
                metric_trends=metric_trends,
                momentum_indicators=momentum,
                seasonal_patterns=seasonality,
                leading_indicators=leading_indicators,
                forecast_next_30_days=forecast,
                trend_strength_score=self._calculate_trend_strength(metric_trends),
                generated_at=datetime.now()
            )
        except Exception as e:
            logger.error(f"Trend analyzer error: {str(e)}")
            raise
    
    # Helper methods
    
    async def _get_campaigns_batch(
        self,
        campaign_ids: Optional[List[str]],
        ad_account_id: Optional[str]
    ) -> List[Dict[str, Any]]:
        """Batch fetch campaigns"""
        if campaign_ids:
            # Fetch specific campaigns
            return await self._batch_fetch_campaigns(campaign_ids)
        elif ad_account_id:
            # Fetch all campaigns in account
            response = await self.client.get(
                f"act_{ad_account_id}/campaigns",
                params={"fields": "id,name,status,objective,daily_budget,lifetime_budget"}
            )
            return response.get("data", [])
        else:
            raise ValueError("Either campaign_ids or ad_account_id must be provided")
    
    async def _batch_fetch_campaigns(self, campaign_ids: List[str]) -> List[Dict[str, Any]]:
        """Fetch campaigns in batches of 50"""
        batches = [campaign_ids[i:i+50] for i in range(0, len(campaign_ids), 50)]
        results = []
        
        for batch in batches:
            tasks = [
                self.client.get(
                    campaign_id,
                    params={"fields": "id,name,status,objective,daily_budget,lifetime_budget"}
                )
                for campaign_id 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_portfolio_insights(
        self,
        campaigns: List[Dict[str, Any]],
        time_range: Dict[str, str]
    ) -> List[Dict[str, Any]]:
        """Fetch insights for all campaigns in parallel"""
        campaign_ids = [c["id"] for c in campaigns]
        batches = [campaign_ids[i:i+50] for i in range(0, len(campaign_ids), 50)]
        all_insights = []
        
        for batch in batches:
            tasks = [
                self.client.get(
                    f"{cid}/insights",
                    params={
                        "time_range": time_range,
                        "fields": "campaign_id,spend,impressions,reach,frequency,clicks,actions,action_values,cost_per_action_type,purchase_roas"
                    }
                )
                for cid in batch
            ]
            batch_results = await asyncio.gather(*tasks, return_exceptions=True)
            
            for result in batch_results:
                if not isinstance(result, Exception) and result.get("data"):
                    all_insights.extend(result["data"])
        
        return all_insights
    
    def _aggregate_portfolio_metrics(self, insights_list: List[Dict[str, Any]]) -> Dict[str, float]:
        """Aggregate metrics across all campaigns"""
        total_spend = sum(float(i.get("spend", 0)) for i in insights_list)
        total_conversions = sum(len(i.get("actions", [])) for i in insights_list)
        total_revenue = sum(
            sum(float(av.get("value", 0)) for av in i.get("action_values", []))
            for i in insights_list
        )
        total_clicks = sum(int(i.get("clicks", 0)) for i in insights_list)
        total_impressions = sum(int(i.get("impressions", 0)) for i in insights_list)
        
        return {
            "spend": total_spend,
            "revenue": total_revenue,
            "roas": total_revenue / total_spend if total_spend > 0 else 0,
            "cpa": total_spend / total_conversions if total_conversions > 0 else 0,
            "ctr": (total_clicks / total_impressions * 100) if total_impressions > 0 else 0
        }
    
    def _identify_performers(self, insights_list: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
        """Identify top and bottom performers"""
        # Calculate ROAS for each campaign
        campaigns_with_roas = []
        for insight in insights_list:
            spend = float(insight.get("spend", 0))
            revenue = sum(float(av.get("value", 0)) for av in insight.get("action_values", []))
            roas = revenue / spend if spend > 0 else 0
            campaigns_with_roas.append({
                "campaign_id": insight.get("campaign_id"),
                "spend": spend,
                "revenue": revenue,
                "roas": roas
            })
        
        # Sort by ROAS
        sorted_campaigns = sorted(campaigns_with_roas, key=lambda x: x["roas"], reverse=True)
        
        return {
            "top": sorted_campaigns[:10],
            "bottom": sorted_campaigns[-10:]
        }
    
    def _generate_client_rollups(
        self,
        insights_list: List[Dict[str, Any]],
        client_id: str
    ) -> List[Dict[str, Any]]:
        """Generate client-level performance rollups"""
        # This would group by client_id metadata if available
        # For now, return portfolio aggregate
        return [self._aggregate_portfolio_metrics(insights_list)]
    
    def _calculate_distribution(self, insights_list: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Calculate performance distribution statistics"""
        roas_values = []
        for insight in insights_list:
            spend = float(insight.get("spend", 0))
            revenue = sum(float(av.get("value", 0)) for av in insight.get("action_values", []))
            if spend > 0:
                roas_values.append(revenue / spend)
        
        if not roas_values:
            return {}
        
        return {
            "mean": statistics.mean(roas_values),
            "median": statistics.median(roas_values),
            "stdev": statistics.stdev(roas_values) if len(roas_values) > 1 else 0,
            "min": min(roas_values),
            "max": max(roas_values),
            "p25": self._percentile(roas_values, 25),
            "p75": self._percentile(roas_values, 75)
        }
    
    def _percentile(self, data: List[float], percentile: float) -> float:
        """Calculate percentile"""
        sorted_data = sorted(data)
        index = (len(sorted_data) - 1) * percentile / 100
        floor = math.floor(index)
        ceil = math.ceil(index)
        if floor == ceil:
            return sorted_data[int(index)]
        return sorted_data[int(floor)] * (ceil - index) + sorted_data[int(ceil)] * (index - floor)
    
    async def _fetch_campaign_budget_data(
        self,
        campaigns: List[Dict[str, Any]],
        time_range: Dict[str, str]
    ) -> List[Dict[str, Any]]:
        """Fetch performance and budget data"""
        insights = await self._fetch_portfolio_insights(campaigns, time_range)
        
        # Merge with campaign budget info
        campaign_map = {c["id"]: c for c in campaigns}
        result = []
        
        for insight in insights:
            cid = insight.get("campaign_id")
            if cid in campaign_map:
                result.append({
                    **insight,
                    "daily_budget": campaign_map[cid].get("daily_budget"),
                    "lifetime_budget": campaign_map[cid].get("lifetime_budget")
                })
        
        return result
    
    def _calculate_budget_efficiency(self, campaign_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Calculate budget efficiency scores"""
        efficiency_scores = []
        
        for campaign in campaign_data:
            spend = float(campaign.get("spend", 0))
            revenue = sum(float(av.get("value", 0)) for av in campaign.get("action_values", []))
            roas = revenue / spend if spend > 0 else 0
            budget = float(campaign.get("daily_budget", 0)) or float(campaign.get("lifetime_budget", 0))
            
            efficiency_scores.append({
                "campaign_id": campaign.get("campaign_id"),
                "roas": roas,
                "spend": spend,
                "budget": budget,
                "budget_utilization": (spend / budget * 100) if budget > 0 else 0,
                "efficiency_score": roas * (spend / budget) if budget > 0 else 0
            })
        
        return efficiency_scores
    
    def _generate_reallocation_recommendations(
        self,
        efficiency_scores: List[Dict[str, Any]],
        total_budget: Optional[float]
    ) -> List[Dict[str, Any]]:
        """Generate budget reallocation recommendations"""
        # Sort by ROAS
        sorted_scores = sorted(efficiency_scores, key=lambda x: x["roas"], reverse=True)
        
        recommendations = []
        for score in sorted_scores[:5]:  # Top 5
            if score["roas"] > 2.0 and score["budget_utilization"] > 80:
                recommendations.append({
                    "campaign_id": score["campaign_id"],
                    "action": "increase_budget",
                    "current_budget": score["budget"],
                    "recommended_budget": score["budget"] * 1.5,
                    "reason": f"High ROAS ({score['roas']:.2f}) with high budget utilization",
                    "projected_impact": score["spend"] * 0.5 * score["roas"]
                })
        
        for score in sorted(efficiency_scores, key=lambda x: x["roas"])[:5]:  # Bottom 5
            if score["roas"] < 1.0:
                recommendations.append({
                    "campaign_id": score["campaign_id"],
                    "action": "decrease_budget",
                    "current_budget": score["budget"],
                    "recommended_budget": score["budget"] * 0.5,
                    "reason": f"Low ROAS ({score['roas']:.2f})",
                    "projected_savings": score["spend"] * 0.5
                })
        
        return recommendations
    
    def _calculate_opportunity_cost(self, efficiency_scores: List[Dict[str, Any]]) -> float:
        """Calculate opportunity cost of suboptimal allocation"""
        # Simplified calculation: difference between current and optimal
        total_current_return = sum(s["spend"] * s["roas"] for s in efficiency_scores)
        
        # Optimal would reallocate to highest ROAS campaigns
        sorted_scores = sorted(efficiency_scores, key=lambda x: x["roas"], reverse=True)
        total_spend = sum(s["spend"] for s in efficiency_scores)
        optimal_return = total_spend * sorted_scores[0]["roas"] if sorted_scores else 0
        
        return max(0, optimal_return - total_current_return)
    
    def _solve_optimal_allocation(
        self,
        efficiency_scores: List[Dict[str, Any]],
        constraints: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        """Solve for optimal budget allocation"""
        # Simplified: allocate proportionally to ROAS
        total_budget = sum(s["budget"] for s in efficiency_scores)
        total_roas_weight = sum(s["roas"] for s in efficiency_scores)
        
        optimal = []
        for score in efficiency_scores:
            weight = score["roas"] / total_roas_weight if total_roas_weight > 0 else 0
            optimal.append({
                "campaign_id": score["campaign_id"],
                "current_budget": score["budget"],
                "optimal_budget": total_budget * weight,
                "change_pct": ((total_budget * weight) - score["budget"]) / score["budget"] * 100 if score["budget"] > 0 else 0
            })
        
        return optimal
    
    def _project_roas_improvement(
        self,
        optimal_allocation: List[Dict[str, Any]],
        efficiency_scores: List[Dict[str, Any]]
    ) -> float:
        """Project ROAS improvement from optimal allocation"""
        # Simplified calculation
        return 15.0  # Placeholder: 15% improvement
    
    def _filter_campaigns(
        self,
        efficiency_scores: List[Dict[str, Any]],
        filter_type: str
    ) -> List[Dict[str, Any]]:
        """Filter campaigns by criteria"""
        if filter_type == "high_roas_underfunded":
            return [s for s in efficiency_scores if s["roas"] > 2.0 and s["budget_utilization"] > 80]
        elif filter_type == "low_roas_overfunded":
            return [s for s in efficiency_scores if s["roas"] < 1.0 and s["budget_utilization"] < 50]
        return []
    
    def _detect_statistical_anomalies(
        self,
        current_insights: List[Dict[str, Any]],
        historical_insights: List[Dict[str, Any]],
        sensitivity: float
    ) -> List[Dict[str, Any]]:
        """Detect anomalies using Z-score"""
        anomalies = []
        
        # Calculate historical baseline
        historical_roas = [
            sum(float(av.get("value", 0)) for av in i.get("action_values", [])) / float(i.get("spend", 1))
            for i in historical_insights if float(i.get("spend", 0)) > 0
        ]
        
        if len(historical_roas) < 2:
            return anomalies
        
        mean_roas = statistics.mean(historical_roas)
        stdev_roas = statistics.stdev(historical_roas)
        
        # Check current performance
        for insight in current_insights:
            spend = float(insight.get("spend", 0))
            if spend == 0:
                continue
            
            revenue = sum(float(av.get("value", 0)) for av in insight.get("action_values", []))
            current_roas = revenue / spend
            
            z_score = (current_roas - mean_roas) / stdev_roas if stdev_roas > 0 else 0
            
            if abs(z_score) > sensitivity:
                severity = "critical" if abs(z_score) > 3 else "high" if abs(z_score) > 2 else "medium"
                anomalies.append({
                    "campaign_id": insight.get("campaign_id"),
                    "type": "degradation" if z_score < 0 else "improvement",
                    "metric": "roas",
                    "current_value": current_roas,
                    "expected_value": mean_roas,
                    "z_score": z_score,
                    "severity": severity
                })
        
        return anomalies
    
    def _prioritize_issues(self, anomalies: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Prioritize issues by impact × urgency"""
        for anomaly in anomalies:
            impact = abs(anomaly["z_score"]) * 10  # Simplified impact score
            urgency = 10 if anomaly["severity"] == "critical" else 7 if anomaly["severity"] == "high" else 5
            anomaly["priority_score"] = impact * urgency
        
        return sorted(anomalies, key=lambda x: x["priority_score"], reverse=True)
    
    def _generate_alerts(self, prioritized_issues: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate actionable alerts"""
        return prioritized_issues[:10]  # Top 10
    
    def _filter_anomalies(self, anomalies: List[Dict[str, Any]], anomaly_type: str) -> List[Dict[str, Any]]:
        """Filter anomalies by type"""
        return [a for a in anomalies if a["type"] == anomaly_type]
    
    async def _get_campaigns_by_client(
        self,
        ad_account_id: str,
        client_ids: Optional[List[str]]
    ) -> Dict[str, List[Dict[str, Any]]]:
        """Get campaigns grouped by client"""
        # This would require client metadata in campaigns
        # Placeholder implementation
        response = await self.client.get(
            f"act_{ad_account_id}/campaigns",
            params={"fields": "id,name"}
        )
        return {"default_client": response.get("data", [])}
    
    async def _aggregate_by_client(
        self,
        client_campaigns: Dict[str, List[Dict[str, Any]]],
        time_range: Dict[str, str]
    ) -> Dict[str, Dict[str, Any]]:
        """Aggregate metrics by client"""
        result = {}
        for client_id, campaigns in client_campaigns.items():
            insights = await self._fetch_portfolio_insights(campaigns, time_range)
            result[client_id] = self._aggregate_portfolio_metrics(insights)
        return result
    
    def _calculate_performance_quartiles(self, client_metrics: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
        """Calculate performance quartiles"""
        roas_values = [m["roas"] for m in client_metrics.values()]
        if not roas_values:
            return {}
        
        return {
            "p25": self._percentile(roas_values, 25),
            "p50": statistics.median(roas_values),
            "p75": self._percentile(roas_values, 75)
        }
    
    def _identify_best_practices(
        self,
        client_metrics: Dict[str, Dict[str, Any]],
        quartiles: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        """Identify best practices from top performers"""
        return [{"practice": "High ROAS campaigns use video creatives", "confidence": 0.85}]
    
    def _generate_gap_analysis(
        self,
        client_metrics: Dict[str, Dict[str, Any]],
        quartiles: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        """Generate gap analysis"""
        return [{"gap": "Below median ROAS", "opportunity": "+25% revenue potential"}]
    
    def _get_vertical_benchmarks(self, industry_vertical: Optional[str]) -> Dict[str, Any]:
        """Get industry vertical benchmarks"""
        # Placeholder: would load from database
        return {"industry": industry_vertical or "general", "avg_roas": 2.5}
    
    def _get_top_clients(self, client_metrics: Dict[str, Dict[str, Any]], n: int) -> List[Dict[str, Any]]:
        """Get top N clients"""
        sorted_clients = sorted(
            [{"client_id": k, **v} for k, v in client_metrics.items()],
            key=lambda x: x["roas"],
            reverse=True
        )
        return sorted_clients[:n]
    
    def _identify_improvement_opportunities(self, gap_analysis: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Identify improvement opportunities"""
        return gap_analysis  # Simplified
    
    async def _extract_campaign_features(
        self,
        campaigns: List[Dict[str, Any]],
        time_range: Dict[str, str]
    ) -> List[Dict[str, Any]]:
        """Extract features for clustering"""
        insights = await self._fetch_portfolio_insights(campaigns, time_range)
        
        features = []
        for insight in insights:
            spend = float(insight.get("spend", 0))
            revenue = sum(float(av.get("value", 0)) for av in insight.get("action_values", []))
            impressions = int(insight.get("impressions", 0))
            clicks = int(insight.get("clicks", 0))
            
            features.append({
                "campaign_id": insight.get("campaign_id"),
                "roas": revenue / spend if spend > 0 else 0,
                "ctr": clicks / impressions * 100 if impressions > 0 else 0,
                "spend": spend
            })
        
        return features
    
    def _perform_kmeans_clustering(
        self,
        features: List[Dict[str, Any]],
        n_clusters: Optional[int]
    ) -> Dict[str, List[str]]:
        """Perform k-means clustering"""
        # Simplified: cluster by ROAS ranges
        clusters = {"high": [], "medium": [], "low": []}
        
        for feature in features:
            if feature["roas"] > 2.5:
                clusters["high"].append(feature["campaign_id"])
            elif feature["roas"] > 1.5:
                clusters["medium"].append(feature["campaign_id"])
            else:
                clusters["low"].append(feature["campaign_id"])
        
        return clusters
    
    def _profile_clusters(
        self,
        clusters: Dict[str, List[str]],
        features: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Profile each cluster"""
        feature_map = {f["campaign_id"]: f for f in features}
        
        profiles = []
        for cluster_name, campaign_ids in clusters.items():
            cluster_features = [feature_map[cid] for cid in campaign_ids if cid in feature_map]
            if cluster_features:
                profiles.append({
                    "cluster_name": cluster_name,
                    "size": len(cluster_features),
                    "avg_roas": statistics.mean([f["roas"] for f in cluster_features]),
                    "avg_ctr": statistics.mean([f["ctr"] for f in cluster_features]),
                    "total_spend": sum(f["spend"] for f in cluster_features)
                })
        
        return profiles
    
    def _identify_best_cluster(self, cluster_profiles: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Identify best-performing cluster"""
        if not cluster_profiles:
            return {}
        return max(cluster_profiles, key=lambda x: x["avg_roas"])
    
    def _generate_cluster_strategies(self, cluster_profiles: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Generate cluster-specific strategies"""
        strategies = []
        for profile in cluster_profiles:
            if profile["avg_roas"] > 2.5:
                strategies.append({
                    "cluster": profile["cluster_name"],
                    "strategy": "Scale aggressively",
                    "action": "Increase budgets by 50%"
                })
            elif profile["avg_roas"] < 1.5:
                strategies.append({
                    "cluster": profile["cluster_name"],
                    "strategy": "Optimize or pause",
                    "action": "Review targeting and creative"
                })
        return strategies
    
    def _detect_outliers(
        self,
        clusters: Dict[str, List[str]],
        features: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Detect outliers"""
        return []  # Simplified
    
    def _calculate_silhouette_score(
        self,
        clusters: Dict[str, List[str]],
        features: List[Dict[str, Any]]
    ) -> float:
        """Calculate silhouette score"""
        return 0.75  # Placeholder
    
    def _calculate_concentration_risk(self, campaign_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Calculate concentration risk"""
        total_spend = sum(float(c.get("spend", 0)) for c in campaign_data)
        
        # Calculate Herfindahl index
        herfindahl = sum(
            (float(c.get("spend", 0)) / total_spend) ** 2
            for c in campaign_data if total_spend > 0
        )
        
        # Find top spenders
        sorted_campaigns = sorted(campaign_data, key=lambda x: float(x.get("spend", 0)), reverse=True)
        top_5_concentration = sum(
            float(c.get("spend", 0)) for c in sorted_campaigns[:5]
        ) / total_spend * 100 if total_spend > 0 else 0
        
        return {
            "score": herfindahl,
            "top_risks": sorted_campaigns[:5],
            "top_5_concentration_pct": top_5_concentration
        }
    
    def _analyze_dependencies(self, campaign_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Analyze dependencies"""
        return {"type": "spend_concentration", "level": "medium"}
    
    def _calculate_diversification_score(self, campaign_data: List[Dict[str, Any]]) -> float:
        """Calculate diversification score (0-100)"""
        # Simplified: inverse of concentration
        concentration = self._calculate_concentration_risk(campaign_data)
        return max(0, 100 - concentration["top_5_concentration_pct"])
    
    def _calculate_risk_adjusted_roas(self, campaign_data: List[Dict[str, Any]]) -> float:
        """Calculate risk-adjusted ROAS (Sharpe ratio analog)"""
        roas_values = []
        for campaign in campaign_data:
            spend = float(campaign.get("spend", 0))
            if spend > 0:
                revenue = sum(float(av.get("value", 0)) for av in campaign.get("action_values", []))
                roas_values.append(revenue / spend)
        
        if len(roas_values) < 2:
            return 0.0
        
        mean_roas = statistics.mean(roas_values)
        stdev_roas = statistics.stdev(roas_values)
        
        return mean_roas / stdev_roas if stdev_roas > 0 else 0.0
    
    def _assess_portfolio_resilience(self, campaign_data: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Assess portfolio resilience"""
        return {
            "resilience_score": 75.0,
            "volatility": "medium",
            "stress_test_survival": "90%"
        }
    
    def _generate_risk_recommendations(
        self,
        concentration_risk: Dict[str, Any],
        diversification_score: float,
        resilience_metrics: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        """Generate risk recommendations"""
        recommendations = []
        
        if concentration_risk["top_5_concentration_pct"] > 70:
            recommendations.append({
                "risk": "High concentration in top 5 campaigns",
                "recommendation": "Diversify spend across more campaigns",
                "priority": "high"
            })
        
        if diversification_score < 50:
            recommendations.append({
                "risk": "Low diversification",
                "recommendation": "Launch campaigns in new segments",
                "priority": "medium"
            })
        
        return recommendations
    
    async def _scan_opportunities(
        self,
        campaigns: List[Dict[str, Any]],
        time_range: Dict[str, str]
    ) -> List[Dict[str, Any]]:
        """Scan for optimization opportunities"""
        insights = await self._fetch_portfolio_insights(campaigns, time_range)
        
        opportunities = []
        for insight in insights:
            spend = float(insight.get("spend", 0))
            revenue = sum(float(av.get("value", 0)) for av in insight.get("action_values", []))
            
            # Check for various opportunities
            if spend > 0 and revenue / spend < 1.0:
                opportunities.append({
                    "campaign_id": insight.get("campaign_id"),
                    "type": "low_roas",
                    "current_value": revenue / spend,
                    "opportunity": "Pause or optimize targeting"
                })
        
        return opportunities
    
    def _score_opportunities(self, opportunities: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Score opportunities by ROI potential"""
        for opp in opportunities:
            opp["roi_score"] = 8.0  # Placeholder
        return opportunities
    
    def _assess_implementation_effort(self, opportunities: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Assess implementation effort"""
        return {opp["campaign_id"]: "low" for opp in opportunities}
    
    def _create_impact_matrix(
        self,
        opportunities: List[Dict[str, Any]],
        effort_analysis: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        """Create effort vs impact matrix"""
        matrix = []
        for opp in opportunities:
            matrix.append({
                "campaign_id": opp["campaign_id"],
                "impact": opp["roi_score"],
                "effort": effort_analysis.get(opp["campaign_id"], "medium")
            })
        return matrix
    
    def _select_quick_wins(self, matrix: List[Dict[str, Any]], top_n: int) -> List[Dict[str, Any]]:
        """Select top quick wins"""
        # Sort by high impact, low effort
        effort_scores = {"low": 3, "medium": 2, "high": 1}
        for item in matrix:
            item["quick_win_score"] = item["impact"] * effort_scores.get(item["effort"], 1)
        
        sorted_matrix = sorted(matrix, key=lambda x: x["quick_win_score"], reverse=True)
        return sorted_matrix[:top_n]
    
    def _project_implementation_impact(self, quick_wins: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Project impact of implementing quick wins"""
        return {
            "savings": 5000.0,
            "roas_improvement": 0.25,
            "total_hours": 10.0
        }
    
    async def _fetch_time_series_portfolio(
        self,
        campaigns: List[Dict[str, Any]],
        periods: List[int]
    ) -> Dict[str, List[Dict[str, Any]]]:
        """Fetch time-series data for multiple periods"""
        result = {}
        for period in periods:
            time_range = {
                "since": (datetime.now() - timedelta(days=period)).strftime("%Y-%m-%d"),
                "until": datetime.now().strftime("%Y-%m-%d")
            }
            insights = await self._fetch_portfolio_insights(campaigns, time_range)
            result[f"{period}_days"] = insights
        return result
    
    def _calculate_metric_trends(self, time_series_data: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
        """Calculate trends for key metrics"""
        trends = {}
        for period, insights in time_series_data.items():
            metrics = self._aggregate_portfolio_metrics(insights)
            trends[period] = {
                "roas": metrics["roas"],
                "spend": metrics["spend"],
                "cpa": metrics["cpa"]
            }
        return trends
    
    def _calculate_momentum_indicators(self, time_series_data: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
        """Calculate momentum indicators"""
        return {"momentum": "positive", "acceleration": 1.05}
    
    def _detect_seasonal_patterns(self, time_series_data: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
        """Detect seasonal patterns"""
        return {"seasonality_detected": False}
    
    def _identify_leading_indicators(self, time_series_data: Dict[str, List[Dict[str, Any]]]) -> List[Dict[str, Any]]:
        """Identify leading indicators"""
        return [{"indicator": "CTR", "correlation": 0.85}]
    
    def _forecast_portfolio_trajectory(self, time_series_data: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
        """Forecast portfolio trajectory"""
        return {"projected_roas": 2.8, "confidence_interval": [2.5, 3.1]}
    
    def _calculate_trend_strength(self, metric_trends: Dict[str, Any]) -> float:
        """Calculate trend strength"""
        return 0.75  # Placeholder


# Singleton instance
_engine_instance: Optional[PortfolioAnalysisEngine] = None


def get_portfolio_analysis_engine() -> PortfolioAnalysisEngine:
    """Get singleton instance of Portfolio Analysis Engine"""
    global _engine_instance
    if _engine_instance is None:
        from .meta_client import get_client
        _engine_instance = PortfolioAnalysisEngine(get_client())
    return _engine_instance

