import time
from datetime import datetime, timedelta

from django.db.models import Q
from django.http import JsonResponse
from django.utils import timezone

from backup.models import BackupProfile
from backup_data.models import RouterBackup
from message_center.functions import notify_backup_fail
from router_manager.models import Router, BackupSchedule, RouterStatus
from routerlib.backup_functions import perform_backup
from message_center.models import Message


def next_weekday(now, weekday, hour):
    now = timezone.localtime(timezone.now())
    days_ahead = weekday - now.weekday()
    if days_ahead < 0 or (days_ahead == 0 and now.hour >= hour):
        days_ahead += 7
    next_backup = now + timedelta(days=days_ahead)
    next_backup = next_backup.replace(hour=hour, minute=0, second=0, microsecond=0)
    return next_backup


def find_next_active_day(start_date, active_days, backup_hour):
    now = timezone.localtime()
    for i in range(7):
        potential_date = timezone.localtime(start_date + timedelta(days=i))
        weekday = potential_date.weekday()
        if active_days[weekday]:
            next_active_date = potential_date.replace(hour=backup_hour, minute=0, second=0, microsecond=0)
            if next_active_date > now:
                return next_active_date
            if i == 0 and now.hour >= backup_hour:
                continue
    return None


def calculate_next_backup(backup_profile):
    now = timezone.now()

    if backup_profile.daily_backup:
        weekdays_enabled = [
            backup_profile.daily_day_monday,
            backup_profile.daily_day_tuesday,
            backup_profile.daily_day_wednesday,
            backup_profile.daily_day_thursday,
            backup_profile.daily_day_friday,
            backup_profile.daily_day_saturday,
            backup_profile.daily_day_sunday,
        ]
        next_daily_backup = find_next_active_day(now, weekdays_enabled, backup_profile.daily_hour)
    else:
        next_daily_backup = None

    if backup_profile.weekly_backup:
        weekday = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'].index(backup_profile.weekly_day)
        next_weekly_backup = next_weekday(now, weekday, backup_profile.weekly_hour)
    else:
        next_weekly_backup = None

    if backup_profile.monthly_backup:
        potential_monthly_backup = datetime(year=now.year, month=now.month, day=backup_profile.monthly_day,
                                            hour=backup_profile.monthly_hour, tzinfo=timezone.get_current_timezone())
        if potential_monthly_backup <= now:
            month_increment = 1 if now.month < 12 else -11
            year_increment = 0 if now.month < 12 else 1
            next_monthly_backup = potential_monthly_backup.replace(year=now.year + year_increment,
                                                                   month=now.month + month_increment)
        else:
            next_monthly_backup = potential_monthly_backup
    else:
        next_monthly_backup = None

    return next_daily_backup, next_weekly_backup, next_monthly_backup


def view_generate_backup_schedule(request):
    data = {
        'backup_schedule_created': 0,
        'daily_backup_schedule_created': 0,
        'weekly_backup_schedule_created': 0,
        'monthly_backup_schedule_created': 0,
        'daily_backup_schedule_removed': 0,
        'weekly_backup_schedule_removed': 0,
        'monthly_backup_schedule_removed': 0
    }

    for router in Router.objects.filter(backupschedule__isnull=True):
        new_backup_schedule, _ = BackupSchedule.objects.get_or_create(router=router)
        data['backup_schedule_created'] += 1

    for backup_profile in BackupProfile.objects.all():
        backup_schedule_list = BackupSchedule.objects.filter(router__backup_profile=backup_profile)
        next_daily_backup, next_weekly_backup, next_monthly_backup = calculate_next_backup(backup_profile)

        if backup_profile.daily_backup and not next_daily_backup:
            backup_profile.profile_error_information = 'Error calculating next daily backup. Check profile settings'
            backup_profile.save()
        if backup_profile.weekly_backup and not next_weekly_backup:
            backup_profile.profile_error_information = 'Error calculating next weekly backup. Check profile settings'
            backup_profile.save()
        if backup_profile.monthly_backup and not next_monthly_backup:
            backup_profile.profile_error_information = 'Error calculating next monthly backup. Check profile settings'
            backup_profile.save()

        if backup_profile.daily_backup:
            daily_schedule_list = backup_schedule_list.filter(next_daily_backup__isnull=True)
            for schedule in daily_schedule_list:
                schedule.next_daily_backup = next_daily_backup
                schedule.save()
                data['daily_backup_schedule_created'] += 1
        else:
            daily_schedule_list = backup_schedule_list.filter(next_daily_backup__isnull=False)
            for schedule in daily_schedule_list:
                schedule.next_daily_backup = None
                schedule.save()
                data['daily_backup_schedule_removed'] += 1

        if backup_profile.weekly_backup:
            weekly_schedule_list = backup_schedule_list.filter(next_weekly_backup__isnull=True)
            for schedule in weekly_schedule_list:
                schedule.next_weekly_backup = next_weekly_backup
                schedule.save()
                data['weekly_backup_schedule_created'] += 1
        else:
            weekly_schedule_list = backup_schedule_list.filter(next_weekly_backup__isnull=False)
            for schedule in weekly_schedule_list:
                schedule.next_weekly_backup = None
                schedule.save()
                data['weekly_backup_schedule_removed'] += 1

        if backup_profile.monthly_backup:
            monthly_schedule_list = backup_schedule_list.filter(next_monthly_backup__isnull=True)
            for schedule in monthly_schedule_list:
                schedule.next_monthly_backup = next_monthly_backup
                schedule.save()
                data['monthly_backup_schedule_created'] += 1
        else:
            monthly_schedule_list = backup_schedule_list.filter(next_monthly_backup__isnull=False)
            for schedule in monthly_schedule_list:
                schedule.next_monthly_backup = None
                schedule.save()
                data['monthly_backup_schedule_removed'] += 1
    return JsonResponse(data)


def create_backup_tasks_from_schedule_list(schedule_list, schedule_type):
    tasks_created = 0
    for schedule in schedule_list:
        if schedule_type == 'daily':
            schedule_time = schedule.next_daily_backup
            schedule.next_daily_backup = None
        elif schedule_type == 'weekly':
            schedule_time = schedule.next_weekly_backup
            schedule.next_weekly_backup = None
        elif schedule_type == 'monthly':
            schedule_time = schedule.next_monthly_backup
            schedule.next_monthly_backup = None
        else:
            return
        schedule.save()

        backup = RouterBackup.objects.create(
            router=schedule.router, schedule_time=schedule_time, schedule_type=schedule_type
        )
        tasks_created += 1
        backup.save()
        backup.router.routerstatus.backup_lock = backup.schedule_time
        backup.router.routerstatus.save()

    return tasks_created


def view_create_backup_tasks(request):
    data = {
        'daily_backup_tasks_created': 0,
        'weekly_backup_tasks_created': 0,
        'monthly_backup_tasks_created': 0
    }
    # Priorize monthly, then weekly, then daily.
    monthly_pending_schedule_list = BackupSchedule.objects.filter(
        next_monthly_backup__lte=timezone.now(), router__enabled=True, router__routerstatus__backup_lock__isnull=True
    ).filter(
        Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True)
    )
    data['monthly_backup_tasks_created'] = create_backup_tasks_from_schedule_list(
        monthly_pending_schedule_list, 'monthly'
    )

    weekly_pending_schedule_list = BackupSchedule.objects.filter(
        next_weekly_backup__lte=timezone.now(), router__enabled=True, router__routerstatus__backup_lock__isnull=True
    ).filter(
        Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True)
    )
    data['weekly_backup_tasks_created'] = create_backup_tasks_from_schedule_list(
        weekly_pending_schedule_list, 'weekly'
    )

    daily_pending_schedule_list = BackupSchedule.objects.filter(
        next_daily_backup__lte=timezone.now(), router__enabled=True, router__routerstatus__backup_lock__isnull=True
    ).filter(
        Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True)
    )
    data['daily_backup_tasks_created'] = create_backup_tasks_from_schedule_list(
        daily_pending_schedule_list, 'daily'
    )

    return JsonResponse(data)


def view_perform_backup_tasks(request):
    data = {
        'backup_tasks_performed': 0
    }
    max_execution_time = 45  # seconds
    execution_start_time = timezone.now()
    pending_backup_list = RouterBackup.objects.filter(success=False, error=False).filter(
        Q(schedule_time__lte=timezone.now(), next_retry__isnull=True) | Q(next_retry__lte=timezone.now())
    ).filter(
        Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True)
    )

    for backup in pending_backup_list:
        perform_backup(backup)
        data['backup_tasks_performed'] += 1
        if backup.router.backup_profile.backup_interval >= 60:
            break
        else:
            if timezone.now() - execution_start_time > timedelta(seconds=max_execution_time):
                break
            else:
                if backup.router.backup_profile.backup_interval > 0:
                    time.sleep(backup.router.backup_profile.backup_interval)
                if timezone.now() - execution_start_time > timedelta(seconds=max_execution_time):
                    break

    return JsonResponse(data)


def view_housekeeping(requests):
    max_backup_task_age = timezone.now() - timedelta(hours=18)
    data = {
        'backup_tasks_expired': 0,
        'backup_locks_removed': 0,
        'messages_removed': 0,
    }
    for backup in RouterBackup.objects.filter(created__lt=max_backup_task_age, success=False, error=False):
        backup.error = True
        backup.error_message = 'Backup task expired'
        backup.save()
        backup.router.routerstatus.last_backup_failed = timezone.now()
        backup.router.routerstatus.save()
        notify_backup_fail(backup)

        if not RouterBackup.objects.filter(router=backup.router, success=False, error=False).exists():
            backup.router.routerstatus.backup_lock = None
            backup.router.routerstatus.save()
        data['backup_tasks_expired'] += 1  # Do not count locks removed for expired tasks

    for router_status in RouterStatus.objects.filter(backup_lock__lt=max_backup_task_age):
        if not RouterBackup.objects.filter(router=router_status.router, success=False, error=False).exists():
            router_status.backup_lock = None
            router_status.last_backup_failed = timezone.now()
            router_status.save()
            data['backup_locks_removed'] += 1

    for backup_profile in BackupProfile.objects.all():
        if backup_profile.name == 'default':
            backup_list = RouterBackup.objects.filter(Q(router__backup_profile=backup_profile) | Q(router__backup_profile__isnull=True))
        else:
            backup_list = RouterBackup.objects.filter(router__backup_profile=backup_profile)

        if backup_profile.retain_backups_on_error:
            backup_list = backup_list.filter(router__routerstatus__last_backup_failed__isnull=True)

        backup_list.filter(schedule_type='instant', created__lt=timezone.now() - timedelta(days=backup_profile.instant_retention)).delete()
        backup_list.filter(schedule_type='monthly', created__lt=timezone.now() - timedelta(days=backup_profile.monthly_retention)).delete()
        backup_list.filter(schedule_type='weekly', created__lt=timezone.now() - timedelta(days=backup_profile.weekly_retention)).delete()
        backup_list.filter(schedule_type='daily', created__lt=timezone.now() - timedelta(days=backup_profile.daily_retention)).delete()

    expired_messages = Message.objects.filter(created__lt=timezone.now() - timedelta(days=30))
    data['messages_removed'] = expired_messages.count()
    expired_messages.delete()

    return JsonResponse(data)
