"""
Helper utilities for campaign health, optimization suggestions, and analysis.

This module provides intelligent analysis tools to help optimize
Meta Ads campaigns and provide actionable insights.
"""

from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
from dataclasses import dataclass
from enum import Enum

from .logging import get_logger
from .meta_client import MetaClient
from .errors import MetaApiError

logger = get_logger(__name__)


class HealthStatus(str, Enum):
    """Campaign health status levels."""
    EXCELLENT = "excellent"
    GOOD = "good"
    WARNING = "warning"
    CRITICAL = "critical"


class OptimizationType(str, Enum):
    """Types of optimization suggestions."""
    BUDGET = "budget"
    BID = "bid"
    AUDIENCE = "audience"
    CREATIVE = "creative"
    SCHEDULING = "scheduling"
    TARGETING = "targeting"


@dataclass
class HealthMetric:
    """Represents a health metric."""
    name: str
    value: float
    threshold: float
    status: HealthStatus
    description: str
    recommendation: str


@dataclass
class CampaignHealth:
    """Represents overall campaign health."""
    campaign_id: str
    campaign_name: str
    overall_status: HealthStatus
    score: float  # 0-100
    metrics: List[HealthMetric]
    critical_issues: List[str]
    recommendations: List[str]


@dataclass
class OptimizationSuggestion:
    """Represents an optimization suggestion."""
    type: OptimizationType
    priority: int  # 1-5, 5 being highest
    title: str
    description: str
    impact: str  # "high", "medium", "low"
    effort: str  # "low", "medium", "high"
    estimated_improvement: str
    action_required: str


@dataclass
class CreativePerformance:
    """Represents creative performance analysis."""
    creative_id: str
    creative_name: str
    impressions: int
    clicks: int
    ctr: float
    cpm: float
    cpc: float
    conversions: int
    cost_per_conversion: float
    performance_score: float
    recommendations: List[str]


@dataclass
class AudienceOverlap:
    """Represents audience overlap analysis."""
    audience_1_id: str
    audience_1_name: str
    audience_2_id: str
    audience_2_name: str
    overlap_percentage: float
    overlap_size: int
    recommendation: str


class CampaignAnalyzer:
    """Analyzes campaigns and provides optimization insights."""
    
    def __init__(self, client: MetaClient):
        self._client = client
    
    async def analyze_campaign_health(
        self,
        campaign_id: str,
        days_back: int = 7,
    ) -> CampaignHealth:
        """
        Analyze campaign health and provide recommendations.
        
        Args:
            campaign_id: Campaign ID to analyze
            days_back: Number of days to analyze (default: 7)
        
        Returns:
            CampaignHealth: Comprehensive health analysis
        """
        try:
            # Get campaign details
            campaign = await self._client.get(
                campaign_id,
                params={"fields": "id,name,status,objective,daily_budget,lifetime_budget"},
            )
            
            # Get insights for the specified period
            end_time = datetime.now()
            start_time = end_time - timedelta(days=days_back)
            
            insights = await self._client.get(
                f"{campaign_id}/insights",
                params={
                    "fields": "impressions,clicks,ctr,cpm,cpc,spend,conversions,cost_per_conversion",
                    "time_range": {
                        "since": start_time.strftime("%Y-%m-%d"),
                        "until": end_time.strftime("%Y-%m-%d"),
                    },
                },
            )
            
            # Analyze metrics
            metrics = self._analyze_metrics(insights.get("data", [{}])[0])
            
            # Calculate overall health score
            score = self._calculate_health_score(metrics)
            overall_status = self._determine_health_status(score)
            
            # Generate recommendations
            recommendations = self._generate_health_recommendations(metrics, campaign)
            critical_issues = [m.description for m in metrics if m.status == HealthStatus.CRITICAL]
            
            return CampaignHealth(
                campaign_id=campaign_id,
                campaign_name=campaign.get("name", "Unknown"),
                overall_status=overall_status,
                score=score,
                metrics=metrics,
                critical_issues=critical_issues,
                recommendations=recommendations,
            )
            
        except MetaApiError as exc:
            logger.error("Campaign health analysis failed", error=exc.to_dict())
            raise
    
    async def get_optimization_suggestions(
        self,
        campaign_id: str,
        days_back: int = 7,
    ) -> List[OptimizationSuggestion]:
        """
        Get optimization suggestions for a campaign.
        
        Args:
            campaign_id: Campaign ID to analyze
            days_back: Number of days to analyze
        
        Returns:
            List[OptimizationSuggestion]: Prioritized optimization suggestions
        """
        try:
            suggestions = []
            
            # Get campaign and insights data
            campaign = await self._client.get(
                campaign_id,
                params={"fields": "id,name,objective,daily_budget,status"},
            )
            
            end_time = datetime.now()
            start_time = end_time - timedelta(days=days_back)
            
            insights = await self._client.get(
                f"{campaign_id}/insights",
                params={
                    "fields": "impressions,clicks,ctr,cpm,cpc,spend,conversions,cost_per_conversion,reach,frequency",
                    "time_range": {
                        "since": start_time.strftime("%Y-%m-%d"),
                        "until": end_time.strftime("%Y-%m-%d"),
                    },
                },
            )
            
            insight_data = insights.get("data", [{}])[0]
            
            # Budget optimization suggestions
            budget_suggestions = self._analyze_budget_optimization(campaign, insight_data)
            suggestions.extend(budget_suggestions)
            
            # Bid optimization suggestions
            bid_suggestions = self._analyze_bid_optimization(campaign, insight_data)
            suggestions.extend(bid_suggestions)
            
            # Audience optimization suggestions
            audience_suggestions = await self._analyze_audience_optimization(campaign_id, insight_data)
            suggestions.extend(audience_suggestions)
            
            # Creative optimization suggestions
            creative_suggestions = await self._analyze_creative_optimization(campaign_id, insight_data)
            suggestions.extend(creative_suggestions)
            
            # Sort by priority
            suggestions.sort(key=lambda x: x.priority, reverse=True)
            
            return suggestions
            
        except MetaApiError as exc:
            logger.error("Optimization suggestions failed", error=exc.to_dict())
            raise
    
    async def analyze_creative_performance(
        self,
        campaign_id: str,
        days_back: int = 7,
    ) -> List[CreativePerformance]:
        """
        Analyze creative performance within a campaign.
        
        Args:
            campaign_id: Campaign ID to analyze
            days_back: Number of days to analyze
        
        Returns:
            List[CreativePerformance]: Creative performance analysis
        """
        try:
            # Get ads in the campaign
            ads = await self._client.get(
                f"{campaign_id}/ads",
                params={"fields": "id,name,creative{id,name}"},
            )
            
            end_time = datetime.now()
            start_time = end_time - timedelta(days=days_back)
            
            creative_performances = []
            
            for ad in ads.get("data", []):
                # Get ad insights
                ad_insights = await self._client.get(
                    f"{ad['id']}/insights",
                    params={
                        "fields": "impressions,clicks,ctr,cpm,cpc,spend,conversions,cost_per_conversion",
                        "time_range": {
                            "since": start_time.strftime("%Y-%m-%d"),
                            "until": end_time.strftime("%Y-%m-%d"),
                        },
                    },
                )
                
                insight_data = ad_insights.get("data", [{}])[0]
                creative = ad.get("creative", {})
                
                if creative:
                    performance = CreativePerformance(
                        creative_id=creative.get("id", ""),
                        creative_name=creative.get("name", ""),
                        impressions=int(insight_data.get("impressions", 0)),
                        clicks=int(insight_data.get("clicks", 0)),
                        ctr=float(insight_data.get("ctr", 0)),
                        cpm=float(insight_data.get("cpm", 0)),
                        cpc=float(insight_data.get("cpc", 0)),
                        conversions=int(insight_data.get("conversions", 0)),
                        cost_per_conversion=float(insight_data.get("cost_per_conversion", 0)),
                        performance_score=self._calculate_creative_score(insight_data),
                        recommendations=self._generate_creative_recommendations(insight_data),
                    )
                    creative_performances.append(performance)
            
            # Sort by performance score
            creative_performances.sort(key=lambda x: x.performance_score, reverse=True)
            
            return creative_performances
            
        except MetaApiError as exc:
            logger.error("Creative performance analysis failed", error=exc.to_dict())
            raise
    
    async def detect_audience_overlap(
        self,
        ad_account_id: str,
        audience_ids: Optional[List[str]] = None,
    ) -> List[AudienceOverlap]:
        """
        Detect audience overlap between custom audiences.
        
        Args:
            ad_account_id: Ad account ID
            audience_ids: Specific audience IDs to check (optional)
        
        Returns:
            List[AudienceOverlap]: Audience overlap analysis
        """
        try:
            # Get custom audiences
            audiences = await self._client.get(
                f"{ad_account_id}/customaudiences",
                params={"fields": "id,name,approximate_count"},
            )
            
            audience_list = audiences.get("data", [])
            if audience_ids:
                audience_list = [a for a in audience_list if a["id"] in audience_ids]
            
            overlaps = []
            
            # Check overlap between all pairs
            for i, audience_1 in enumerate(audience_list):
                for audience_2 in audience_list[i + 1:]:
                    # In a real implementation, this would use Meta's audience overlap API
                    # For now, we'll simulate the analysis
                    overlap_percentage = self._simulate_audience_overlap(
                        audience_1["id"], audience_2["id"]
                    )
                    
                    if overlap_percentage > 10:  # Only report significant overlaps
                        overlap = AudienceOverlap(
                            audience_1_id=audience_1["id"],
                            audience_1_name=audience_1["name"],
                            audience_2_id=audience_2["id"],
                            audience_2_name=audience_2["name"],
                            overlap_percentage=overlap_percentage,
                            overlap_size=int(
                                audience_1.get("approximate_count", 0) * overlap_percentage / 100
                            ),
                            recommendation=self._generate_overlap_recommendation(overlap_percentage),
                        )
                        overlaps.append(overlap)
            
            # Sort by overlap percentage
            overlaps.sort(key=lambda x: x.overlap_percentage, reverse=True)
            
            return overlaps
            
        except MetaApiError as exc:
            logger.error("Audience overlap detection failed", error=exc.to_dict())
            raise
    
    def _analyze_metrics(self, insight_data: Dict[str, Any]) -> List[HealthMetric]:
        """Analyze campaign metrics and determine health status."""
        metrics = []
        
        # CTR Analysis
        ctr = float(insight_data.get("ctr", 0))
        ctr_threshold = 1.0  # 1% CTR threshold
        ctr_status = HealthStatus.EXCELLENT if ctr >= 2.0 else HealthStatus.GOOD if ctr >= 1.0 else HealthStatus.WARNING if ctr >= 0.5 else HealthStatus.CRITICAL
        
        metrics.append(HealthMetric(
            name="Click-Through Rate",
            value=ctr,
            threshold=ctr_threshold,
            status=ctr_status,
            description=f"CTR is {ctr:.2f}%",
            recommendation="Improve ad creative or targeting" if ctr < 1.0 else "CTR is performing well",
        ))
        
        # CPM Analysis
        cpm = float(insight_data.get("cpm", 0))
        cpm_threshold = 10.0  # $10 CPM threshold
        cpm_status = HealthStatus.EXCELLENT if cpm <= 5.0 else HealthStatus.GOOD if cpm <= 10.0 else HealthStatus.WARNING if cpm <= 20.0 else HealthStatus.CRITICAL
        
        metrics.append(HealthMetric(
            name="Cost Per Mille",
            value=cpm,
            threshold=cpm_threshold,
            status=cpm_status,
            description=f"CPM is ${cpm:.2f}",
            recommendation="Optimize targeting or creative" if cpm > 10.0 else "CPM is within acceptable range",
        ))
        
        # Conversion Rate Analysis
        conversions = int(insight_data.get("conversions", 0))
        clicks = int(insight_data.get("clicks", 0))
        conversion_rate = (conversions / clicks * 100) if clicks > 0 else 0
        conv_threshold = 2.0  # 2% conversion rate threshold
        conv_status = HealthStatus.EXCELLENT if conversion_rate >= 5.0 else HealthStatus.GOOD if conversion_rate >= 2.0 else HealthStatus.WARNING if conversion_rate >= 1.0 else HealthStatus.CRITICAL
        
        metrics.append(HealthMetric(
            name="Conversion Rate",
            value=conversion_rate,
            threshold=conv_threshold,
            status=conv_status,
            description=f"Conversion rate is {conversion_rate:.2f}%",
            recommendation="Improve landing page or audience targeting" if conversion_rate < 2.0 else "Conversion rate is performing well",
        ))
        
        return metrics
    
    def _calculate_health_score(self, metrics: List[HealthMetric]) -> float:
        """Calculate overall health score (0-100)."""
        if not metrics:
            return 0.0
        
        total_score = 0.0
        for metric in metrics:
            if metric.status == HealthStatus.EXCELLENT:
                total_score += 100
            elif metric.status == HealthStatus.GOOD:
                total_score += 75
            elif metric.status == HealthStatus.WARNING:
                total_score += 50
            else:  # CRITICAL
                total_score += 25
        
        return total_score / len(metrics)
    
    def _determine_health_status(self, score: float) -> HealthStatus:
        """Determine overall health status based on score."""
        if score >= 90:
            return HealthStatus.EXCELLENT
        elif score >= 75:
            return HealthStatus.GOOD
        elif score >= 50:
            return HealthStatus.WARNING
        else:
            return HealthStatus.CRITICAL
    
    def _generate_health_recommendations(
        self,
        metrics: List[HealthMetric],
        campaign: Dict[str, Any],
    ) -> List[str]:
        """Generate health recommendations."""
        recommendations = []
        
        for metric in metrics:
            if metric.status in [HealthStatus.WARNING, HealthStatus.CRITICAL]:
                recommendations.append(metric.recommendation)
        
        # Budget recommendations
        daily_budget = campaign.get("daily_budget")
        if daily_budget and daily_budget < 50:
            recommendations.append("Consider increasing daily budget for better reach and optimization")
        
        return recommendations
    
    def _analyze_budget_optimization(
        self,
        campaign: Dict[str, Any],
        insight_data: Dict[str, Any],
    ) -> List[OptimizationSuggestion]:
        """Analyze budget optimization opportunities."""
        suggestions = []
        
        spend = float(insight_data.get("spend", 0))
        daily_budget = campaign.get("daily_budget", 0)
        
        if daily_budget > 0:
            budget_utilization = (spend / 7) / daily_budget * 100  # Assuming 7 days of data
            
            if budget_utilization < 80:
                suggestions.append(OptimizationSuggestion(
                    type=OptimizationType.BUDGET,
                    priority=3,
                    title="Increase Daily Budget",
                    description=f"Budget utilization is only {budget_utilization:.1f}%",
                    impact="medium",
                    effort="low",
                    estimated_improvement="10-20% more reach",
                    action_required="Increase daily budget by 20-30%",
                ))
            elif budget_utilization > 95:
                suggestions.append(OptimizationSuggestion(
                    type=OptimizationType.BUDGET,
                    priority=4,
                    title="Budget Constraint Detected",
                    description="Campaign is hitting budget limits",
                    impact="high",
                    effort="low",
                    estimated_improvement="15-25% more conversions",
                    action_required="Increase daily budget or optimize for efficiency",
                ))
        
        return suggestions
    
    def _analyze_bid_optimization(
        self,
        campaign: Dict[str, Any],
        insight_data: Dict[str, Any],
    ) -> List[OptimizationSuggestion]:
        """Analyze bid optimization opportunities."""
        suggestions = []
        
        cpc = float(insight_data.get("cpc", 0))
        cpm = float(insight_data.get("cpm", 0))
        
        if cpc > 2.0:  # High CPC threshold
            suggestions.append(OptimizationSuggestion(
                type=OptimizationType.BID,
                priority=4,
                title="High Cost Per Click",
                description=f"CPC is ${cpc:.2f}, which is above optimal range",
                impact="high",
                effort="medium",
                estimated_improvement="20-30% cost reduction",
                action_required="Lower bid amounts or switch to cost cap bidding",
            ))
        
        if cpm > 15.0:  # High CPM threshold
            suggestions.append(OptimizationSuggestion(
                type=OptimizationType.BID,
                priority=3,
                title="High Cost Per Mille",
                description=f"CPM is ${cpm:.2f}, indicating competitive bidding",
                impact="medium",
                effort="medium",
                estimated_improvement="15-25% cost reduction",
                action_required="Review targeting and consider broader audiences",
            ))
        
        return suggestions
    
    async def _analyze_audience_optimization(
        self,
        campaign_id: str,
        insight_data: Dict[str, Any],
    ) -> List[OptimizationSuggestion]:
        """Analyze audience optimization opportunities."""
        suggestions = []
        
        # Get ad sets to analyze audience performance
        adsets = await self._client.get(
            f"{campaign_id}/adsets",
            params={"fields": "id,name,targeting"},
        )
        
        for adset in adsets.get("data", []):
            # Get ad set insights
            adset_insights = await self._client.get(
                f"{adset['id']}/insights",
                params={"fields": "impressions,clicks,ctr,cpm,spend"},
            )
            
            adset_data = adset_insights.get("data", [{}])[0]
            ctr = float(adset_data.get("ctr", 0))
            
            if ctr < 0.5:  # Low CTR threshold
                suggestions.append(OptimizationSuggestion(
                    type=OptimizationType.AUDIENCE,
                    priority=4,
                    title="Low Audience Engagement",
                    description=f"Ad set '{adset['name']}' has CTR of {ctr:.2f}%",
                    impact="high",
                    effort="medium",
                    estimated_improvement="30-50% CTR improvement",
                    action_required="Refine audience targeting or create new audiences",
                ))
        
        return suggestions
    
    async def _analyze_creative_optimization(
        self,
        campaign_id: str,
        insight_data: Dict[str, Any],
    ) -> List[OptimizationSuggestion]:
        """Analyze creative optimization opportunities."""
        suggestions = []
        
        # Get ads to analyze creative performance
        ads = await self._client.get(
            f"{campaign_id}/ads",
            params={"fields": "id,name,creative"},
        )
        
        creative_performances = []
        for ad in ads.get("data", []):
            ad_insights = await self._client.get(
                f"{ad['id']}/insights",
                params={"fields": "impressions,clicks,ctr"},
            )
            
            ad_data = ad_insights.get("data", [{}])[0]
            ctr = float(ad_data.get("ctr", 0))
            creative_performances.append((ad['name'], ctr))
        
        # Find underperforming creatives
        if creative_performances:
            avg_ctr = sum(ctr for _, ctr in creative_performances) / len(creative_performances)
            underperforming = [name for name, ctr in creative_performances if ctr < avg_ctr * 0.5]
            
            if underperforming:
                suggestions.append(OptimizationSuggestion(
                    type=OptimizationType.CREATIVE,
                    priority=3,
                    title="Underperforming Creatives",
                    description=f"{len(underperforming)} creatives performing below average",
                    impact="medium",
                    effort="high",
                    estimated_improvement="20-40% CTR improvement",
                    action_required="Create new creative variations or pause underperforming ads",
                ))
        
        return suggestions
    
    def _calculate_creative_score(self, insight_data: Dict[str, Any]) -> float:
        """Calculate creative performance score."""
        ctr = float(insight_data.get("ctr", 0))
        cpm = float(insight_data.get("cpm", 0))
        conversions = int(insight_data.get("conversions", 0))
        
        # Simple scoring algorithm
        score = 0
        score += min(ctr * 20, 50)  # CTR component (max 50 points)
        score += max(0, 30 - cpm)  # CPM component (max 30 points)
        score += min(conversions * 2, 20)  # Conversions component (max 20 points)
        
        return min(score, 100)
    
    def _generate_creative_recommendations(self, insight_data: Dict[str, Any]) -> List[str]:
        """Generate creative recommendations."""
        recommendations = []
        
        ctr = float(insight_data.get("ctr", 0))
        cpm = float(insight_data.get("cpm", 0))
        
        if ctr < 1.0:
            recommendations.append("Improve ad creative to increase engagement")
        
        if cpm > 15.0:
            recommendations.append("Consider refreshing creative to reduce costs")
        
        if int(insight_data.get("conversions", 0)) == 0:
            recommendations.append("Test new creative variations to improve conversions")
        
        return recommendations
    
    def _simulate_audience_overlap(self, audience_1_id: str, audience_2_id: str) -> float:
        """Simulate audience overlap percentage."""
        # In a real implementation, this would use Meta's audience overlap API
        # For now, we'll return a simulated value
        import random
        return random.uniform(5, 60)
    
    def _generate_overlap_recommendation(self, overlap_percentage: float) -> str:
        """Generate overlap recommendation."""
        if overlap_percentage > 50:
            return "High overlap detected. Consider consolidating audiences or excluding one from the other."
        elif overlap_percentage > 25:
            return "Moderate overlap. Monitor performance and consider audience exclusions."
        else:
            return "Low overlap. Audiences are well-segmented."
