"""
Automated Reminder Service for RateRight
Handles scheduling and sending of automated reminders for bookings and availability
"""

import os
from datetime import datetime, timedelta
from typing import Dict, List, Optional
import logging
from celery import Celery
from flask import current_app

from app.models import db
from app.models.booking import Booking, RecurringBooking
from app.models.availability import Availability
from app.models.user import User
from app.models.notification import Notification
from app.services.email_service import EmailService
from app.services.push_notification_service import PushNotificationService

logger = logging.getLogger(__name__)

# Initialize Celery
celery = Celery(
    'rateright_reminders',
    broker=os.environ.get('CELERY_BROKER_URL', 'redis://localhost:6379/0'),
    backend=os.environ.get('CELERY_RESULT_BACKEND', 'redis://localhost:6379/0')
)

# Configure Celery
celery.conf.update(
    timezone='Australia/Sydney',
    task_serializer='json',
    accept_content=['json'],
    result_serializer='json',
    beat_schedule={
        # Check for reminders every 15 minutes
        'check-booking-reminders': {
            'task': 'app.services.reminder_service.check_booking_reminders',
            'schedule': timedelta(minutes=15),
        },
        # Daily check for upcoming availability gaps
        'check-availability-gaps': {
            'task': 'app.services.reminder_service.check_availability_gaps',
            'schedule': timedelta(days=1),
            'options': {'hour': 9, 'minute': 0}  # Run at 9 AM daily
        },
        # Weekly summary
        'send-weekly-summary': {
            'task': 'app.services.reminder_service.send_weekly_summary',
            'schedule': timedelta(weeks=1),
            'options': {'day_of_week': 1, 'hour': 8, 'minute': 0}  # Monday 8 AM
        },
        # Check for incomplete time entries
        'check-incomplete-time-entries': {
            'task': 'app.services.reminder_service.check_incomplete_time_entries',
            'schedule': timedelta(hours=1),
        }
    }
)

class ReminderService:
    """Service for managing automated reminders"""
    
    # Reminder timing configurations
    REMINDER_TIMES = {
        '1_week_before': timedelta(days=7),
        '3_days_before': timedelta(days=3),
        '1_day_before': timedelta(days=1),
        '2_hours_before': timedelta(hours=2),
        '30_minutes_before': timedelta(minutes=30),
        '15_minutes_after_start': timedelta(minutes=-15),  # Negative for after
    }
    
    @classmethod
    def schedule_booking_reminders(cls, booking_id: int):
        """Schedule all reminders for a booking"""
        booking = Booking.query.get(booking_id)
        if not booking or booking.status != 'confirmed':
            return
        
        worker = User.query.get(booking.worker_id)
        client = User.query.get(booking.client_id)
        
        if not worker or not client:
            return
        
        # Get reminder preferences
        worker_prefs = worker.notification_preferences or {}
        client_prefs = client.notification_preferences or {}
        
        # Schedule worker reminders
        if worker_prefs.get('booking_reminders', True):
            cls._schedule_worker_reminders(booking, worker)
        
        # Schedule client reminders
        if client_prefs.get('booking_reminders', True):
            cls._schedule_client_reminders(booking, client)
    
    @classmethod
    def _schedule_worker_reminders(cls, booking: Booking, worker: User):
        """Schedule reminders for the worker"""
        reminders = [
            ('1_day_before', 'Reminder: You have a booking tomorrow'),
            ('2_hours_before', 'Upcoming booking in 2 hours'),
            ('30_minutes_before', 'Booking starts in 30 minutes')
        ]
        
        for reminder_key, message in reminders:
            reminder_time = booking.scheduled_datetime - cls.REMINDER_TIMES[reminder_key]
            
            if reminder_time > datetime.utcnow():
                # Schedule the reminder task
                send_reminder.apply_async(
                    args=[worker.id, booking.id, message, 'booking_reminder'],
                    eta=reminder_time
                )
                logger.info(f"Scheduled {reminder_key} reminder for booking {booking.id}")
    
    @classmethod
    def _schedule_client_reminders(cls, booking: Booking, client: User):
        """Schedule reminders for the client"""
        reminders = [
            ('1_day_before', 'Reminder: Your booking is tomorrow'),
            ('2_hours_before', 'Your service starts in 2 hours'),
            ('15_minutes_after_start', 'How was your service? Please leave a review')
        ]
        
        for reminder_key, message in reminders:
            if reminder_key == '15_minutes_after_start':
                reminder_time = booking.scheduled_datetime + timedelta(
                    hours=booking.duration_hours, 
                    minutes=15
                )
            else:
                reminder_time = booking.scheduled_datetime - cls.REMINDER_TIMES[reminder_key]
            
            if reminder_time > datetime.utcnow():
                send_reminder.apply_async(
                    args=[client.id, booking.id, message, 'booking_reminder'],
                    eta=reminder_time
                )
    
    @classmethod
    def cancel_booking_reminders(cls, booking_id: int):
        """Cancel all scheduled reminders for a booking"""
        # In production, you'd track task IDs and cancel them
        # For now, we'll mark them as cancelled in the database
        Notification.query.filter_by(
            related_id=booking_id,
            type='booking_reminder',
            read=False
        ).update({'read': True})
        db.session.commit()
        logger.info(f"Cancelled reminders for booking {booking_id}")
    
    @classmethod
    def send_availability_reminder(cls, worker_id: int):
        """Remind worker to update their availability"""
        worker = User.query.get(worker_id)
        if not worker:
            return
        
        # Check last availability update
        last_availability = Availability.query.filter_by(
            worker_id=worker_id
        ).order_by(Availability.created_at.desc()).first()
        
        if not last_availability or \
           (datetime.utcnow() - last_availability.created_at).days > 7:
            
            message = "Don't forget to update your availability for next week!"
            
            # Send notification
            notification = Notification(
                user_id=worker_id,
                type='availability_reminder',
                title='Update Your Availability',
                message=message,
                priority='medium'
            )
            db.session.add(notification)
            db.session.commit()
            
            # Send email
            EmailService.send_reminder_email(
                worker.email,
                'Update Your Availability',
                message
            )
            
            # Send push notification if enabled
            if worker.push_subscriptions:
                PushNotificationService.send_to_user(
                    worker_id,
                    'Update Your Availability',
                    message
                )
    
    @classmethod
    def send_shift_reminder(cls, shift_id: int):
        """Send reminder for upcoming shift"""
        from app.models.time_tracking import Shift
        
        shift = Shift.query.get(shift_id)
        if not shift:
            return
        
        # Send reminders to all assigned workers
        for worker_id in shift.assigned_workers:
            worker = User.query.get(worker_id)
            if worker:
                message = f"Reminder: Your shift starts at {shift.start_time.strftime('%I:%M %p')}"
                
                notification = Notification(
                    user_id=worker_id,
                    type='shift_reminder',
                    title='Shift Reminder',
                    message=message,
                    priority='high',
                    related_id=shift_id
                )
                db.session.add(notification)
        
        db.session.commit()
    
    @classmethod
    def generate_weekly_summary(cls, user_id: int) -> Dict:
        """Generate weekly summary for a user"""
        user = User.query.get(user_id)
        if not user:
            return {}
        
        # Calculate date range
        end_date = datetime.utcnow()
        start_date = end_date - timedelta(days=7)
        
        summary = {
            'user': user.username,
            'period': f"{start_date.strftime('%b %d')} - {end_date.strftime('%b %d, %Y')}",
            'stats': {}
        }
        
        if user.user_type == 'worker':
            # Worker summary
            bookings = Booking.query.filter(
                Booking.worker_id == user_id,
                Booking.scheduled_datetime.between(start_date, end_date)
            ).all()
            
            summary['stats'] = {
                'total_bookings': len(bookings),
                'completed': len([b for b in bookings if b.status == 'completed']),
                'cancelled': len([b for b in bookings if b.status == 'cancelled']),
                'hours_worked': sum(b.duration_hours for b in bookings if b.status == 'completed'),
                'earnings': sum(b.total_amount for b in bookings if b.status == 'completed'),
                'upcoming_this_week': Booking.query.filter(
                    Booking.worker_id == user_id,
                    Booking.scheduled_datetime.between(end_date, end_date + timedelta(days=7)),
                    Booking.status == 'confirmed'
                ).count()
            }
        else:
            # Client summary
            bookings = Booking.query.filter(
                Booking.client_id == user_id,
                Booking.scheduled_datetime.between(start_date, end_date)
            ).all()
            
            summary['stats'] = {
                'total_bookings': len(bookings),
                'completed': len([b for b in bookings if b.status == 'completed']),
                'cancelled': len([b for b in bookings if b.status == 'cancelled']),
                'total_spent': sum(b.total_amount for b in bookings if b.status == 'completed'),
                'upcoming_this_week': Booking.query.filter(
                    Booking.client_id == user_id,
                    Booking.scheduled_datetime.between(end_date, end_date + timedelta(days=7)),
                    Booking.status == 'confirmed'
                ).count()
            }
        
        return summary
    
    @classmethod
    def check_recurring_bookings(cls):
        """Check and create instances for recurring bookings"""
        recurring_bookings = RecurringBooking.query.filter_by(
            is_active=True
        ).all()
        
        for recurring in recurring_bookings:
            # Calculate next occurrence
            next_date = cls._calculate_next_occurrence(
                recurring.last_generated_date or recurring.start_date,
                recurring.recurring_pattern
            )
            
            # Check if we should generate the next instance
            if next_date <= datetime.utcnow() + timedelta(days=7):
                # Create booking instance
                booking = Booking(
                    worker_id=recurring.worker_id,
                    client_id=recurring.client_id,
                    recurring_booking_id=recurring.id,
                    scheduled_datetime=next_date,
                    duration_hours=recurring.duration_hours,
                    service_description=recurring.service_description,
                    status='pending',
                    is_recurring=True
                )
                db.session.add(booking)
                
                # Update last generated date
                recurring.last_generated_date = next_date
                
                # Send notification to worker
                notification = Notification(
                    user_id=recurring.worker_id,
                    type='recurring_booking_created',
                    title='New Recurring Booking',
                    message=f'A new booking has been created for {next_date.strftime("%b %d")}',
                    related_id=booking.id
                )
                db.session.add(notification)
        
        db.session.commit()
    
    @classmethod
    def _calculate_next_occurrence(cls, last_date: datetime, pattern: str) -> datetime:
        """Calculate next occurrence based on pattern"""
        if pattern == 'daily':
            return last_date + timedelta(days=1)
        elif pattern == 'weekly':
            return last_date + timedelta(weeks=1)
        elif pattern == 'biweekly':
            return last_date + timedelta(weeks=2)
        elif pattern == 'monthly':
            # Add one month (approximate)
            next_month = last_date.month + 1
            year = last_date.year
            if next_month > 12:
                next_month = 1
                year += 1
            return last_date.replace(month=next_month, year=year)
        else:
            return last_date + timedelta(weeks=1)  # Default to weekly

# Celery Tasks
@celery.task
def send_reminder(user_id: int, related_id: int, message: str, reminder_type: str):
    """Send a reminder to a user"""
    with current_app.app_context():
        user = User.query.get(user_id)
        if not user:
            return
        
        # Create notification
        notification = Notification(
            user_id=user_id,
            type=reminder_type,
            title='Reminder',
            message=message,
            priority='high',
            related_id=related_id
        )
        db.session.add(notification)
        db.session.commit()
        
        # Send email
        EmailService.send_reminder_email(user.email, 'Reminder', message)
        
        # Send push notification if enabled
        if user.push_subscriptions:
            PushNotificationService.send_to_user(user_id, 'Reminder', message)
        
        logger.info(f"Sent reminder to user {user_id}: {message}")

@celery.task
def check_booking_reminders():
    """Check and send booking reminders"""
    with current_app.app_context():
        # Get bookings that need reminders
        upcoming_bookings = Booking.query.filter(
            Booking.status == 'confirmed',
            Booking.scheduled_datetime.between(
                datetime.utcnow(),
                datetime.utcnow() + timedelta(days=1)
            )
        ).all()
        
        for booking in upcoming_bookings:
            ReminderService.schedule_booking_reminders(booking.id)

@celery.task
def check_availability_gaps():
    """Check for workers with availability gaps"""
    with current_app.app_context():
        # Get all active workers
        workers = User.query.filter_by(
            user_type='worker',
            is_active=True
        ).all()
        
        for worker in workers:
            # Check if they have availability for next week
            next_week_start = datetime.utcnow() + timedelta(days=7)
            next_week_end = next_week_start + timedelta(days=7)
            
            availability_count = Availability.query.filter(
                Availability.worker_id == worker.id,
                Availability.start_datetime.between(next_week_start, next_week_end)
            ).count()
            
            if availability_count < 3:  # Less than 3 availability slots
                ReminderService.send_availability_reminder(worker.id)

@celery.task
def send_weekly_summary():
    """Send weekly summary to all users"""
    with current_app.app_context():
        # Get all active users who want summaries
        users = User.query.filter_by(is_active=True).all()
        
        for user in users:
            prefs = user.notification_preferences or {}
            if prefs.get('weekly_summary', True):
                summary = ReminderService.generate_weekly_summary(user.id)
                
                # Format and send summary
                if summary.get('stats'):
                    EmailService.send_weekly_summary_email(
                        user.email,
                        summary
                    )
                    logger.info(f"Sent weekly summary to user {user.id}")

@celery.task
def check_incomplete_time_entries():
    """Check for workers who forgot to clock out"""
    with current_app.app_context():
        from app.models.time_tracking import TimeEntry
        
        # Find entries without clock out from more than 12 hours ago
        cutoff_time = datetime.utcnow() - timedelta(hours=12)
        
        incomplete_entries = TimeEntry.query.filter(
            TimeEntry.clock_out_time.is_(None),
            TimeEntry.clock_in_time < cutoff_time
        ).all()
        
        for entry in incomplete_entries:
            # Send reminder to clock out
            notification = Notification(
                user_id=entry.worker_id,
                type='clock_out_reminder',
                title='Forgot to Clock Out?',
                message=f'You have been clocked in since {entry.clock_in_time.strftime("%I:%M %p")}. Please clock out.',
                priority='high'
            )
            db.session.add(notification)
        
        db.session.commit()

@celery.task
def check_recurring_bookings():
    """Check and create recurring booking instances"""
    with current_app.app_context():
        ReminderService.check_recurring_bookings()
