"""
Advanced bid control tools for Meta Ads campaigns and ad sets.

This module provides sophisticated bid management with auction insights,
simulation capabilities, and optimization recommendations.
"""

from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

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

logger = get_logger(__name__)


class BidManager:
    """Manages bid operations for campaigns and ad sets."""

    def __init__(self, client: MetaClient):
        self._client = client

    async def get_current_bid(
        self,
        entity_id: str,
        entity_type: str,
    ) -> Dict[str, Any]:
        """
        Get current bid strategy and amounts for a campaign or ad set.
        """
        try:
            # Get entity details
            response = await self._client.get(
                entity_id,
                params={"fields": "bid_strategy,bid_amount,optimization_goal,status"}
            )
            entity_data = response.get("data", {})

            # Get recent performance data
            performance_response = await self._client.get(
                f"{entity_id}/insights",
                params={
                    "fields": "spend,impressions,clicks,ctr,cpc,conversions",
                    "time_range": {"since": (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d"), "until": datetime.now().strftime("%Y-%m-%d")},
                }
            )
            performance_data = performance_response.get("data", [])

            # Calculate performance metrics
            total_spend = sum(float(insight.get("spend", 0)) for insight in performance_data)
            total_impressions = sum(int(insight.get("impressions", 0)) for insight in performance_data)
            total_clicks = sum(int(insight.get("clicks", 0)) for insight in performance_data)
            total_conversions = sum(
                sum(action.get("value", 0) for action in insight.get("actions", [])
                    if action.get("action_type") == "offsite_conversion.fb_pixel_purchase")
                for insight in performance_data
            )

            avg_ctr = total_clicks / total_impressions if total_impressions > 0 else 0
            avg_cpc = total_spend / total_clicks if total_clicks > 0 else 0
            cost_per_conversion = total_spend / total_conversions if total_conversions > 0 else 0

            bid_strategy = entity_data.get("bid_strategy", "unknown")
            bid_amount = float(entity_data.get("bid_amount", 0))
            optimization_goal = entity_data.get("optimization_goal", "unknown")

            return {
                "entity_id": entity_id,
                "entity_type": entity_type,
                "current_bid_config": {
                    "bid_strategy": bid_strategy,
                    "bid_amount": bid_amount,
                    "optimization_goal": optimization_goal,
                },
                "performance_metrics": {
                    "avg_ctr": avg_ctr,
                    "avg_cpc": avg_cpc,
                    "cost_per_conversion": cost_per_conversion,
                    "total_conversions": total_conversions,
                },
                "bid_effectiveness": {
                    "strategy_performance": "good" if avg_cpc < 2.0 and cost_per_conversion < 50 else "needs_optimization",
                    "recommendation": self._get_bid_recommendation(bid_strategy, avg_cpc, cost_per_conversion),
                },
                "next_recommended_actions": [
                    "Monitor bid performance vs. target metrics",
                    "Consider bid strategy optimization",
                    "Test different bid amounts",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to get current bid", entity_id=entity_id, error=exc.to_dict())
            raise

    async def set_bid_strategy(
        self,
        entity_id: str,
        entity_type: str,
        bid_strategy: str,
        bid_amount: Optional[float] = None,
    ) -> Dict[str, Any]:
        """
        Change bid strategy for a campaign or ad set.
        """
        try:
            # Validate bid strategy
            valid_strategies = [
                "LOWEST_COST_WITHOUT_CAP",
                "LOWEST_COST_WITH_BID_CAP", 
                "TARGET_COST",
                "BID_CAP"
            ]
            if bid_strategy not in valid_strategies:
                return {
                    "success": False,
                    "error": f"Invalid bid strategy: {bid_strategy}",
                    "suggested_fix": f"Use one of: {', '.join(valid_strategies)}",
                }

            # Validate bid amount for strategies that require it
            if bid_strategy in ["TARGET_COST", "BID_CAP"] and bid_amount is None:
                return {
                    "success": False,
                    "error": f"Bid amount required for strategy: {bid_strategy}",
                    "suggested_fix": "Provide bid_amount parameter",
                }

            # Prepare update data
            update_data = {"bid_strategy": bid_strategy}
            if bid_amount is not None:
                update_data["bid_amount"] = bid_amount

            # Update bid strategy
            await self._client.post(
                entity_id,
                body=update_data,
                encoding=self._client.GraphBodyEncoding.FORM,
            )

            # Get updated bid info
            updated_bid = await self.get_current_bid(entity_id, entity_type)

            return {
                "success": True,
                "entity_id": entity_id,
                "entity_type": entity_type,
                "new_bid_config": {
                    "bid_strategy": bid_strategy,
                    "bid_amount": bid_amount,
                },
                "updated_bid_info": updated_bid,
                "next_recommended_actions": [
                    "Monitor performance after bid strategy change",
                    "Track cost per conversion vs. target",
                    "Adjust bid amount if needed",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to set bid strategy", entity_id=entity_id, error=exc.to_dict())
            return {
                "success": False,
                "error": exc.context.message,
                "suggested_fix": "Check entity status and bid strategy parameters",
            }

    async def set_bid_cap(
        self,
        entity_id: str,
        entity_type: str,
        bid_cap: float,
        validate_limits: bool = True,
    ) -> Dict[str, Any]:
        """
        Set bid cap for a campaign or ad set.
        """
        try:
            # Validate bid cap if requested
            if validate_limits:
                validation_result = await self._validate_bid_limits(entity_id, entity_type, bid_cap)
                if not validation_result["valid"]:
                    return {
                        "success": False,
                        "error": validation_result["error"],
                        "suggested_fix": validation_result["suggested_fix"],
                    }

            # Update bid cap
            update_data = {
                "bid_strategy": "LOWEST_COST_WITH_BID_CAP",
                "bid_amount": bid_cap,
            }
            await self._client.post(
                entity_id,
                body=update_data,
                encoding=self._client.GraphBodyEncoding.FORM,
            )

            # Get updated bid info
            updated_bid = await self.get_current_bid(entity_id, entity_type)

            return {
                "success": True,
                "entity_id": entity_id,
                "entity_type": entity_type,
                "new_bid_cap": bid_cap,
                "updated_bid_info": updated_bid,
                "next_recommended_actions": [
                    "Monitor spend vs. bid cap",
                    "Track delivery and performance",
                    "Adjust bid cap if delivery is limited",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to set bid cap", entity_id=entity_id, error=exc.to_dict())
            return {
                "success": False,
                "error": exc.context.message,
                "suggested_fix": "Check entity status and bid cap parameters",
            }

    async def set_target_cost(
        self,
        entity_id: str,
        entity_type: str,
        target_cost: float,
        validate_limits: bool = True,
    ) -> Dict[str, Any]:
        """
        Set target cost for a campaign or ad set.
        """
        try:
            # Validate target cost if requested
            if validate_limits:
                validation_result = await self._validate_bid_limits(entity_id, entity_type, target_cost)
                if not validation_result["valid"]:
                    return {
                        "success": False,
                        "error": validation_result["error"],
                        "suggested_fix": validation_result["suggested_fix"],
                    }

            # Update target cost
            update_data = {
                "bid_strategy": "TARGET_COST",
                "bid_amount": target_cost,
            }
            await self._client.post(
                entity_id,
                body=update_data,
                encoding=self._client.GraphBodyEncoding.FORM,
            )

            # Get updated bid info
            updated_bid = await self.get_current_bid(entity_id, entity_type)

            return {
                "success": True,
                "entity_id": entity_id,
                "entity_type": entity_type,
                "new_target_cost": target_cost,
                "updated_bid_info": updated_bid,
                "next_recommended_actions": [
                    "Monitor actual cost vs. target cost",
                    "Track conversion volume and quality",
                    "Adjust target cost based on performance",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to set target cost", entity_id=entity_id, error=exc.to_dict())
            return {
                "success": False,
                "error": exc.context.message,
                "suggested_fix": "Check entity status and target cost parameters",
            }

    async def get_auction_insights(
        self,
        entity_id: str,
        entity_type: str,
        start_date: str,
        end_date: str,
        include_competitors: bool = True,
    ) -> Dict[str, Any]:
        """
        Get auction competitiveness data and insights.
        """
        try:
            # Get auction insights from Meta API
            insights_response = await self._client.get(
                f"{entity_id}/insights",
                params={
                    "fields": "spend,impressions,clicks,ctr,cpc,cpm,reach,frequency",
                    "time_range": {"since": start_date, "until": end_date},
                    "breakdowns": "publisher_platform,placement",
                }
            )
            insights_data = insights_response.get("data", [])

            # Get current bid info
            current_bid = await self.get_current_bid(entity_id, entity_type)

            # Calculate auction competitiveness metrics
            total_spend = sum(float(insight.get("spend", 0)) for insight in insights_data)
            total_impressions = sum(int(insight.get("impressions", 0)) for insight in insights_data)
            total_clicks = sum(int(insight.get("clicks", 0)) for insight in insights_data)
            avg_cpm = sum(float(insight.get("cpm", 0)) for insight in insights_data) / len(insights_data) if insights_data else 0
            avg_cpc = total_spend / total_clicks if total_clicks > 0 else 0

            # Analyze placement performance
            placement_performance = {}
            for insight in insights_data:
                placement = insight.get("breakdowns.placement", "unknown")
                if placement not in placement_performance:
                    placement_performance[placement] = {
                        "placement": placement,
                        "spend": 0,
                        "impressions": 0,
                        "clicks": 0,
                        "cpm": 0,
                    }
                
                placement_performance[placement]["spend"] += float(insight.get("spend", 0))
                placement_performance[placement]["impressions"] += int(insight.get("impressions", 0))
                placement_performance[placement]["clicks"] += int(insight.get("clicks", 0))
                placement_performance[placement]["cpm"] += float(insight.get("cpm", 0))

            # Calculate competitiveness score
            competitiveness_score = self._calculate_competitiveness_score(avg_cpm, avg_cpc, total_impressions)

            result = {
                "entity_id": entity_id,
                "entity_type": entity_type,
                "date_range": {"start": start_date, "end": end_date},
                "auction_metrics": {
                    "total_spend": total_spend,
                    "total_impressions": total_impressions,
                    "total_clicks": total_clicks,
                    "avg_cpm": avg_cpm,
                    "avg_cpc": avg_cpc,
                    "competitiveness_score": competitiveness_score,
                },
                "placement_analysis": list(placement_performance.values()),
                "current_bid_context": current_bid["current_bid_config"],
                "recommendations": self._get_auction_recommendations(competitiveness_score, avg_cpm, avg_cpc),
            }

            # Add competitor analysis if requested
            if include_competitors:
                result["competitor_insights"] = {
                    "market_cpm_range": f"${avg_cpm * 0.8:.2f} - ${avg_cpm * 1.2:.2f}",
                    "competitive_position": "above_market" if avg_cpm > avg_cpm * 1.1 else "below_market" if avg_cpm < avg_cpm * 0.9 else "market_rate",
                    "bid_optimization_opportunity": "increase_bid" if competitiveness_score < 0.3 else "decrease_bid" if competitiveness_score > 0.7 else "maintain_bid",
                }

            result["next_recommended_actions"] = [
                "Analyze placement performance for optimization",
                "Consider bid adjustments based on competitiveness",
                "Monitor auction dynamics over time",
            ]

            return result
        except MetaApiError as exc:
            logger.error("Failed to get auction insights", entity_id=entity_id, error=exc.to_dict())
            raise

    async def simulate_bid_change(
        self,
        entity_id: str,
        entity_type: str,
        new_bid_strategy: str,
        new_bid_amount: Optional[float] = None,
        simulation_period: int = 7,
    ) -> Dict[str, Any]:
        """
        Preview impact before applying bid changes.
        """
        try:
            # Get current performance data
            current_bid = await self.get_current_bid(entity_id, entity_type)
            current_performance = current_bid["performance_metrics"]

            # Get historical performance for simulation
            historical_response = await self._client.get(
                f"{entity_id}/insights",
                params={
                    "fields": "spend,impressions,clicks,ctr,cpc,conversions",
                    "time_range": {"since": (datetime.now() - timedelta(days=simulation_period)).strftime("%Y-%m-%d"), "until": datetime.now().strftime("%Y-%m-%d")},
                }
            )
            historical_data = historical_response.get("data", [])

            # Calculate historical averages
            total_spend = sum(float(insight.get("spend", 0)) for insight in historical_data)
            total_impressions = sum(int(insight.get("impressions", 0)) for insight in historical_data)
            total_clicks = sum(int(insight.get("clicks", 0)) for insight in historical_data)
            total_conversions = sum(
                sum(action.get("value", 0) for action in insight.get("actions", [])
                    if action.get("action_type") == "offsite_conversion.fb_pixel_purchase")
                for insight in historical_data
            )

            avg_daily_spend = total_spend / simulation_period
            avg_daily_impressions = total_impressions / simulation_period
            avg_daily_clicks = total_clicks / simulation_period
            avg_daily_conversions = total_conversions / simulation_period

            # Simulate impact based on bid strategy change
            simulation_results = self._simulate_bid_impact(
                new_bid_strategy,
                new_bid_amount,
                current_performance,
                avg_daily_spend,
                avg_daily_impressions,
                avg_daily_clicks,
                avg_daily_conversions,
            )

            return {
                "entity_id": entity_id,
                "entity_type": entity_type,
                "simulation_period_days": simulation_period,
                "current_config": current_bid["current_bid_config"],
                "proposed_config": {
                    "bid_strategy": new_bid_strategy,
                    "bid_amount": new_bid_amount,
                },
                "historical_baseline": {
                    "avg_daily_spend": avg_daily_spend,
                    "avg_daily_impressions": avg_daily_impressions,
                    "avg_daily_clicks": avg_daily_clicks,
                    "avg_daily_conversions": avg_daily_conversions,
                },
                "simulation_results": simulation_results,
                "risk_assessment": {
                    "risk_level": "low" if simulation_results["confidence"] > 0.7 else "medium" if simulation_results["confidence"] > 0.4 else "high",
                    "recommended_action": "proceed" if simulation_results["confidence"] > 0.6 else "test_small" if simulation_results["confidence"] > 0.3 else "reconsider",
                },
                "next_recommended_actions": [
                    "Review simulation results carefully",
                    "Consider A/B testing the bid change",
                    "Monitor performance closely after implementation",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to simulate bid change", entity_id=entity_id, error=exc.to_dict())
            raise

    def _get_bid_recommendation(
        self,
        bid_strategy: str,
        avg_cpc: float,
        cost_per_conversion: float,
    ) -> str:
        """Get bid strategy recommendation based on performance."""
        if bid_strategy == "LOWEST_COST_WITHOUT_CAP":
            if avg_cpc > 3.0:
                return "Consider adding bid cap to control costs"
            elif cost_per_conversion > 100:
                return "Consider TARGET_COST strategy for better conversion control"
        elif bid_strategy == "LOWEST_COST_WITH_BID_CAP":
            if avg_cpc < 1.0 and cost_per_conversion < 30:
                return "Consider removing bid cap to increase volume"
        elif bid_strategy == "TARGET_COST":
            if cost_per_conversion > 50:
                return "Consider lowering target cost"
            elif cost_per_conversion < 20:
                return "Consider increasing target cost for more volume"
        
        return "Current bid strategy appears optimal"

    def _calculate_competitiveness_score(
        self,
        avg_cpm: float,
        avg_cpc: float,
        total_impressions: int,
    ) -> float:
        """Calculate auction competitiveness score (0-1)."""
        # Simple competitiveness scoring based on CPM and impression volume
        cpm_score = min(avg_cpm / 10.0, 1.0)  # Normalize CPM (assume $10 is high)
        volume_score = min(total_impressions / 100000, 1.0)  # Normalize volume
        
        # Higher CPM and lower volume = more competitive
        competitiveness = (cpm_score + (1 - volume_score)) / 2
        return min(max(competitiveness, 0.0), 1.0)

    def _get_auction_recommendations(
        self,
        competitiveness_score: float,
        avg_cpm: float,
        avg_cpc: float,
    ) -> List[str]:
        """Get auction optimization recommendations."""
        recommendations = []
        
        if competitiveness_score > 0.7:
            recommendations.append("High competition detected - consider increasing bids")
            recommendations.append("Focus on high-performing placements")
        elif competitiveness_score < 0.3:
            recommendations.append("Low competition - consider decreasing bids")
            recommendations.append("Opportunity to expand to more placements")
        
        if avg_cpm > 5.0:
            recommendations.append("High CPM - review targeting and creative quality")
        elif avg_cpm < 1.0:
            recommendations.append("Low CPM - opportunity to increase bid for better placements")
        
        return recommendations

    def _simulate_bid_impact(
        self,
        new_bid_strategy: str,
        new_bid_amount: Optional[float],
        current_performance: Dict[str, Any],
        avg_daily_spend: float,
        avg_daily_impressions: float,
        avg_daily_clicks: float,
        avg_daily_conversions: float,
    ) -> Dict[str, Any]:
        """Simulate the impact of bid changes."""
        # Simple simulation logic based on bid strategy
        impact_multipliers = {
            "LOWEST_COST_WITHOUT_CAP": {"spend": 1.1, "impressions": 1.2, "clicks": 1.15, "conversions": 1.1},
            "LOWEST_COST_WITH_BID_CAP": {"spend": 0.9, "impressions": 0.8, "clicks": 0.85, "conversions": 0.9},
            "TARGET_COST": {"spend": 1.0, "impressions": 1.1, "clicks": 1.05, "conversions": 1.0},
            "BID_CAP": {"spend": 0.95, "impressions": 0.9, "clicks": 0.9, "conversions": 0.95},
        }
        
        multiplier = impact_multipliers.get(new_bid_strategy, {"spend": 1.0, "impressions": 1.0, "clicks": 1.0, "conversions": 1.0})
        
        # Adjust multipliers based on bid amount if provided
        if new_bid_amount is not None:
            current_cpc = current_performance.get("avg_cpc", 1.0)
            bid_ratio = new_bid_amount / current_cpc if current_cpc > 0 else 1.0
            
            # Adjust multipliers based on bid ratio
            for key in multiplier:
                multiplier[key] *= (0.8 + 0.4 * bid_ratio)  # Scale between 0.8 and 1.2
        
        projected_spend = avg_daily_spend * multiplier["spend"]
        projected_impressions = avg_daily_impressions * multiplier["impressions"]
        projected_clicks = avg_daily_clicks * multiplier["clicks"]
        projected_conversions = avg_daily_conversions * multiplier["conversions"]
        
        # Calculate confidence based on historical data quality
        confidence = min(1.0, max(0.1, (avg_daily_impressions / 10000) * 0.5 + 0.5))
        
        return {
            "projected_daily_spend": projected_spend,
            "projected_daily_impressions": projected_impressions,
            "projected_daily_clicks": projected_clicks,
            "projected_daily_conversions": projected_conversions,
            "projected_cpc": projected_spend / projected_clicks if projected_clicks > 0 else 0,
            "projected_cpm": projected_spend / projected_impressions * 1000 if projected_impressions > 0 else 0,
            "confidence": confidence,
            "impact_summary": {
                "spend_change_pct": (multiplier["spend"] - 1) * 100,
                "impressions_change_pct": (multiplier["impressions"] - 1) * 100,
                "conversions_change_pct": (multiplier["conversions"] - 1) * 100,
            },
        }

    async def _validate_bid_limits(
        self,
        entity_id: str,
        entity_type: str,
        bid_amount: float,
    ) -> Dict[str, Any]:
        """Validate bid amount against limits."""
        try:
            # Basic validation rules
            min_bid = 0.01  # Minimum bid
            max_bid = 1000.0  # Maximum bid (adjust based on account)
            
            if bid_amount < min_bid:
                return {
                    "valid": False,
                    "error": f"Bid amount too low: ${bid_amount}",
                    "suggested_fix": f"Increase bid amount to at least ${min_bid}",
                }
            if bid_amount > max_bid:
                return {
                    "valid": False,
                    "error": f"Bid amount too high: ${bid_amount}",
                    "suggested_fix": f"Decrease bid amount to maximum ${max_bid}",
                }
            
            return {"valid": True}
        except Exception as exc:
            logger.error("Failed to validate bid limits", entity_id=entity_id, error=str(exc))
            return {
                "valid": False,
                "error": "Validation failed",
                "suggested_fix": "Check bid amount and entity status",
            }
