"""
Scheduling and automation tools for Meta Ads campaigns.

This module provides capabilities for scheduling future actions,
managing dayparting, and automating campaign operations.
"""

import uuid
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 ScheduleManager:
    """Manages scheduled actions and automation for campaigns."""

    def __init__(self, client: MetaClient):
        self._client = client
        # In-memory storage for schedules (in production, this would be a database)
        self._schedules: Dict[str, Dict[str, Any]] = {}

    async def create_schedule(
        self,
        entity_id: str,
        entity_type: str,
        action_type: str,
        action_params: Dict[str, Any],
        schedule_date: str,
        schedule_time: str = "00:00",
        timezone: str = "UTC",
        recurring: bool = False,
        recurring_frequency: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Schedule a future action for a campaign, ad set, or ad.
        """
        try:
            # Validate action type
            valid_actions = ["pause", "resume", "budget_change", "bid_change", "status_change"]
            if action_type not in valid_actions:
                return {
                    "success": False,
                    "error": f"Invalid action type: {action_type}",
                    "suggested_fix": f"Use one of: {', '.join(valid_actions)}",
                }

            # Validate schedule date
            try:
                schedule_datetime = datetime.strptime(f"{schedule_date} {schedule_time}", "%Y-%m-%d %H:%M")
                if schedule_datetime <= datetime.now():
                    return {
                        "success": False,
                        "error": "Schedule date must be in the future",
                        "suggested_fix": "Set a future date and time",
                    }
            except ValueError:
                return {
                    "success": False,
                    "error": "Invalid date/time format",
                    "suggested_fix": "Use YYYY-MM-DD format for date and HH:MM for time",
                }

            # Validate recurring frequency if recurring is enabled
            if recurring and recurring_frequency:
                valid_frequencies = ["daily", "weekly", "monthly"]
                if recurring_frequency not in valid_frequencies:
                    return {
                        "success": False,
                        "error": f"Invalid recurring frequency: {recurring_frequency}",
                        "suggested_fix": f"Use one of: {', '.join(valid_frequencies)}",
                    }

            # Generate unique schedule ID
            schedule_id = str(uuid.uuid4())

            # Create schedule entry
            schedule_data = {
                "schedule_id": schedule_id,
                "entity_id": entity_id,
                "entity_type": entity_type,
                "action_type": action_type,
                "action_params": action_params,
                "schedule_date": schedule_date,
                "schedule_time": schedule_time,
                "timezone": timezone,
                "recurring": recurring,
                "recurring_frequency": recurring_frequency,
                "status": "pending",
                "created_at": datetime.now().isoformat(),
                "next_execution": f"{schedule_date} {schedule_time}",
            }

            # Store schedule
            self._schedules[schedule_id] = schedule_data

            # Validate entity exists
            try:
                await self._client.get(entity_id, params={"fields": "id,name,status"})
            except MetaApiError:
                return {
                    "success": False,
                    "error": f"Entity {entity_id} not found or inaccessible",
                    "suggested_fix": "Check entity ID and permissions",
                }

            return {
                "success": True,
                "schedule_id": schedule_id,
                "entity_id": entity_id,
                "entity_type": entity_type,
                "action_type": action_type,
                "schedule_configuration": schedule_data,
                "execution_summary": {
                    "scheduled_for": f"{schedule_date} {schedule_time} {timezone}",
                    "action": action_type,
                    "recurring": recurring,
                    "frequency": recurring_frequency if recurring else "one-time",
                },
                "next_recommended_actions": [
                    "Monitor schedule execution",
                    "Review scheduled actions regularly",
                    "Update or cancel if needed",
                ],
            }
        except Exception as exc:
            logger.error("Failed to create schedule", entity_id=entity_id, error=str(exc))
            return {
                "success": False,
                "error": str(exc),
                "suggested_fix": "Check parameters and entity status",
            }

    async def list_schedules(
        self,
        entity_id: Optional[str] = None,
        entity_type: Optional[str] = None,
        status: Optional[str] = None,
        limit: int = 50,
    ) -> Dict[str, Any]:
        """
        List scheduled actions with optional filtering.
        """
        try:
            # Filter schedules based on criteria
            filtered_schedules = []
            for schedule in self._schedules.values():
                # Apply filters
                if entity_id and schedule["entity_id"] != entity_id:
                    continue
                if entity_type and schedule["entity_type"] != entity_type:
                    continue
                if status and schedule["status"] != status:
                    continue
                
                filtered_schedules.append(schedule)

            # Sort by next execution time
            filtered_schedules.sort(key=lambda x: x["next_execution"])

            # Apply limit
            filtered_schedules = filtered_schedules[:limit]

            # Calculate summary statistics
            status_counts = {}
            for schedule in filtered_schedules:
                status_counts[schedule["status"]] = status_counts.get(schedule["status"], 0) + 1

            return {
                "total_schedules": len(filtered_schedules),
                "filtered_schedules": filtered_schedules,
                "summary": {
                    "status_breakdown": status_counts,
                    "upcoming_executions": len([s for s in filtered_schedules if s["status"] == "pending"]),
                    "recurring_schedules": len([s for s in filtered_schedules if s["recurring"]]),
                },
                "filters_applied": {
                    "entity_id": entity_id,
                    "entity_type": entity_type,
                    "status": status,
                    "limit": limit,
                },
                "next_recommended_actions": [
                    "Review upcoming scheduled actions",
                    "Monitor execution status",
                    "Update or cancel outdated schedules",
                ],
            }
        except Exception as exc:
            logger.error("Failed to list schedules", error=str(exc))
            return {
                "success": False,
                "error": str(exc),
                "suggested_fix": "Check filter parameters",
            }

    async def cancel_schedule(
        self,
        schedule_id: str,
    ) -> Dict[str, Any]:
        """
        Cancel a scheduled action.
        """
        try:
            # Check if schedule exists
            if schedule_id not in self._schedules:
                return {
                    "success": False,
                    "error": f"Schedule {schedule_id} not found",
                    "suggested_fix": "Check schedule ID",
                }

            schedule = self._schedules[schedule_id]

            # Check if schedule can be cancelled
            if schedule["status"] == "completed":
                return {
                    "success": False,
                    "error": "Cannot cancel completed schedule",
                    "suggested_fix": "Schedule has already been executed",
                }

            if schedule["status"] == "cancelled":
                return {
                    "success": False,
                    "error": "Schedule already cancelled",
                    "suggested_fix": "Schedule is already in cancelled status",
                }

            # Cancel the schedule
            schedule["status"] = "cancelled"
            schedule["cancelled_at"] = datetime.now().isoformat()

            return {
                "success": True,
                "schedule_id": schedule_id,
                "cancelled_schedule": schedule,
                "cancellation_summary": {
                    "entity_id": schedule["entity_id"],
                    "entity_type": schedule["entity_type"],
                    "action_type": schedule["action_type"],
                    "was_scheduled_for": schedule["next_execution"],
                    "recurring": schedule["recurring"],
                },
                "next_recommended_actions": [
                    "Verify schedule cancellation",
                    "Create new schedule if needed",
                    "Update automation strategy",
                ],
            }
        except Exception as exc:
            logger.error("Failed to cancel schedule", schedule_id=schedule_id, error=str(exc))
            return {
                "success": False,
                "error": str(exc),
                "suggested_fix": "Check schedule ID and status",
            }

    async def get_dayparting(
        self,
        entity_id: str,
        entity_type: str,
    ) -> Dict[str, Any]:
        """
        Get current dayparting settings for a campaign or ad set.
        """
        try:
            # Get entity details
            response = await self._client.get(
                entity_id,
                params={"fields": "id,name,status,ad_schedule"}
            )
            entity_data = response.get("data", {})

            # Parse ad schedule if available
            ad_schedule = entity_data.get("ad_schedule", [])
            dayparting_schedule = {}

            if ad_schedule:
                # Convert Meta's ad_schedule format to readable format
                for schedule_item in ad_schedule:
                    day = schedule_item.get("days", [])
                    hours = schedule_item.get("hours", [])
                    
                    for d in day:
                        day_name = self._get_day_name(d)
                        if day_name not in dayparting_schedule:
                            dayparting_schedule[day_name] = []
                        dayparting_schedule[day_name].extend(hours)

            # Get recent performance by hour for analysis
            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")},
                    "breakdowns": "hourly_stats_aggregated_by_advertiser_time_zone",
                }
            )
            performance_data = performance_response.get("data", [])

            # Analyze hourly performance
            hourly_performance = {}
            for insight in performance_data:
                hour = insight.get("breakdowns.hourly_stats_aggregated_by_advertiser_time_zone", "unknown")
                hourly_performance[hour] = {
                    "hour": hour,
                    "spend": float(insight.get("spend", 0)),
                    "impressions": int(insight.get("impressions", 0)),
                    "clicks": int(insight.get("clicks", 0)),
                    "ctr": float(insight.get("ctr", 0)),
                    "cpc": float(insight.get("cpc", 0)),
                    "conversions": sum(
                        action.get("value", 0) for action in insight.get("actions", [])
                        if action.get("action_type") == "offsite_conversion.fb_pixel_purchase"
                    ),
                }

            # Find best and worst performing hours
            best_hour = max(hourly_performance.values(), key=lambda x: x["conversions"]) if hourly_performance else None
            worst_hour = min(hourly_performance.values(), key=lambda x: x["conversions"]) if hourly_performance else None

            return {
                "entity_id": entity_id,
                "entity_type": entity_type,
                "current_dayparting": {
                    "schedule": dayparting_schedule,
                    "is_active": len(dayparting_schedule) > 0,
                    "total_active_hours": sum(len(hours) for hours in dayparting_schedule.values()),
                },
                "performance_analysis": {
                    "hourly_performance": list(hourly_performance.values()),
                    "best_performing_hour": best_hour["hour"] if best_hour else None,
                    "worst_performing_hour": worst_hour["hour"] if worst_hour else None,
                    "total_hours_analyzed": len(hourly_performance),
                },
                "recommendations": self._get_dayparting_recommendations(hourly_performance, dayparting_schedule),
                "next_recommended_actions": [
                    "Review hourly performance patterns",
                    "Optimize dayparting based on performance",
                    "Consider bid adjustments for high-performing hours",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to get dayparting", entity_id=entity_id, error=exc.to_dict())
            raise

    async def set_dayparting(
        self,
        entity_id: str,
        entity_type: str,
        dayparting_schedule: Dict[str, List[int]],
        timezone: str = "UTC",
        bid_adjustments: Optional[Dict[str, float]] = None,
    ) -> Dict[str, Any]:
        """
        Configure ad scheduling by day/hour with optional bid adjustments.
        """
        try:
            # Validate dayparting schedule
            validation_result = self._validate_dayparting_schedule(dayparting_schedule)
            if not validation_result["valid"]:
                return {
                    "success": False,
                    "error": validation_result["error"],
                    "suggested_fix": validation_result["suggested_fix"],
                }

            # Convert dayparting schedule to Meta's format
            ad_schedule = self._convert_to_meta_schedule(dayparting_schedule)

            # Prepare update data
            update_data = {"ad_schedule": ad_schedule}

            # Add bid adjustments if provided
            if bid_adjustments:
                # Note: Bid adjustments by time would typically be handled through
                # automated rules or bid strategies in Meta's system
                update_data["bid_adjustments"] = bid_adjustments

            # Update entity with dayparting schedule
            await self._client.post(
                entity_id,
                body=update_data,
                encoding=self._client.GraphBodyEncoding.FORM,
            )

            # Get updated dayparting info
            updated_dayparting = await self.get_dayparting(entity_id, entity_type)

            return {
                "success": True,
                "entity_id": entity_id,
                "entity_type": entity_type,
                "new_dayparting_schedule": dayparting_schedule,
                "timezone": timezone,
                "bid_adjustments": bid_adjustments,
                "updated_dayparting_info": updated_dayparting,
                "schedule_summary": {
                    "total_active_days": len(dayparting_schedule),
                    "total_active_hours": sum(len(hours) for hours in dayparting_schedule.values()),
                    "coverage_percentage": (sum(len(hours) for hours in dayparting_schedule.values()) / (7 * 24)) * 100,
                },
                "next_recommended_actions": [
                    "Monitor performance after dayparting changes",
                    "Track delivery during active hours",
                    "Adjust schedule based on performance data",
                ],
            }
        except MetaApiError as exc:
            logger.error("Failed to set dayparting", entity_id=entity_id, error=exc.to_dict())
            return {
                "success": False,
                "error": exc.context.message,
                "suggested_fix": "Check dayparting schedule format and entity status",
            }

    def _get_day_name(self, day_number: int) -> str:
        """Convert day number to day name."""
        days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
        return days[day_number] if 0 <= day_number < 7 else "unknown"

    def _get_dayparting_recommendations(
        self,
        hourly_performance: Dict[str, Dict[str, Any]],
        current_schedule: Dict[str, List[int]],
    ) -> List[str]:
        """Get dayparting optimization recommendations."""
        recommendations = []

        if not hourly_performance:
            recommendations.append("No performance data available for dayparting analysis")
            return recommendations

        # Find high and low performing hours
        high_performing_hours = [
            hour for hour, data in hourly_performance.items()
            if data["conversions"] > 0 and data["cpc"] < 2.0
        ]
        low_performing_hours = [
            hour for hour, data in hourly_performance.items()
            if data["conversions"] == 0 or data["cpc"] > 5.0
        ]

        if high_performing_hours:
            recommendations.append(f"Consider increasing bids during high-performing hours: {', '.join(high_performing_hours[:3])}")
        
        if low_performing_hours:
            recommendations.append(f"Consider reducing bids or pausing during low-performing hours: {', '.join(low_performing_hours[:3])}")

        # Check schedule coverage
        total_active_hours = sum(len(hours) for hours in current_schedule.values())
        if total_active_hours < 24:
            recommendations.append("Consider expanding dayparting to capture more opportunities")
        elif total_active_hours > 120:  # More than 5 days * 24 hours
            recommendations.append("Consider optimizing dayparting to focus on best-performing hours")

        return recommendations

    def _validate_dayparting_schedule(
        self,
        dayparting_schedule: Dict[str, List[int]],
    ) -> Dict[str, Any]:
        """Validate dayparting schedule format."""
        valid_days = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
        
        for day, hours in dayparting_schedule.items():
            if day not in valid_days:
                return {
                    "valid": False,
                    "error": f"Invalid day: {day}",
                    "suggested_fix": f"Use one of: {', '.join(valid_days)}",
                }
            
            for hour in hours:
                if not isinstance(hour, int) or hour < 0 or hour > 23:
                    return {
                        "valid": False,
                        "error": f"Invalid hour: {hour}",
                        "suggested_fix": "Use integers between 0 and 23",
                    }

        return {"valid": True}

    def _convert_to_meta_schedule(
        self,
        dayparting_schedule: Dict[str, List[int]],
    ) -> List[Dict[str, Any]]:
        """Convert dayparting schedule to Meta's ad_schedule format."""
        day_mapping = {
            "monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3,
            "friday": 4, "saturday": 5, "sunday": 6
        }
        
        meta_schedule = []
        for day, hours in dayparting_schedule.items():
            if hours:  # Only include days with active hours
                meta_schedule.append({
                    "days": [day_mapping[day]],
                    "hours": hours,
                })
        
        return meta_schedule
