"""
Calculation & Analysis Utilities for Meta Ads MCP Server

This module provides calculation helpers and analysis utilities for Meta Ads data
to enable LLMs to perform complex calculations and analysis on campaign performance.
"""

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

from .meta_client import MetaClient
from .schemas import (
    CalculateROIRequest,
    CalculateROASRequest,
    CalculateEfficiencyScoreRequest,
    CalculateTrendRequest,
    CalculateVarianceRequest,
    CalculateCorrelationRequest,
    CalculatePacingRequest,
    CalculateROIResponse,
    CalculateROASResponse,
    CalculateEfficiencyScoreResponse,
    CalculateTrendResponse,
    CalculateVarianceResponse,
    CalculateCorrelationResponse,
    CalculatePacingResponse,
    EfficiencyMetrics,
    TrendAnalysis,
    VarianceAnalysis,
    CorrelationAnalysis,
    PacingAnalysis,
)
from .logging import get_logger

logger = get_logger(__name__)


@dataclass
class CalculationContext:
    """Context for calculation operations"""
    ad_account_id: str
    time_range: Dict[str, str]
    entity_ids: List[str]
    entity_type: str
    metrics: List[str]


class CalculationEngine:
    """Handles calculation and analysis operations for Meta Ads"""
    
    def __init__(self, client: MetaClient):
        self.client = client
    
    async def calculate_roi(
        self, 
        request: CalculateROIRequest
    ) -> CalculateROIResponse:
        """Calculate Return on Investment (ROI) for campaigns/adsets/ads"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=["spend", "conversions", "conversion_value"]
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context)
            
            # Calculate ROI for each entity
            roi_results = []
            for data in performance_data:
                spend = float(data.get("spend", 0))
                conversions = int(data.get("conversions", 0))
                conversion_value = float(data.get("conversion_value", 0))
                
                # Calculate ROI
                if spend > 0:
                    roi = ((conversion_value - spend) / spend) * 100
                else:
                    roi = 0
                
                roi_results.append({
                    "entity_id": data.get("campaign_id") or data.get("adset_id") or data.get("ad_id"),
                    "entity_name": data.get("campaign_name") or data.get("adset_name") or data.get("ad_name"),
                    "spend": spend,
                    "conversions": conversions,
                    "conversion_value": conversion_value,
                    "roi": roi,
                    "profit": conversion_value - spend
                })
            
            # Calculate aggregate ROI
            total_spend = sum(result["spend"] for result in roi_results)
            total_conversion_value = sum(result["conversion_value"] for result in roi_results)
            aggregate_roi = ((total_conversion_value - total_spend) / total_spend * 100) if total_spend > 0 else 0
            
            return CalculateROIResponse(
                roi_results=roi_results,
                aggregate_roi=aggregate_roi,
                total_spend=total_spend,
                total_conversion_value=total_conversion_value,
                total_profit=total_conversion_value - total_spend,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating ROI: {e}")
            raise
    
    async def calculate_roas(
        self, 
        request: CalculateROASRequest
    ) -> CalculateROASResponse:
        """Calculate Return on Ad Spend (ROAS) for campaigns/adsets/ads"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=["spend", "conversions", "conversion_value"]
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context)
            
            # Calculate ROAS for each entity
            roas_results = []
            for data in performance_data:
                spend = float(data.get("spend", 0))
                conversion_value = float(data.get("conversion_value", 0))
                
                # Calculate ROAS
                roas = (conversion_value / spend) if spend > 0 else 0
                
                roas_results.append({
                    "entity_id": data.get("campaign_id") or data.get("adset_id") or data.get("ad_id"),
                    "entity_name": data.get("campaign_name") or data.get("adset_name") or data.get("ad_name"),
                    "spend": spend,
                    "conversion_value": conversion_value,
                    "roas": roas
                })
            
            # Calculate aggregate ROAS
            total_spend = sum(result["spend"] for result in roas_results)
            total_conversion_value = sum(result["conversion_value"] for result in roas_results)
            aggregate_roas = (total_conversion_value / total_spend) if total_spend > 0 else 0
            
            return CalculateROASResponse(
                roas_results=roas_results,
                aggregate_roas=aggregate_roas,
                total_spend=total_spend,
                total_conversion_value=total_conversion_value,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating ROAS: {e}")
            raise
    
    async def calculate_efficiency_score(
        self, 
        request: CalculateEfficiencyScoreRequest
    ) -> CalculateEfficiencyScoreResponse:
        """Calculate efficiency score based on multiple performance metrics"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=request.metrics
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context)
            
            # Calculate efficiency scores
            efficiency_results = []
            for data in performance_data:
                metrics = EfficiencyMetrics(
                    ctr=float(data.get("ctr", 0)),
                    cpc=float(data.get("cpc", 0)),
                    cpm=float(data.get("cpm", 0)),
                    conversion_rate=float(data.get("conversion_rate", 0)),
                    cost_per_conversion=float(data.get("cost_per_conversion", 0)),
                    roas=float(data.get("roas", 0))
                )
                
                # Calculate composite efficiency score
                efficiency_score = self._calculate_composite_efficiency_score(metrics, request.weights)
                
                efficiency_results.append({
                    "entity_id": data.get("campaign_id") or data.get("adset_id") or data.get("ad_id"),
                    "entity_name": data.get("campaign_name") or data.get("adset_name") or data.get("ad_name"),
                    "metrics": metrics,
                    "efficiency_score": efficiency_score,
                    "rank": 0  # Will be set after sorting
                })
            
            # Sort by efficiency score and assign ranks
            efficiency_results.sort(key=lambda x: x["efficiency_score"], reverse=True)
            for i, result in enumerate(efficiency_results):
                result["rank"] = i + 1
            
            # Calculate aggregate efficiency
            aggregate_efficiency = statistics.mean([result["efficiency_score"] for result in efficiency_results]) if efficiency_results else 0
            
            return CalculateEfficiencyScoreResponse(
                efficiency_results=efficiency_results,
                aggregate_efficiency=aggregate_efficiency,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating efficiency score: {e}")
            raise
    
    async def calculate_trend(
        self, 
        request: CalculateTrendRequest
    ) -> CalculateTrendResponse:
        """Calculate trend analysis for performance metrics"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=[request.metric]
            )
            
            # Get historical data for trend analysis
            historical_data = await self._get_historical_data(context, request.periods)
            
            # Calculate trend for each entity
            trend_results = []
            for entity_id, data_points in historical_data.items():
                if len(data_points) < 2:
                    continue
                
                # Calculate trend using linear regression
                trend_analysis = self._calculate_linear_trend(data_points, request.metric)
                
                trend_results.append({
                    "entity_id": entity_id,
                    "metric": request.metric,
                    "trend_analysis": trend_analysis,
                    "data_points": len(data_points)
                })
            
            return CalculateTrendResponse(
                trend_results=trend_results,
                metric=request.metric,
                periods=request.periods,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating trend: {e}")
            raise
    
    async def calculate_variance(
        self, 
        request: CalculateVarianceRequest
    ) -> CalculateVarianceResponse:
        """Calculate variance analysis for performance metrics"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=request.metrics
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context)
            
            # Calculate variance for each metric
            variance_results = {}
            for metric in request.metrics:
                values = [float(data.get(metric, 0)) for data in performance_data if data.get(metric)]
                
                if len(values) > 1:
                    variance_analysis = VarianceAnalysis(
                        mean=statistics.mean(values),
                        variance=statistics.variance(values),
                        standard_deviation=statistics.stdev(values),
                        coefficient_of_variation=(statistics.stdev(values) / statistics.mean(values)) if statistics.mean(values) > 0 else 0,
                        min_value=min(values),
                        max_value=max(values),
                        range=max(values) - min(values)
                    )
                else:
                    variance_analysis = VarianceAnalysis(
                        mean=values[0] if values else 0,
                        variance=0,
                        standard_deviation=0,
                        coefficient_of_variation=0,
                        min_value=values[0] if values else 0,
                        max_value=values[0] if values else 0,
                        range=0
                    )
                
                variance_results[metric] = variance_analysis
            
            return CalculateVarianceResponse(
                variance_results=variance_results,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating variance: {e}")
            raise
    
    async def calculate_correlation(
        self, 
        request: CalculateCorrelationRequest
    ) -> CalculateCorrelationResponse:
        """Calculate correlation between performance metrics"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=request.metrics
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context)
            
            # Extract metric values
            metric_values = {}
            for metric in request.metrics:
                metric_values[metric] = [float(data.get(metric, 0)) for data in performance_data if data.get(metric)]
            
            # Calculate correlations between all metric pairs
            correlation_results = {}
            for i, metric1 in enumerate(request.metrics):
                for j, metric2 in enumerate(request.metrics):
                    if i < j:  # Avoid duplicate pairs
                        values1 = metric_values[metric1]
                        values2 = metric_values[metric2]
                        
                        if len(values1) == len(values2) and len(values1) > 1:
                            correlation = self._calculate_pearson_correlation(values1, values2)
                            correlation_results[f"{metric1}_vs_{metric2}"] = CorrelationAnalysis(
                                metric1=metric1,
                                metric2=metric2,
                                correlation_coefficient=correlation,
                                strength=self._interpret_correlation_strength(correlation),
                                direction="positive" if correlation > 0 else "negative"
                            )
            
            return CalculateCorrelationResponse(
                correlation_results=correlation_results,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating correlation: {e}")
            raise
    
    async def calculate_pacing(
        self, 
        request: CalculatePacingRequest
    ) -> CalculatePacingResponse:
        """Calculate budget pacing analysis"""
        try:
            context = CalculationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                entity_ids=request.entity_ids,
                entity_type=request.entity_type,
                metrics=["spend", "daily_budget", "lifetime_budget"]
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context)
            
            # Calculate pacing for each entity
            pacing_results = []
            for data in performance_data:
                spend = float(data.get("spend", 0))
                daily_budget = float(data.get("daily_budget", 0))
                lifetime_budget = float(data.get("lifetime_budget", 0))
                
                # Calculate pacing metrics
                days_elapsed = self._calculate_days_elapsed(request.time_range)
                expected_spend = daily_budget * days_elapsed if daily_budget > 0 else 0
                
                pacing_analysis = PacingAnalysis(
                    current_spend=spend,
                    daily_budget=daily_budget,
                    lifetime_budget=lifetime_budget,
                    expected_spend=expected_spend,
                    pacing_percentage=(spend / expected_spend * 100) if expected_spend > 0 else 0,
                    days_elapsed=days_elapsed,
                    days_remaining=self._calculate_days_remaining(request.time_range),
                    projected_total_spend=spend * (30 / days_elapsed) if days_elapsed > 0 else 0,
                    budget_utilization=(spend / lifetime_budget * 100) if lifetime_budget > 0 else 0
                )
                
                pacing_results.append({
                    "entity_id": data.get("campaign_id") or data.get("adset_id") or data.get("ad_id"),
                    "entity_name": data.get("campaign_name") or data.get("adset_name") or data.get("ad_name"),
                    "pacing_analysis": pacing_analysis
                })
            
            return CalculatePacingResponse(
                pacing_results=pacing_results,
                entity_type=request.entity_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error calculating pacing: {e}")
            raise
    
    async def _get_performance_data(
        self, 
        context: CalculationContext
    ) -> List[Dict[str, Any]]:
        """Get performance data for entities"""
        try:
            # Get insights with specified metrics
            if context.entity_type == "campaign":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "campaign",
                    "fields": ",".join(context.metrics),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            elif context.entity_type == "adset":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "adset",
                    "fields": ",".join(context.metrics),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            elif context.entity_type == "ad":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "ad",
                    "fields": ",".join(context.metrics),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            else:
                raise ValueError(f"Unsupported entity type: {context.entity_type}")
            
            # Add entity filters if specified
            if context.entity_ids:
                field_name = f"{context.entity_type}.id"
                params["filtering"] = [{"field": field_name, "operator": "IN", "value": context.entity_ids}]
            
            insights_response = await self.client.graph_request(
                method="GET",
                path=path,
                params=params
            )
            
            return insights_response.get("data", [])
            
        except Exception as e:
            logger.error(f"Error getting performance data: {e}")
            raise
    
    async def _get_historical_data(
        self, 
        context: CalculationContext, 
        periods: int
    ) -> Dict[str, List[Dict[str, Any]]]:
        """Get historical data for trend analysis"""
        try:
            # Calculate date ranges for historical periods
            end_date = datetime.strptime(context.time_range["until"], "%Y-%m-%d")
            start_date = datetime.strptime(context.time_range["since"], "%Y-%m-%d")
            period_days = (end_date - start_date).days // periods
            
            historical_data = {}
            
            for i in range(periods):
                period_start = start_date + timedelta(days=i * period_days)
                period_end = start_date + timedelta(days=(i + 1) * period_days)
                
                period_time_range = {
                    "since": period_start.strftime("%Y-%m-%d"),
                    "until": period_end.strftime("%Y-%m-%d")
                }
                
                # Get data for this period
                period_context = CalculationContext(
                    ad_account_id=context.ad_account_id,
                    time_range=period_time_range,
                    entity_ids=context.entity_ids,
                    entity_type=context.entity_type,
                    metrics=context.metrics
                )
                
                period_data = await self._get_performance_data(period_context)
                
                # Group by entity
                for data in period_data:
                    entity_id = data.get("campaign_id") or data.get("adset_id") or data.get("ad_id")
                    if entity_id not in historical_data:
                        historical_data[entity_id] = []
                    historical_data[entity_id].append(data)
            
            return historical_data
            
        except Exception as e:
            logger.error(f"Error getting historical data: {e}")
            raise
    
    def _calculate_composite_efficiency_score(
        self, 
        metrics: EfficiencyMetrics, 
        weights: Dict[str, float]
    ) -> float:
        """Calculate composite efficiency score"""
        try:
            # Normalize metrics to 0-100 scale
            normalized_metrics = {
                "ctr": min(metrics.ctr * 10, 100),  # CTR typically 0-10%
                "cpc": max(0, 100 - (metrics.cpc * 2)),  # Lower CPC is better
                "cpm": max(0, 100 - (metrics.cpm * 0.1)),  # Lower CPM is better
                "conversion_rate": min(metrics.conversion_rate * 20, 100),  # Conversion rate typically 0-5%
                "cost_per_conversion": max(0, 100 - (metrics.cost_per_conversion * 0.1)),  # Lower cost is better
                "roas": min(metrics.roas * 20, 100)  # ROAS typically 0-5
            }
            
            # Calculate weighted score
            total_score = 0
            total_weight = 0
            
            for metric, value in normalized_metrics.items():
                weight = weights.get(metric, 1.0)
                total_score += value * weight
                total_weight += weight
            
            return (total_score / total_weight) if total_weight > 0 else 0
            
        except Exception as e:
            logger.error(f"Error calculating composite efficiency score: {e}")
            return 0
    
    def _calculate_linear_trend(
        self, 
        data_points: List[Dict[str, Any]], 
        metric: str
    ) -> TrendAnalysis:
        """Calculate linear trend using simple linear regression"""
        try:
            if len(data_points) < 2:
                return TrendAnalysis(
                    slope=0,
                    intercept=0,
                    r_squared=0,
                    trend_direction="stable",
                    trend_strength="none"
                )
            
            # Extract metric values
            values = [float(point.get(metric, 0)) for point in data_points]
            n = len(values)
            
            # Calculate linear regression
            x_values = list(range(n))
            x_mean = statistics.mean(x_values)
            y_mean = statistics.mean(values)
            
            numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, values))
            denominator = sum((x - x_mean) ** 2 for x in x_values)
            
            if denominator == 0:
                slope = 0
            else:
                slope = numerator / denominator
            
            intercept = y_mean - slope * x_mean
            
            # Calculate R-squared
            y_pred = [slope * x + intercept for x in x_values]
            ss_res = sum((y - y_pred[i]) ** 2 for i, y in enumerate(values))
            ss_tot = sum((y - y_mean) ** 2 for y in values)
            r_squared = 1 - (ss_res / ss_tot) if ss_tot > 0 else 0
            
            # Determine trend direction and strength
            if abs(slope) < 0.01:
                direction = "stable"
            elif slope > 0:
                direction = "increasing"
            else:
                direction = "decreasing"
            
            if r_squared > 0.7:
                strength = "strong"
            elif r_squared > 0.3:
                strength = "moderate"
            else:
                strength = "weak"
            
            return TrendAnalysis(
                slope=slope,
                intercept=intercept,
                r_squared=r_squared,
                trend_direction=direction,
                trend_strength=strength
            )
            
        except Exception as e:
            logger.error(f"Error calculating linear trend: {e}")
            return TrendAnalysis(
                slope=0,
                intercept=0,
                r_squared=0,
                trend_direction="stable",
                trend_strength="none"
            )
    
    def _calculate_pearson_correlation(
        self, 
        x_values: List[float], 
        y_values: List[float]
    ) -> float:
        """Calculate Pearson correlation coefficient"""
        try:
            if len(x_values) != len(y_values) or len(x_values) < 2:
                return 0
            
            len(x_values)
            x_mean = statistics.mean(x_values)
            y_mean = statistics.mean(y_values)
            
            numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, y_values))
            x_variance = sum((x - x_mean) ** 2 for x in x_values)
            y_variance = sum((y - y_mean) ** 2 for y in y_values)
            
            denominator = math.sqrt(x_variance * y_variance)
            
            return (numerator / denominator) if denominator > 0 else 0
            
        except Exception as e:
            logger.error(f"Error calculating Pearson correlation: {e}")
            return 0
    
    def _interpret_correlation_strength(self, correlation: float) -> str:
        """Interpret correlation strength"""
        abs_correlation = abs(correlation)
        if abs_correlation >= 0.7:
            return "strong"
        elif abs_correlation >= 0.3:
            return "moderate"
        else:
            return "weak"
    
    def _calculate_days_elapsed(self, time_range: Dict[str, str]) -> int:
        """Calculate days elapsed in time range"""
        try:
            start_date = datetime.strptime(time_range["since"], "%Y-%m-%d")
            end_date = datetime.strptime(time_range["until"], "%Y-%m-%d")
            return (end_date - start_date).days
        except Exception:
            return 0
    
    def _calculate_days_remaining(self, time_range: Dict[str, str]) -> int:
        """Calculate days remaining in time range"""
        try:
            end_date = datetime.strptime(time_range["until"], "%Y-%m-%d")
            today = datetime.now()
            return max(0, (end_date - today).days)
        except Exception:
            return 0


# Global calculation engine instance
_calculation_engine: Optional[CalculationEngine] = None


def get_calculation_engine() -> CalculationEngine:
    """Get the global CalculationEngine instance"""
    global _calculation_engine
    if _calculation_engine is None:
        from .meta_client import get_client
        _calculation_engine = CalculationEngine(get_client())
    return _calculation_engine
