from sqlalchemy.orm import Session
from typing import List, Dict, Any, Optional
from datetime import datetime, timedelta
from ..models.email_campaign import (
    Campaign, CampaignStatus, CampaignType, EmailStatus,
    CampaignSegment, EmailTemplate, CampaignEmail, EmailClick,
    DripSequence, DripSequenceStep, DripSequenceEnrollment, Unsubscribe, NewsletterSubscriber
)
from ...auth.models.user import User
from ...bookings.models.booking import Booking
from ...shared.config.logging_config import get_logger
from ...shared.utils.mailer import send_email

logger = get_logger(__name__)

class EmailCampaignService:
    """Service for managing email campaigns"""
    
    @staticmethod
    def create_campaign(
        db: Session,
        name: str,
        subject: str,
        html_content: str,
        text_content: Optional[str] = None,
        campaign_type: CampaignType = CampaignType.newsletter,
        segment_id: Optional[int] = None,
        scheduled_at: Optional[datetime] = None,
        created_by: Optional[int] = None,
        **kwargs
    ) -> Campaign:
        """Create a new email campaign"""
        campaign = Campaign(
            name=name,
            subject=subject,
            html_content=html_content,
            text_content=text_content,
            campaign_type=campaign_type,
            segment_id=segment_id,
            scheduled_at=scheduled_at,
            created_by=created_by,
            **kwargs
        )
        db.add(campaign)
        db.commit()
        db.refresh(campaign)
        return campaign
    
    @staticmethod
    def get_campaign_recipients(
        db: Session,
        campaign: Campaign
    ) -> List[User]:
        """Get list of recipients for a campaign based on segment and recipient_type"""
        recipient_type = getattr(campaign, 'recipient_type', 'users') or 'users'
        recipients = []
        
        # Get users if recipient_type is 'users' or 'both'
        if recipient_type in ['users', 'both']:
            if campaign.segment_id:
                segment = db.query(CampaignSegment).filter(
                    CampaignSegment.id == campaign.segment_id
                ).first()
                if segment:
                    recipients.extend(EmailCampaignService._apply_segment_criteria(db, segment.criteria))
                else:
                    # If segment not found, get all active users
                    recipients.extend(db.query(User).filter(User.is_active == True).all())
            else:
                # If no segment, return all active users
                recipients.extend(db.query(User).filter(User.is_active == True).all())
        
        # Get subscribers if recipient_type is 'subscribers' or 'both'
        if recipient_type in ['subscribers', 'both']:
            subscribers = db.query(NewsletterSubscriber).filter(
                NewsletterSubscriber.is_active == True
            ).all()
            
            # Convert subscribers to User-like objects or get their User records
            for subscriber in subscribers:
                if subscriber.user_id:
                    # If subscriber is a user, get the User object (avoid duplicates)
                    user = db.query(User).filter(User.id == subscriber.user_id).first()
                    if user and user not in recipients:
                        recipients.append(user)
                else:
                    # Guest subscriber - create a minimal User-like object for sending
                    # We'll handle this in send_campaign by using the email directly
                    # For now, we'll skip guest subscribers in the User list
                    # They'll be handled separately in send_campaign
                    pass
        
        # Remove duplicates based on email
        seen_emails = set()
        unique_recipients = []
        for user in recipients:
            if user.email not in seen_emails:
                seen_emails.add(user.email)
                unique_recipients.append(user)
        
        return unique_recipients
    
    @staticmethod
    def _apply_segment_criteria(db: Session, criteria: Dict[str, Any]) -> List[User]:
        """Apply segment criteria to get matching users"""
        query = db.query(User).filter(User.is_active == True)
        
        # Role filter
        if 'role' in criteria:
            from ...auth.models.role import Role
            role = db.query(Role).filter(Role.name == criteria['role']).first()
            if role:
                query = query.filter(User.role_id == role.id)
        
        # Last booking days
        if 'last_booking_days' in criteria:
            cutoff_date = datetime.utcnow() - timedelta(days=criteria['last_booking_days'])
            query = query.join(Booking).filter(Booking.created_at >= cutoff_date)
        
        # VIP status
        if 'is_vip' in criteria:
            query = query.filter(User.is_vip == criteria['is_vip'])
        
        # Has bookings
        if 'has_bookings' in criteria:
            if criteria['has_bookings']:
                query = query.join(Booking).distinct()
            else:
                query = query.outerjoin(Booking).filter(Booking.id.is_(None))
        
        return query.distinct().all()
    
    @staticmethod
    def send_campaign(db: Session, campaign_id: int) -> Dict[str, Any]:
        """Send an email campaign"""
        campaign = db.query(Campaign).filter(Campaign.id == campaign_id).first()
        if not campaign:
            raise ValueError("Campaign not found")
        
        if campaign.status not in [CampaignStatus.draft, CampaignStatus.scheduled]:
            raise ValueError(f"Cannot send campaign with status: {campaign.status}")
        
        # Get recipients (users)
        user_recipients = EmailCampaignService.get_campaign_recipients(db, campaign)
        recipient_type = getattr(campaign, 'recipient_type', 'users') or 'users'
        
        # Get guest subscribers if recipient_type is 'subscribers' or 'both'
        guest_subscribers = []
        if recipient_type in ['subscribers', 'both']:
            guest_subscribers = db.query(NewsletterSubscriber).filter(
                NewsletterSubscriber.is_active == True,
                NewsletterSubscriber.user_id.is_(None)  # Only guest subscribers
            ).all()
        
        # Combine all recipient emails
        all_recipient_emails = set()
        recipient_data = {}  # Store email -> (user_id, name) mapping
        
        # Add user recipients
        for user in user_recipients:
            all_recipient_emails.add(user.email)
            recipient_data[user.email] = (user.id, user.full_name)
        
        # Add guest subscribers
        for subscriber in guest_subscribers:
            if subscriber.email not in all_recipient_emails:
                all_recipient_emails.add(subscriber.email)
                recipient_data[subscriber.email] = (None, subscriber.name)
        
        campaign.total_recipients = len(all_recipient_emails)
        
        # Update status
        campaign.status = CampaignStatus.sending
        db.commit()
        
        sent_count = 0
        failed_count = 0
        
        for email in all_recipient_emails:
            # Check if email is unsubscribed
            if EmailCampaignService._is_unsubscribed(db, email, campaign):
                continue
            
            user_id, name = recipient_data[email]
            
            try:
                # Create campaign email record
                campaign_email = CampaignEmail(
                    campaign_id=campaign.id,
                    user_id=user_id,
                    email=email,
                    status=EmailStatus.pending
                )
                db.add(campaign_email)
                db.flush()
                
                # Replace template variables
                html_content = EmailCampaignService._replace_variables(
                    campaign.html_content or '',
                    name or 'Guest',
                    email
                )
                subject = EmailCampaignService._replace_variables(
                    campaign.subject,
                    name or 'Guest',
                    email
                )
                
                # Send email (async function)
                import asyncio
                try:
                    loop = asyncio.get_event_loop()
                except RuntimeError:
                    loop = asyncio.new_event_loop()
                    asyncio.set_event_loop(loop)
                
                loop.run_until_complete(send_email(
                    to=email,
                    subject=subject,
                    html=html_content,
                    text=campaign.text_content
                ))
                
                campaign_email.status = EmailStatus.sent
                campaign_email.sent_at = datetime.utcnow()
                sent_count += 1
                
            except Exception as e:
                logger.error(f"Failed to send email to {email}: {str(e)}")
                if 'campaign_email' in locals():
                    campaign_email.status = EmailStatus.failed
                    campaign_email.error_message = str(e)
                failed_count += 1
        
        # Update campaign stats
        campaign.total_sent = sent_count
        campaign.status = CampaignStatus.sent
        campaign.sent_at = datetime.utcnow()
        db.commit()
        
        return {
            "sent": sent_count,
            "failed": failed_count,
            "total": len(all_recipient_emails)
        }
    
    @staticmethod
    def _replace_variables(content: str, name: str, email: str) -> str:
        """Replace template variables with user/subscriber data"""
        first_name = name.split()[0] if name and ' ' in name else (name or 'Guest')
        replacements = {
            '{{name}}': name or 'Guest',
            '{{email}}': email,
            '{{first_name}}': first_name,
        }
        
        for key, value in replacements.items():
            content = content.replace(key, str(value))
        
        return content
    
    @staticmethod
    def _is_unsubscribed(db: Session, email: str, campaign: Optional[Campaign] = None) -> bool:
        """Check if email is unsubscribed"""
        query = db.query(Unsubscribe).filter(Unsubscribe.email == email)
        
        # Check for global unsubscribe
        global_unsubscribe = query.filter(Unsubscribe.unsubscribe_all == True).first()
        if global_unsubscribe:
            return True
        
        # Check for campaign-specific unsubscribe
        if campaign:
            campaign_unsubscribe = query.filter(
                Unsubscribe.campaign_id == campaign.id
            ).first()
            if campaign_unsubscribe:
                return True
            
            # Check for type-specific unsubscribe
            type_unsubscribe = query.filter(
                Unsubscribe.unsubscribe_type == campaign.campaign_type.value
            ).first()
            if type_unsubscribe:
                return True
        
        return False
    
    @staticmethod
    def track_email_open(db: Session, campaign_email_id: int):
        """Track email open"""
        campaign_email = db.query(CampaignEmail).filter(
            CampaignEmail.id == campaign_email_id
        ).first()
        
        if campaign_email and campaign_email.status == EmailStatus.sent:
            campaign_email.status = EmailStatus.opened
            campaign_email.opened_at = datetime.utcnow()
            campaign_email.open_count += 1
            campaign_email.last_opened_at = datetime.utcnow()
            
            # Update campaign stats
            campaign = campaign_email.campaign
            campaign.total_opened += 1
            if campaign.total_delivered > 0:
                campaign.open_rate = (campaign.total_opened / campaign.total_delivered) * 100
            
            db.commit()
    
    @staticmethod
    def track_email_click(db: Session, campaign_email_id: int, url: str, ip_address: Optional[str] = None, user_agent: Optional[str] = None):
        """Track email click"""
        campaign_email = db.query(CampaignEmail).filter(
            CampaignEmail.id == campaign_email_id
        ).first()
        
        if campaign_email:
            # Create click record
            click = EmailClick(
                campaign_email_id=campaign_email_id,
                url=url,
                ip_address=ip_address,
                user_agent=user_agent
            )
            db.add(click)
            
            # Update email status
            if campaign_email.status == EmailStatus.opened:
                campaign_email.status = EmailStatus.clicked
                campaign_email.clicked_at = datetime.utcnow()
            
            campaign_email.click_count += 1
            campaign_email.last_clicked_at = datetime.utcnow()
            
            # Update campaign stats
            campaign = campaign_email.campaign
            campaign.total_clicked += 1
            if campaign.total_opened > 0:
                campaign.click_rate = (campaign.total_clicked / campaign.total_opened) * 100
            
            db.commit()
    
    @staticmethod
    def create_segment(
        db: Session,
        name: str,
        criteria: Dict[str, Any],
        description: Optional[str] = None,
        created_by: Optional[int] = None
    ) -> CampaignSegment:
        """Create a new campaign segment"""
        segment = CampaignSegment(
            name=name,
            description=description,
            criteria=criteria,
            created_by=created_by
        )
        db.add(segment)
        db.commit()
        db.refresh(segment)
        
        # Calculate estimated count
        EmailCampaignService._calculate_segment_count(db, segment)
        
        return segment
    
    @staticmethod
    def _calculate_segment_count(db: Session, segment: CampaignSegment):
        """Calculate and update segment estimated count"""
        users = EmailCampaignService._apply_segment_criteria(db, segment.criteria)
        segment.estimated_count = len(users)
        segment.last_calculated_at = datetime.utcnow()
        db.commit()
    
    @staticmethod
    def create_drip_sequence(
        db: Session,
        name: str,
        trigger_event: Optional[str] = None,
        description: Optional[str] = None,
        created_by: Optional[int] = None
    ) -> DripSequence:
        """Create a new drip sequence"""
        sequence = DripSequence(
            name=name,
            description=description,
            trigger_event=trigger_event,
            created_by=created_by
        )
        db.add(sequence)
        db.commit()
        db.refresh(sequence)
        return sequence
    
    @staticmethod
    def add_drip_step(
        db: Session,
        sequence_id: int,
        subject: str,
        html_content: str,
        delay_days: int = 0,
        delay_hours: int = 0,
        step_order: Optional[int] = None,
        text_content: Optional[str] = None,
        template_id: Optional[int] = None
    ) -> DripSequenceStep:
        """Add a step to a drip sequence"""
        if step_order is None:
            # Get next step order
            last_step = db.query(DripSequenceStep).filter(
                DripSequenceStep.sequence_id == sequence_id
            ).order_by(DripSequenceStep.step_order.desc()).first()
            step_order = (last_step.step_order + 1) if last_step else 1
        
        step = DripSequenceStep(
            sequence_id=sequence_id,
            step_order=step_order,
            subject=subject,
            html_content=html_content,
            text_content=text_content,
            template_id=template_id,
            delay_days=delay_days,
            delay_hours=delay_hours
        )
        db.add(step)
        db.commit()
        db.refresh(step)
        return step
    
    @staticmethod
    def enroll_user_in_drip(
        db: Session,
        sequence_id: int,
        user_id: int,
        trigger_data: Optional[Dict[str, Any]] = None
    ) -> DripSequenceEnrollment:
        """Enroll a user in a drip sequence"""
        # Check if already enrolled
        existing = db.query(DripSequenceEnrollment).filter(
            DripSequenceEnrollment.sequence_id == sequence_id,
            DripSequenceEnrollment.user_id == user_id,
            DripSequenceEnrollment.completed == False
        ).first()
        
        if existing:
            return existing
        
        sequence = db.query(DripSequence).filter(DripSequence.id == sequence_id).first()
        if not sequence:
            raise ValueError("Drip sequence not found")
        
        # Get first step
        first_step = db.query(DripSequenceStep).filter(
            DripSequenceStep.sequence_id == sequence_id,
            DripSequenceStep.step_order == 1
        ).first()
        
        if not first_step:
            raise ValueError("Drip sequence has no steps")
        
        # Calculate next send time
        next_send_at = datetime.utcnow() + timedelta(days=first_step.delay_days, hours=first_step.delay_hours)
        
        enrollment = DripSequenceEnrollment(
            sequence_id=sequence_id,
            user_id=user_id,
            current_step=0,
            next_send_at=next_send_at,
            trigger_data=trigger_data
        )
        db.add(enrollment)
        db.commit()
        db.refresh(enrollment)
        return enrollment
    
    @staticmethod
    def process_drip_sequences(db: Session):
        """Process drip sequences and send pending emails"""
        # Get enrollments ready to send
        enrollments = db.query(DripSequenceEnrollment).filter(
            DripSequenceEnrollment.completed == False,
            DripSequenceEnrollment.next_send_at <= datetime.utcnow()
        ).all()
        
        for enrollment in enrollments:
            try:
                sequence = enrollment.sequence
                if not sequence.is_active:
                    continue
                
                # Get next step
                next_step_order = enrollment.current_step + 1
                step = db.query(DripSequenceStep).filter(
                    DripSequenceStep.sequence_id == sequence.id,
                    DripSequenceStep.step_order == next_step_order,
                    DripSequenceStep.is_active == True
                ).first()
                
                if not step:
                    # Sequence completed
                    enrollment.completed = True
                    enrollment.completed_at = datetime.utcnow()
                    db.commit()
                    continue
                
                # Get user
                user = db.query(User).filter(User.id == enrollment.user_id).first()
                if not user or not user.is_active:
                    continue
                
                # Check unsubscribe
                if EmailCampaignService._is_unsubscribed(db, user.email):
                    enrollment.completed = True
                    db.commit()
                    continue
                
                # Send email
                html_content = EmailCampaignService._replace_variables(step.html_content, user)
                subject = EmailCampaignService._replace_variables(step.subject, user)
                
                # Send email (async function)
                import asyncio
                try:
                    loop = asyncio.get_event_loop()
                except RuntimeError:
                    loop = asyncio.new_event_loop()
                    asyncio.set_event_loop(loop)
                
                loop.run_until_complete(send_email(
                    to=user.email,
                    subject=subject,
                    html=html_content,
                    text=step.text_content
                ))
                
                # Update enrollment
                enrollment.current_step = next_step_order
                
                # Calculate next send time
                next_step = db.query(DripSequenceStep).filter(
                    DripSequenceStep.sequence_id == sequence.id,
                    DripSequenceStep.step_order == next_step_order + 1
                ).first()
                
                if next_step:
                    enrollment.next_send_at = datetime.utcnow() + timedelta(
                        days=next_step.delay_days,
                        hours=next_step.delay_hours
                    )
                else:
                    enrollment.completed = True
                    enrollment.completed_at = datetime.utcnow()
                
                db.commit()
                
            except Exception as e:
                logger.error(f"Error processing drip enrollment {enrollment.id}: {str(e)}")
                db.rollback()
    
    @staticmethod
    def handle_abandoned_booking(db: Session, booking_id: int):
        """Handle abandoned booking recovery email"""
        booking = db.query(Booking).filter(Booking.id == booking_id).first()
        if not booking:
            return
        
        # Find abandoned booking recovery sequence
        sequence = db.query(DripSequence).filter(
            DripSequence.trigger_event == 'checkout_abandoned',
            DripSequence.is_active == True
        ).first()
        
        if sequence and booking.user_id:
            EmailCampaignService.enroll_user_in_drip(
                db=db,
                sequence_id=sequence.id,
                user_id=booking.user_id,
                trigger_data={"booking_id": booking_id}
            )

email_campaign_service = EmailCampaignService()

