"""
Data Aggregation Tools for Meta Ads MCP Server

This module provides tools for aggregating and summarizing Meta Ads data
across campaigns, ad sets, and ads to provide high-level insights for LLMs.
"""

from typing import Dict, List, Any, Optional
from datetime import datetime
from dataclasses import dataclass

from .meta_client import MetaClient
from .schemas import (
    AggregateByObjectiveRequest,
    AggregateByStatusRequest,
    AggregateSpendingDistributionRequest,
    AggregatePerformanceSummaryRequest,
    IdentifyTopPerformersRequest,
    IdentifyBottomPerformersRequest,
    AggregateByObjectiveResponse,
    AggregateByStatusResponse,
    AggregateSpendingDistributionResponse,
    AggregatePerformanceSummaryResponse,
    IdentifyTopPerformersResponse,
    IdentifyBottomPerformersResponse,
    PerformanceMetrics,
    SpendingDistribution,
    TopPerformer,
    BottomPerformer,
    PerformanceSummary,
    ObjectiveSummary,
    StatusSummary,
)
from .logging import get_logger

logger = get_logger(__name__)


@dataclass
class AggregationContext:
    """Context for aggregation operations"""
    ad_account_id: str
    time_range: Dict[str, str]
    fields: List[str]
    breakdowns: Optional[List[str]] = None
    filters: Optional[List[Dict[str, Any]]] = None


class DataAggregator:
    """Handles data aggregation operations for Meta Ads"""
    
    def __init__(self, client: MetaClient):
        self.client = client
    
    async def aggregate_by_objective(
        self, 
        request: AggregateByObjectiveRequest
    ) -> AggregateByObjectiveResponse:
        """Aggregate performance data by campaign objective"""
        try:
            context = AggregationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                fields=request.fields,
                breakdowns=request.breakdowns,
                filters=request.filters
            )
            
            # Get campaigns grouped by objective
            campaigns_data = await self._get_campaigns_by_objective(context)
            
            # Aggregate performance by objective
            objective_summaries = []
            for objective, campaigns in campaigns_data.items():
                summary = await self._aggregate_campaigns_performance(
                    campaigns, context, objective
                )
                objective_summaries.append(summary)
            
            return AggregateByObjectiveResponse(
                objective_summaries=objective_summaries,
                total_campaigns=sum(len(campaigns) for campaigns in campaigns_data.values()),
                total_objectives=len(objective_summaries),
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error aggregating by objective: {e}")
            raise
    
    async def aggregate_by_status(
        self, 
        request: AggregateByStatusRequest
    ) -> AggregateByStatusResponse:
        """Aggregate performance data by campaign/adset/ad status"""
        try:
            context = AggregationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                fields=request.fields,
                filters=request.filters
            )
            
            # Get objects grouped by status
            status_data = await self._get_objects_by_status(context, request.object_type)
            
            # Aggregate performance by status
            status_summaries = []
            for status, objects in status_data.items():
                summary = await self._aggregate_objects_performance(
                    objects, context, status, request.object_type
                )
                status_summaries.append(summary)
            
            return AggregateByStatusResponse(
                status_summaries=status_summaries,
                total_objects=sum(len(objects) for objects in status_data.values()),
                object_type=request.object_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error aggregating by status: {e}")
            raise
    
    async def aggregate_spending_distribution(
        self, 
        request: AggregateSpendingDistributionRequest
    ) -> AggregateSpendingDistributionResponse:
        """Analyze spending distribution across campaigns/adsets/ads"""
        try:
            context = AggregationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                fields=["spend", "budget_remaining", "daily_budget", "lifetime_budget"],
                filters=request.filters
            )
            
            # Get spending data
            spending_data = await self._get_spending_data(context, request.object_type)
            
            # Calculate distribution metrics
            distribution = await self._calculate_spending_distribution(
                spending_data, request.percentiles
            )
            
            return AggregateSpendingDistributionResponse(
                distribution=distribution,
                object_type=request.object_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error aggregating spending distribution: {e}")
            raise
    
    async def aggregate_performance_summary(
        self, 
        request: AggregatePerformanceSummaryRequest
    ) -> AggregatePerformanceSummaryResponse:
        """Generate comprehensive performance summary"""
        try:
            context = AggregationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                fields=request.fields,
                filters=request.filters
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context, request.object_type)
            
            # Calculate summary metrics
            summary = await self._calculate_performance_summary(
                performance_data, request.include_trends
            )
            
            return AggregatePerformanceSummaryResponse(
                summary=summary,
                object_type=request.object_type,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error aggregating performance summary: {e}")
            raise
    
    async def identify_top_performers(
        self, 
        request: IdentifyTopPerformersRequest
    ) -> IdentifyTopPerformersResponse:
        """Identify top performing campaigns/adsets/ads"""
        try:
            context = AggregationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                fields=request.fields,
                filters=request.filters
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context, request.object_type)
            
            # Identify top performers
            top_performers = await self._identify_top_performers(
                performance_data, 
                request.performance_metric,
                request.limit,
                request.minimum_spend
            )
            
            return IdentifyTopPerformersResponse(
                top_performers=top_performers,
                object_type=request.object_type,
                performance_metric=request.performance_metric,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error identifying top performers: {e}")
            raise
    
    async def identify_bottom_performers(
        self, 
        request: IdentifyBottomPerformersRequest
    ) -> IdentifyBottomPerformersResponse:
        """Identify bottom performing campaigns/adsets/ads"""
        try:
            context = AggregationContext(
                ad_account_id=request.ad_account_id,
                time_range=request.time_range,
                fields=request.fields,
                filters=request.filters
            )
            
            # Get performance data
            performance_data = await self._get_performance_data(context, request.object_type)
            
            # Identify bottom performers
            bottom_performers = await self._identify_bottom_performers(
                performance_data, 
                request.performance_metric,
                request.limit,
                request.minimum_spend
            )
            
            return IdentifyBottomPerformersResponse(
                bottom_performers=bottom_performers,
                object_type=request.object_type,
                performance_metric=request.performance_metric,
                time_range=request.time_range,
                generated_at=datetime.utcnow()
            )
            
        except Exception as e:
            logger.error(f"Error identifying bottom performers: {e}")
            raise
    
    async def _get_campaigns_by_objective(
        self, 
        context: AggregationContext
    ) -> Dict[str, List[Dict[str, Any]]]:
        """Get campaigns grouped by objective"""
        try:
            # Get all campaigns
            campaigns_response = await self.client.get(
                f"{context.ad_account_id}/campaigns",
                params={
                    "fields": "id,name,objective,status",
                    "limit": 1000
                }
            )
            
            campaigns = campaigns_response.get("data", [])
            
            # Group by objective
            objective_groups = {}
            for campaign in campaigns:
                objective = campaign.get("objective", "UNKNOWN")
                if objective not in objective_groups:
                    objective_groups[objective] = []
                objective_groups[objective].append(campaign)
            
            return objective_groups
            
        except Exception as e:
            logger.error(f"Error getting campaigns by objective: {e}")
            raise
    
    async def _get_objects_by_status(
        self, 
        context: AggregationContext, 
        object_type: str
    ) -> Dict[str, List[Dict[str, Any]]]:
        """Get objects grouped by status"""
        try:
            # Get objects based on type
            if object_type == "campaign":
                path = f"{context.ad_account_id}/campaigns"
            elif object_type == "adset":
                path = f"{context.ad_account_id}/adsets"
            elif object_type == "ad":
                path = f"{context.ad_account_id}/ads"
            else:
                raise ValueError(f"Unsupported object type: {object_type}")
            
            objects_response = await self.client.get(
                path,
                params={
                    "fields": "id,name,status",
                    "limit": 1000
                }
            )
            
            objects = objects_response.get("data", [])
            
            # Group by status
            status_groups = {}
            for obj in objects:
                status = obj.get("status", "UNKNOWN")
                if status not in status_groups:
                    status_groups[status] = []
                status_groups[status].append(obj)
            
            return status_groups
            
        except Exception as e:
            logger.error(f"Error getting objects by status: {e}")
            raise
    
    async def _get_spending_data(
        self, 
        context: AggregationContext, 
        object_type: str
    ) -> List[Dict[str, Any]]:
        """Get spending data for objects"""
        try:
            # Get insights with spending fields
            if object_type == "campaign":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "campaign",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            elif object_type == "adset":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "adset",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            elif object_type == "ad":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "ad",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            else:
                raise ValueError(f"Unsupported object type: {object_type}")
            
            if context.filters:
                params["filtering"] = context.filters
            
            insights_response = await self.client.get(
                path,
                params=params
            )

            return insights_response.get("data", [])

        except Exception as e:
            logger.error(f"Error getting spending data: {e}")
            raise
    
    async def _get_performance_data(
        self, 
        context: AggregationContext, 
        object_type: str
    ) -> List[Dict[str, Any]]:
        """Get performance data for objects"""
        try:
            # Get insights with performance fields
            if object_type == "campaign":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "campaign",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            elif object_type == "adset":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "adset",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            elif object_type == "ad":
                path = f"{context.ad_account_id}/insights"
                params = {
                    "level": "ad",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "limit": 1000
                }
            else:
                raise ValueError(f"Unsupported object type: {object_type}")
            
            if context.filters:
                params["filtering"] = context.filters
            
            insights_response = await self.client.get(
                path,
                params=params
            )

            return insights_response.get("data", [])

        except Exception as e:
            logger.error(f"Error getting performance data: {e}")
            raise
    
    async def _aggregate_campaigns_performance(
        self, 
        campaigns: List[Dict[str, Any]], 
        context: AggregationContext,
        objective: str
    ) -> ObjectiveSummary:
        """Aggregate performance for campaigns with same objective"""
        try:
            campaign_ids = [campaign["id"] for campaign in campaigns]
            
            # Get insights for these campaigns
            insights_response = await self.client.get(
                f"{context.ad_account_id}/insights",
                params={
                    "level": "campaign",
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "filtering": [{"field": "campaign.id", "operator": "IN", "value": campaign_ids}],
                    "limit": 1000
                }
            )

            insights = insights_response.get("data", [])

            # Calculate aggregated metrics
            metrics = self._calculate_aggregated_metrics(insights)

            return ObjectiveSummary(
                objective=objective,
                campaign_count=len(campaigns),
                metrics=metrics,
                top_campaigns=[campaign["name"] for campaign in campaigns[:5]]
            )
            
        except Exception as e:
            logger.error(f"Error aggregating campaigns performance: {e}")
            raise
    
    async def _aggregate_objects_performance(
        self, 
        objects: List[Dict[str, Any]], 
        context: AggregationContext,
        status: str,
        object_type: str
    ) -> StatusSummary:
        """Aggregate performance for objects with same status"""
        try:
            object_ids = [obj["id"] for obj in objects]
            
            # Get insights for these objects
            level = object_type if object_type in ["campaign", "adset", "ad"] else "campaign"
            field_name = f"{object_type}.id" if object_type != "campaign" else "campaign.id"
            
            insights_response = await self.client.get(
                f"{context.ad_account_id}/insights",
                params={
                    "level": level,
                    "fields": ",".join(context.fields),
                    "time_range": context.time_range,
                    "filtering": [{"field": field_name, "operator": "IN", "value": object_ids}],
                    "limit": 1000
                }
            )
            
            insights = insights_response.get("data", [])
            
            # Calculate aggregated metrics
            metrics = self._calculate_aggregated_metrics(insights)
            
            return StatusSummary(
                status=status,
                object_count=len(objects),
                metrics=metrics,
                top_objects=[obj["name"] for obj in objects[:5]]
            )
            
        except Exception as e:
            logger.error(f"Error aggregating objects performance: {e}")
            raise
    
    async def _calculate_spending_distribution(
        self, 
        spending_data: List[Dict[str, Any]], 
        percentiles: List[float]
    ) -> SpendingDistribution:
        """Calculate spending distribution metrics"""
        try:
            spends = [float(data.get("spend", 0)) for data in spending_data if data.get("spend")]
            
            if not spends:
                return SpendingDistribution(
                    total_spend=0,
                    average_spend=0,
                    median_spend=0,
                    percentile_breakdown={},
                    spend_range={"min": 0, "max": 0}
                )
            
            spends.sort()
            total_spend = sum(spends)
            average_spend = total_spend / len(spends)
            median_spend = spends[len(spends) // 2]
            
            # Calculate percentiles
            percentile_breakdown = {}
            for percentile in percentiles:
                index = int((percentile / 100) * len(spends))
                if index >= len(spends):
                    index = len(spends) - 1
                percentile_breakdown[f"p{int(percentile)}"] = spends[index]
            
            return SpendingDistribution(
                total_spend=total_spend,
                average_spend=average_spend,
                median_spend=median_spend,
                percentile_breakdown=percentile_breakdown,
                spend_range={"min": min(spends), "max": max(spends)}
            )
            
        except Exception as e:
            logger.error(f"Error calculating spending distribution: {e}")
            raise
    
    async def _calculate_performance_summary(
        self, 
        performance_data: List[Dict[str, Any]], 
        include_trends: bool
    ) -> PerformanceSummary:
        """Calculate comprehensive performance summary"""
        try:
            # Calculate aggregated metrics
            metrics = self._calculate_aggregated_metrics(performance_data)
            
            # Calculate trends if requested
            trends = None
            if include_trends:
                trends = await self._calculate_performance_trends(performance_data)
            
            return PerformanceSummary(
                metrics=metrics,
                trends=trends,
                data_points=len(performance_data)
            )
            
        except Exception as e:
            logger.error(f"Error calculating performance summary: {e}")
            raise
    
    async def _identify_top_performers(
        self, 
        performance_data: List[Dict[str, Any]], 
        performance_metric: str,
        limit: int,
        minimum_spend: float
    ) -> List[TopPerformer]:
        """Identify top performing objects"""
        try:
            # Filter by minimum spend
            filtered_data = [
                data for data in performance_data 
                if float(data.get("spend", 0)) >= minimum_spend
            ]
            
            # Sort by performance metric
            sorted_data = sorted(
                filtered_data,
                key=lambda x: float(x.get(performance_metric, 0)),
                reverse=True
            )
            
            # Create top performers list
            top_performers = []
            for i, data in enumerate(sorted_data[:limit]):
                top_performers.append(TopPerformer(
                    rank=i + 1,
                    object_id=data.get("campaign_id") or data.get("adset_id") or data.get("ad_id"),
                    object_name=data.get("campaign_name") or data.get("adset_name") or data.get("ad_name"),
                    performance_metric_value=float(data.get(performance_metric, 0)),
                    spend=float(data.get("spend", 0)),
                    impressions=int(data.get("impressions", 0)),
                    clicks=int(data.get("clicks", 0))
                ))
            
            return top_performers
            
        except Exception as e:
            logger.error(f"Error identifying top performers: {e}")
            raise
    
    async def _identify_bottom_performers(
        self, 
        performance_data: List[Dict[str, Any]], 
        performance_metric: str,
        limit: int,
        minimum_spend: float
    ) -> List[BottomPerformer]:
        """Identify bottom performing objects"""
        try:
            # Filter by minimum spend
            filtered_data = [
                data for data in performance_data 
                if float(data.get("spend", 0)) >= minimum_spend
            ]
            
            # Sort by performance metric (ascending for bottom performers)
            sorted_data = sorted(
                filtered_data,
                key=lambda x: float(x.get(performance_metric, 0))
            )
            
            # Create bottom performers list
            bottom_performers = []
            for i, data in enumerate(sorted_data[:limit]):
                bottom_performers.append(BottomPerformer(
                    rank=i + 1,
                    object_id=data.get("campaign_id") or data.get("adset_id") or data.get("ad_id"),
                    object_name=data.get("campaign_name") or data.get("adset_name") or data.get("ad_name"),
                    performance_metric_value=float(data.get(performance_metric, 0)),
                    spend=float(data.get("spend", 0)),
                    impressions=int(data.get("impressions", 0)),
                    clicks=int(data.get("clicks", 0)),
                    improvement_potential=self._calculate_improvement_potential(data, performance_metric)
                ))
            
            return bottom_performers
            
        except Exception as e:
            logger.error(f"Error identifying bottom performers: {e}")
            raise
    
    def _calculate_aggregated_metrics(
        self, 
        insights: List[Dict[str, Any]]
    ) -> PerformanceMetrics:
        """Calculate aggregated performance metrics"""
        try:
            if not insights:
                return PerformanceMetrics(
                    spend=0,
                    impressions=0,
                    clicks=0,
                    conversions=0,
                    cpm=0,
                    cpc=0,
                    ctr=0,
                    conversion_rate=0
                )
            
            # Sum up metrics
            total_spend = sum(float(data.get("spend", 0)) for data in insights)
            total_impressions = sum(int(data.get("impressions", 0)) for data in insights)
            total_clicks = sum(int(data.get("clicks", 0)) for data in insights)
            total_conversions = sum(int(data.get("conversions", 0)) for data in insights)
            
            # Calculate rates
            cpm = (total_spend / total_impressions * 1000) if total_impressions > 0 else 0
            cpc = (total_spend / total_clicks) if total_clicks > 0 else 0
            ctr = (total_clicks / total_impressions * 100) if total_impressions > 0 else 0
            conversion_rate = (total_conversions / total_clicks * 100) if total_clicks > 0 else 0
            
            return PerformanceMetrics(
                spend=total_spend,
                impressions=total_impressions,
                clicks=total_clicks,
                conversions=total_conversions,
                cpm=cpm,
                cpc=cpc,
                ctr=ctr,
                conversion_rate=conversion_rate
            )
            
        except Exception as e:
            logger.error(f"Error calculating aggregated metrics: {e}")
            raise
    
    async def _calculate_performance_trends(
        self, 
        performance_data: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """Calculate performance trends"""
        try:
            # This is a simplified trend calculation
            # In a real implementation, you'd compare with previous periods
            return {
                "spend_trend": "stable",  # Would calculate actual trend
                "performance_trend": "improving",  # Would calculate actual trend
                "efficiency_trend": "stable"  # Would calculate actual trend
            }
            
        except Exception as e:
            logger.error(f"Error calculating performance trends: {e}")
            raise
    
    def _calculate_improvement_potential(
        self, 
        data: Dict[str, Any], 
        performance_metric: str
    ) -> str:
        """Calculate improvement potential for bottom performers"""
        try:
            # Simple improvement potential calculation
            metric_value = float(data.get(performance_metric, 0))
            spend = float(data.get("spend", 0))
            
            if spend > 0 and metric_value > 0:
                efficiency = metric_value / spend
                if efficiency < 0.1:
                    return "high"
                elif efficiency < 0.5:
                    return "medium"
                else:
                    return "low"
            else:
                return "unknown"
                
        except Exception as e:
            logger.error(f"Error calculating improvement potential: {e}")
            return "unknown"


# Global aggregator instance
_aggregator: Optional[DataAggregator] = None


def get_aggregator() -> DataAggregator:
    """Get the global DataAggregator instance"""
    global _aggregator
    if _aggregator is None:
        from .meta_client import get_client
        _aggregator = DataAggregator(get_client())
    return _aggregator
