"""
Proactive Messaging Service

Send automated messages to users based on triggers and schedules.

Features:
- Abandoned cart reminders
- Appointment reminders
- Order status updates
- Re-engagement campaigns
- Promotional broadcasts
"""
from typing import Any, Dict, List, Optional
from enum import Enum
from datetime import datetime, timedelta
from pydantic import BaseModel
import logging

logger = logging.getLogger(__name__)


class MessageType(str, Enum):
    """Types of proactive messages"""
    ABANDONED_CART = "abandoned_cart"
    APPOINTMENT_REMINDER = "appointment_reminder"
    ORDER_UPDATE = "order_update"
    DELIVERY_UPDATE = "delivery_update"
    PAYMENT_REMINDER = "payment_reminder"
    FOLLOW_UP = "follow_up"
    RE_ENGAGEMENT = "re_engagement"
    PROMOTIONAL = "promotional"
    BIRTHDAY = "birthday"
    FEEDBACK_REQUEST = "feedback_request"
    CUSTOM = "custom"


class ChannelType(str, Enum):
    """Delivery channels"""
    WHATSAPP = "whatsapp"
    SMS = "sms"
    EMAIL = "email"
    PUSH = "push"
    IN_APP = "in_app"


class MessageStatus(str, Enum):
    """Message delivery status"""
    PENDING = "pending"
    SCHEDULED = "scheduled"
    SENT = "sent"
    DELIVERED = "delivered"
    READ = "read"
    FAILED = "failed"
    CANCELLED = "cancelled"


class ProactiveMessage(BaseModel):
    """Proactive message model"""
    id: str
    client_id: int
    user_id: str
    message_type: MessageType
    channel: ChannelType
    template_id: Optional[str] = None
    content: str
    variables: Dict[str, Any] = {}
    scheduled_at: Optional[datetime] = None
    sent_at: Optional[datetime] = None
    status: MessageStatus = MessageStatus.PENDING
    retry_count: int = 0
    max_retries: int = 3
    metadata: Dict[str, Any] = {}


class MessageTemplate(BaseModel):
    """Reusable message template"""
    id: str
    client_id: int
    name: str
    message_type: MessageType
    channel: ChannelType
    content: str  # Supports {variable} placeholders
    variables: List[str] = []  # Required variables
    is_active: bool = True


class TriggerRule(BaseModel):
    """Rule for automatic message triggers"""
    id: str
    client_id: int
    name: str
    message_type: MessageType
    template_id: str
    channel: ChannelType

    # Trigger conditions
    trigger_event: str  # e.g., "cart_abandoned", "appointment_created"
    delay_minutes: int = 0  # Delay after trigger

    # Filters
    conditions: Dict[str, Any] = {}  # e.g., {"cart_value_min": 1000}

    # Schedule constraints
    send_hours_start: int = 9  # Don't send before 9 AM
    send_hours_end: int = 21   # Don't send after 9 PM
    send_days: List[int] = [0, 1, 2, 3, 4, 5, 6]  # All days

    is_active: bool = True


class ProactiveConfig(BaseModel):
    """Client proactive messaging configuration"""
    enabled: bool = True

    # Rate limits
    max_messages_per_user_per_day: int = 3
    max_messages_per_user_per_week: int = 10

    # Opt-out handling
    respect_opt_out: bool = True
    opt_out_keywords: List[str] = ["stop", "unsubscribe", "opt out"]

    # Quiet hours
    quiet_hours_start: int = 21  # 9 PM
    quiet_hours_end: int = 9     # 9 AM
    timezone: str = "Asia/Kolkata"


class ProactiveService:
    """
    Manages proactive outbound messaging.

    Use cases:
    1. Abandoned cart → Send reminder after 1 hour
    2. Appointment booked → Send reminder 24h before
    3. Order shipped → Send tracking update
    4. No activity 30 days → Re-engagement message
    """

    def __init__(self):
        self._messages: Dict[str, ProactiveMessage] = {}
        self._templates: Dict[str, MessageTemplate] = {}
        self._rules: Dict[str, TriggerRule] = {}
        self._configs: Dict[int, ProactiveConfig] = {}
        self._user_opt_outs: Dict[str, bool] = {}  # user_id -> opted_out
        self._user_message_counts: Dict[str, Dict[str, int]] = {}  # user_id -> {date: count}

    # ==================== TEMPLATES ====================

    def create_template(self, template: MessageTemplate) -> Dict[str, Any]:
        """Create a reusable message template"""
        self._templates[template.id] = template
        logger.info(f"Template created: {template.id} - {template.name}")
        return {"success": True, "template_id": template.id}

    def get_templates(self, client_id: int, message_type: MessageType = None) -> List[MessageTemplate]:
        """Get templates for a client"""
        templates = [t for t in self._templates.values() if t.client_id == client_id]
        if message_type:
            templates = [t for t in templates if t.message_type == message_type]
        return templates

    # ==================== TRIGGER RULES ====================

    def create_rule(self, rule: TriggerRule) -> Dict[str, Any]:
        """Create an automatic trigger rule"""
        self._rules[rule.id] = rule
        logger.info(f"Trigger rule created: {rule.id} - {rule.name}")
        return {"success": True, "rule_id": rule.id}

    async def process_event(
        self,
        client_id: int,
        event: str,
        user_id: str,
        event_data: Dict[str, Any]
    ) -> List[str]:
        """
        Process an event and create messages based on matching rules.

        Example events:
        - cart_abandoned: {cart_id, items, total_value}
        - appointment_created: {appointment_id, date, time, doctor}
        - order_shipped: {order_id, tracking_number}
        """
        created_message_ids = []

        # Find matching rules
        matching_rules = [
            r for r in self._rules.values()
            if r.client_id == client_id
            and r.trigger_event == event
            and r.is_active
        ]

        for rule in matching_rules:
            # Check conditions
            if not self._check_conditions(rule.conditions, event_data):
                continue

            # Check rate limits
            if not self._check_rate_limit(client_id, user_id):
                logger.info(f"Rate limit exceeded for user {user_id}")
                continue

            # Check opt-out
            if self._is_opted_out(user_id):
                logger.info(f"User {user_id} has opted out")
                continue

            # Get template
            template = self._templates.get(rule.template_id)
            if not template:
                continue

            # Build message
            content = self._render_template(template.content, event_data)

            # Calculate send time
            send_at = datetime.utcnow() + timedelta(minutes=rule.delay_minutes)
            send_at = self._adjust_for_quiet_hours(send_at, rule)

            # Create message
            import uuid
            message = ProactiveMessage(
                id=str(uuid.uuid4()),
                client_id=client_id,
                user_id=user_id,
                message_type=rule.message_type,
                channel=rule.channel,
                template_id=rule.template_id,
                content=content,
                variables=event_data,
                scheduled_at=send_at,
                status=MessageStatus.SCHEDULED,
                metadata={"rule_id": rule.id, "event": event}
            )

            self._messages[message.id] = message
            created_message_ids.append(message.id)

            logger.info(f"Proactive message scheduled: {message.id} for {send_at}")

        return created_message_ids

    # ==================== MESSAGE SENDING ====================

    async def send_message(
        self,
        client_id: int,
        user_id: str,
        channel: ChannelType,
        content: str,
        message_type: MessageType = MessageType.CUSTOM,
        schedule_at: datetime = None,
        metadata: Dict[str, Any] = None
    ) -> Dict[str, Any]:
        """Send a one-off proactive message"""
        import uuid

        # Check rate limits
        if not self._check_rate_limit(client_id, user_id):
            return {"success": False, "error": "Rate limit exceeded"}

        # Check opt-out
        if self._is_opted_out(user_id):
            return {"success": False, "error": "User has opted out"}

        message = ProactiveMessage(
            id=str(uuid.uuid4()),
            client_id=client_id,
            user_id=user_id,
            message_type=message_type,
            channel=channel,
            content=content,
            scheduled_at=schedule_at,
            status=MessageStatus.SCHEDULED if schedule_at else MessageStatus.PENDING,
            metadata=metadata or {}
        )

        self._messages[message.id] = message

        if not schedule_at:
            # Send immediately
            await self._deliver_message(message)

        return {"success": True, "message_id": message.id}

    async def send_broadcast(
        self,
        client_id: int,
        user_ids: List[str],
        channel: ChannelType,
        content: str,
        message_type: MessageType = MessageType.PROMOTIONAL
    ) -> Dict[str, Any]:
        """Send broadcast message to multiple users"""
        results = {"sent": 0, "failed": 0, "skipped": 0}

        for user_id in user_ids:
            result = await self.send_message(
                client_id=client_id,
                user_id=user_id,
                channel=channel,
                content=content,
                message_type=message_type
            )

            if result.get("success"):
                results["sent"] += 1
            elif result.get("error") in ["Rate limit exceeded", "User has opted out"]:
                results["skipped"] += 1
            else:
                results["failed"] += 1

        return results

    async def _deliver_message(self, message: ProactiveMessage) -> bool:
        """Actually deliver the message via the appropriate channel"""
        try:
            # This would integrate with actual delivery services
            # WhatsApp: Meta Business API
            # SMS: Twilio, MSG91
            # Email: SendGrid, AWS SES
            # Push: Firebase, OneSignal

            if message.channel == ChannelType.WHATSAPP:
                # await whatsapp_service.send(message.user_id, message.content)
                pass
            elif message.channel == ChannelType.SMS:
                # await sms_service.send(message.user_id, message.content)
                pass
            elif message.channel == ChannelType.EMAIL:
                # await email_service.send(message.user_id, message.content)
                pass

            message.status = MessageStatus.SENT
            message.sent_at = datetime.utcnow()

            # Update rate limit counter
            self._increment_message_count(message.user_id)

            logger.info(f"Message {message.id} delivered via {message.channel.value}")
            return True

        except Exception as e:
            message.retry_count += 1
            if message.retry_count >= message.max_retries:
                message.status = MessageStatus.FAILED
            logger.error(f"Failed to deliver message {message.id}: {str(e)}")
            return False

    # ==================== SCHEDULED MESSAGE PROCESSOR ====================

    async def process_scheduled_messages(self) -> Dict[str, int]:
        """
        Process messages that are due to be sent.
        Should be called by a background scheduler (e.g., every minute).
        """
        now = datetime.utcnow()
        results = {"processed": 0, "sent": 0, "failed": 0}

        due_messages = [
            m for m in self._messages.values()
            if m.status == MessageStatus.SCHEDULED
            and m.scheduled_at
            and m.scheduled_at <= now
        ]

        for message in due_messages:
            results["processed"] += 1
            success = await self._deliver_message(message)
            if success:
                results["sent"] += 1
            else:
                results["failed"] += 1

        return results

    # ==================== OPT-OUT MANAGEMENT ====================

    def handle_opt_out(self, user_id: str, message: str) -> bool:
        """Check if message is an opt-out request and handle it"""
        config = ProactiveConfig()  # Would get client-specific config

        message_lower = message.lower()
        for keyword in config.opt_out_keywords:
            if keyword in message_lower:
                self._user_opt_outs[user_id] = True
                logger.info(f"User {user_id} opted out of proactive messages")
                return True

        return False

    def opt_in(self, user_id: str) -> bool:
        """Re-enable messages for a user"""
        self._user_opt_outs[user_id] = False
        return True

    def _is_opted_out(self, user_id: str) -> bool:
        """Check if user has opted out"""
        return self._user_opt_outs.get(user_id, False)

    # ==================== HELPERS ====================

    def _check_conditions(self, conditions: Dict[str, Any], data: Dict[str, Any]) -> bool:
        """Check if event data matches rule conditions"""
        for key, value in conditions.items():
            if key.endswith("_min"):
                field = key[:-4]
                if data.get(field, 0) < value:
                    return False
            elif key.endswith("_max"):
                field = key[:-4]
                if data.get(field, float('inf')) > value:
                    return False
            elif key.endswith("_in"):
                field = key[:-3]
                if data.get(field) not in value:
                    return False
            else:
                if data.get(key) != value:
                    return False
        return True

    def _check_rate_limit(self, client_id: int, user_id: str) -> bool:
        """Check if user is within rate limits"""
        config = self._configs.get(client_id, ProactiveConfig())

        today = datetime.utcnow().strftime("%Y-%m-%d")
        user_counts = self._user_message_counts.get(user_id, {})
        today_count = user_counts.get(today, 0)

        return today_count < config.max_messages_per_user_per_day

    def _increment_message_count(self, user_id: str):
        """Increment daily message count for user"""
        today = datetime.utcnow().strftime("%Y-%m-%d")

        if user_id not in self._user_message_counts:
            self._user_message_counts[user_id] = {}

        self._user_message_counts[user_id][today] = \
            self._user_message_counts[user_id].get(today, 0) + 1

    def _render_template(self, template: str, variables: Dict[str, Any]) -> str:
        """Render template with variables"""
        result = template
        for key, value in variables.items():
            result = result.replace(f"{{{key}}}", str(value))
        return result

    def _adjust_for_quiet_hours(self, send_time: datetime, rule: TriggerRule) -> datetime:
        """Adjust send time to respect quiet hours"""
        hour = send_time.hour

        if hour >= rule.send_hours_end or hour < rule.send_hours_start:
            # Move to next available hour
            if hour >= rule.send_hours_end:
                # Next day
                send_time = send_time + timedelta(days=1)
            send_time = send_time.replace(
                hour=rule.send_hours_start,
                minute=0,
                second=0
            )

        return send_time

    # ==================== ANALYTICS ====================

    def get_message_stats(
        self,
        client_id: int,
        date_from: datetime = None,
        date_to: datetime = None
    ) -> Dict[str, Any]:
        """Get messaging statistics"""
        messages = [
            m for m in self._messages.values()
            if m.client_id == client_id
        ]

        if date_from:
            messages = [m for m in messages if m.scheduled_at and m.scheduled_at >= date_from]
        if date_to:
            messages = [m for m in messages if m.scheduled_at and m.scheduled_at <= date_to]

        total = len(messages)
        by_status = {}
        by_type = {}
        by_channel = {}

        for m in messages:
            by_status[m.status.value] = by_status.get(m.status.value, 0) + 1
            by_type[m.message_type.value] = by_type.get(m.message_type.value, 0) + 1
            by_channel[m.channel.value] = by_channel.get(m.channel.value, 0) + 1

        return {
            "total": total,
            "by_status": by_status,
            "by_type": by_type,
            "by_channel": by_channel,
            "delivery_rate": by_status.get("delivered", 0) / total if total > 0 else 0
        }


# Default templates for common use cases
DEFAULT_TEMPLATES = {
    "abandoned_cart": {
        "content": "Hi {customer_name}! You left items in your cart worth ₹{cart_value}. Complete your purchase now and get 10% off! 🛒",
        "variables": ["customer_name", "cart_value"]
    },
    "appointment_reminder": {
        "content": "Reminder: Your appointment with {provider_name} is scheduled for {date} at {time}. Reply YES to confirm or RESCHEDULE to change.",
        "variables": ["provider_name", "date", "time"]
    },
    "order_shipped": {
        "content": "Great news! Your order #{order_id} has been shipped. Track it here: {tracking_url}",
        "variables": ["order_id", "tracking_url"]
    },
    "payment_reminder": {
        "content": "Hi {customer_name}, your payment of ₹{amount} is due on {due_date}. Pay now to avoid late fees.",
        "variables": ["customer_name", "amount", "due_date"]
    },
    "feedback_request": {
        "content": "Hi {customer_name}! How was your recent experience with us? Rate us: {feedback_url}",
        "variables": ["customer_name", "feedback_url"]
    }
}


# Singleton instance
proactive_service = ProactiveService()
