from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, func, desc
from typing import Optional, Dict, List, Any
from datetime import datetime, timedelta
from ..models.notification import (
    Notification, NotificationTemplate, NotificationPreference, NotificationDeliveryLog,
    NotificationChannel, NotificationStatus, NotificationType
)
from ...auth.models.user import User
from ...bookings.models.booking import Booking
from ...payments.models.payment import Payment
import logging
import re

logger = logging.getLogger(__name__)

class NotificationService:
    """Multi-channel Notification Service"""
    
    @staticmethod
    def get_user_preferences(db: Session, user_id: int) -> NotificationPreference:
        """Get or create user notification preferences"""
        preferences = db.query(NotificationPreference).filter(
            NotificationPreference.user_id == user_id
        ).first()
        
        if not preferences:
            preferences = NotificationPreference(user_id=user_id)
            db.add(preferences)
            db.commit()
            db.refresh(preferences)
        
        return preferences
    
    @staticmethod
    def update_user_preferences(
        db: Session,
        user_id: int,
        preferences_data: Dict[str, Any]
    ) -> NotificationPreference:
        """Update user notification preferences"""
        preferences = NotificationService.get_user_preferences(db, user_id)
        
        for key, value in preferences_data.items():
            if hasattr(preferences, key):
                setattr(preferences, key, value)
        
        preferences.updated_at = datetime.utcnow()
        db.commit()
        db.refresh(preferences)
        return preferences
    
    @staticmethod
    def should_send_notification(
        db: Session,
        user_id: int,
        notification_type: NotificationType,
        channel: NotificationChannel
    ) -> bool:
        """Check if notification should be sent based on user preferences"""
        preferences = NotificationService.get_user_preferences(db, user_id)
        
        # Check global channel preference
        channel_attr = f'{channel.value}_enabled'
        if not getattr(preferences, channel_attr, False):
            return False
        
        # Check type-specific preference
        type_attr = f'{notification_type.value}_{channel.value}'
        return getattr(preferences, type_attr, True)
    
    @staticmethod
    def create_template(
        db: Session,
        name: str,
        notification_type: NotificationType,
        channel: NotificationChannel,
        content: str,
        created_by: int,
        subject: Optional[str] = None,
        variables: Optional[List[str]] = None
    ) -> NotificationTemplate:
        """Create notification template"""
        template = NotificationTemplate(
            name=name,
            notification_type=notification_type,
            channel=channel,
            subject=subject,
            content=content,
            variables=variables or [],
            created_by=created_by,
            is_active=True
        )
        db.add(template)
        db.commit()
        db.refresh(template)
        return template
    
    @staticmethod
    def get_template(
        db: Session,
        notification_type: NotificationType,
        channel: NotificationChannel
    ) -> Optional[NotificationTemplate]:
        """Get active template for notification type and channel"""
        return db.query(NotificationTemplate).filter(
            and_(
                NotificationTemplate.notification_type == notification_type,
                NotificationTemplate.channel == channel,
                NotificationTemplate.is_active == True
            )
        ).first()
    
    @staticmethod
    def render_template(template: NotificationTemplate, variables: Dict[str, Any]) -> Dict[str, str]:
        """Render template with variables"""
        content = template.content
        subject = template.subject or ''
        
        # Replace variables in format {{variable_name}}
        for key, value in variables.items():
            placeholder = f'{{{{{key}}}}}'
            content = content.replace(placeholder, str(value))
            subject = subject.replace(placeholder, str(value))
        
        return {'subject': subject, 'content': content}
    
    @staticmethod
    def send_notification(
        db: Session,
        user_id: Optional[int],
        notification_type: NotificationType,
        channel: NotificationChannel,
        content: str,
        subject: Optional[str] = None,
        template_id: Optional[int] = None,
        priority: str = 'normal',
        scheduled_at: Optional[datetime] = None,
        booking_id: Optional[int] = None,
        payment_id: Optional[int] = None,
        meta_data: Optional[Dict[str, Any]] = None
    ) -> Notification:
        """Create and send notification"""
        # Check user preferences if user_id is provided
        if user_id and not NotificationService.should_send_notification(db, user_id, notification_type, channel):
            logger.info(f"Notification skipped due to user preferences: user_id={user_id}, type={notification_type}, channel={channel}")
            # Still create notification but mark as skipped
            notification = Notification(
                user_id=user_id,
                notification_type=notification_type,
                channel=channel,
                subject=subject,
                content=content,
                template_id=template_id,
                status=NotificationStatus.failed,
                priority=priority,
                scheduled_at=scheduled_at,
                booking_id=booking_id,
                payment_id=payment_id,
                meta_data={**(meta_data or {}), 'skipped': True, 'reason': 'user_preference'}
            )
            db.add(notification)
            db.commit()
            return notification
        
        notification = Notification(
            user_id=user_id,
            notification_type=notification_type,
            channel=channel,
            subject=subject,
            content=content,
            template_id=template_id,
            status=NotificationStatus.pending,
            priority=priority,
            scheduled_at=scheduled_at,
            booking_id=booking_id,
            payment_id=payment_id,
            meta_data=meta_data or {}
        )
        db.add(notification)
        db.commit()
        db.refresh(notification)
        
        # Send notification if not scheduled
        if not scheduled_at or scheduled_at <= datetime.utcnow():
            NotificationService._deliver_notification(db, notification)
        
        return notification
    
    @staticmethod
    def _deliver_notification(db: Session, notification: Notification):
        """Deliver notification through appropriate channel"""
        try:
            if notification.channel == NotificationChannel.email:
                NotificationService._send_email(db, notification)
            elif notification.channel == NotificationChannel.sms:
                NotificationService._send_sms(db, notification)
            elif notification.channel == NotificationChannel.push:
                NotificationService._send_push(db, notification)
            elif notification.channel == NotificationChannel.whatsapp:
                NotificationService._send_whatsapp(db, notification)
            elif notification.channel == NotificationChannel.in_app:
                NotificationService._send_in_app(db, notification)
        except Exception as e:
            logger.error(f"Failed to deliver notification {notification.id}: {str(e)}")
            notification.status = NotificationStatus.failed
            notification.error_message = str(e)
            db.commit()
    
    @staticmethod
    def _send_email(db: Session, notification: Notification):
        """Send email notification"""
        # TODO: Integrate with email service (SendGrid, AWS SES, etc.)
        # For now, just mark as sent
        notification.status = NotificationStatus.sent
        notification.sent_at = datetime.utcnow()
        db.commit()
        logger.info(f"Email notification {notification.id} sent (mock)")
    
    @staticmethod
    def _send_sms(db: Session, notification: Notification):
        """Send SMS notification"""
        # TODO: Integrate with SMS service (Twilio, AWS SNS, etc.)
        # For now, just mark as sent
        notification.status = NotificationStatus.sent
        notification.sent_at = datetime.utcnow()
        db.commit()
        logger.info(f"SMS notification {notification.id} sent (mock)")
    
    @staticmethod
    def _send_push(db: Session, notification: Notification):
        """Send push notification"""
        # TODO: Integrate with push notification service (FCM, APNS, etc.)
        # For now, just mark as sent
        notification.status = NotificationStatus.sent
        notification.sent_at = datetime.utcnow()
        db.commit()
        logger.info(f"Push notification {notification.id} sent (mock)")
    
    @staticmethod
    def _send_whatsapp(db: Session, notification: Notification):
        """Send WhatsApp notification"""
        # TODO: Integrate with WhatsApp Business API
        # For now, just mark as sent
        notification.status = NotificationStatus.sent
        notification.sent_at = datetime.utcnow()
        db.commit()
        logger.info(f"WhatsApp notification {notification.id} sent (mock)")
    
    @staticmethod
    def _send_in_app(db: Session, notification: Notification):
        """Send in-app notification"""
        # In-app notifications are always "delivered" immediately
        notification.status = NotificationStatus.delivered
        notification.sent_at = datetime.utcnow()
        notification.delivered_at = datetime.utcnow()
        db.commit()
        logger.info(f"In-app notification {notification.id} delivered")
    
    @staticmethod
    def send_booking_confirmation(db: Session, booking: Booking):
        """Send booking confirmation notifications"""
        user = booking.user
        if not user:
            return
        
        # Get template or use default
        template = NotificationService.get_template(db, NotificationType.booking_confirmation, NotificationChannel.email)
        
        if template:
            variables = {
                'booking_number': booking.booking_number,
                'guest_name': user.full_name,
                'check_in': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else '',
                'check_out': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else '',
                'total_price': str(booking.total_price),
            }
            rendered = NotificationService.render_template(template, variables)
            NotificationService.send_notification(
                db=db,
                user_id=user.id,
                notification_type=NotificationType.booking_confirmation,
                channel=NotificationChannel.email,
                content=rendered['content'],
                subject=rendered['subject'],
                template_id=template.id,
                booking_id=booking.id
            )
        else:
            # Fallback to default message
            NotificationService.send_notification(
                db=db,
                user_id=user.id,
                notification_type=NotificationType.booking_confirmation,
                channel=NotificationChannel.email,
                subject=f'Booking Confirmation - {booking.booking_number}',
                content=f'Your booking {booking.booking_number} has been confirmed.',
                booking_id=booking.id
            )
    
    @staticmethod
    def send_payment_receipt(db: Session, payment: Payment):
        """Send payment receipt notifications"""
        booking = payment.booking
        if not booking or not booking.user:
            return
        
        user = booking.user
        template = NotificationService.get_template(db, NotificationType.payment_receipt, NotificationChannel.email)
        
        if template:
            variables = {
                'payment_amount': str(payment.amount),
                'payment_method': payment.payment_method.value if hasattr(payment.payment_method, 'value') else str(payment.payment_method),
                'transaction_id': payment.transaction_id or '',
                'booking_number': booking.booking_number,
                'guest_name': user.full_name,
            }
            rendered = NotificationService.render_template(template, variables)
            NotificationService.send_notification(
                db=db,
                user_id=user.id,
                notification_type=NotificationType.payment_receipt,
                channel=NotificationChannel.email,
                content=rendered['content'],
                subject=rendered['subject'],
                template_id=template.id,
                payment_id=payment.id,
                booking_id=booking.id
            )
    
    @staticmethod
    def get_notifications(
        db: Session,
        user_id: Optional[int] = None,
        notification_type: Optional[NotificationType] = None,
        channel: Optional[NotificationChannel] = None,
        status: Optional[NotificationStatus] = None,
        skip: int = 0,
        limit: int = 100
    ) -> List[Notification]:
        """Get notifications with filters"""
        query = db.query(Notification)
        
        if user_id:
            query = query.filter(Notification.user_id == user_id)
        if notification_type:
            query = query.filter(Notification.notification_type == notification_type)
        if channel:
            query = query.filter(Notification.channel == channel)
        if status:
            query = query.filter(Notification.status == status)
        
        return query.order_by(desc(Notification.created_at)).offset(skip).limit(limit).all()
    
    @staticmethod
    def mark_as_read(db: Session, notification_id: int, user_id: int) -> Optional[Notification]:
        """Mark notification as read"""
        notification = db.query(Notification).filter(
            and_(
                Notification.id == notification_id,
                Notification.user_id == user_id
            )
        ).first()
        
        if notification:
            notification.status = NotificationStatus.read
            notification.read_at = datetime.utcnow()
            db.commit()
            db.refresh(notification)
        
        return notification

