# -*- coding: utf-8 -*-
"""
Adtlas Activities Cleanup Management Command

This Django management command provides functionality to clean up
old activities based on retention policies, helping maintain
database performance and manage storage space.

Usage:
    python manage.py cleanup_activities [options]

Options:
    --days: Number of days to keep (default: 90)
    --dry-run: Show what would be deleted without actually deleting
    --batch-size: Number of records to delete in each batch (default: 1000)
    --category: Specific category code to clean up
    --user: Specific user ID to clean up
    --force: Skip confirmation prompt

Author: Adtlas Development Team
Version: 1.0.0
Last Updated: 2025-01-27
"""

import logging
from datetime import datetime, timedelta
from typing import Optional

from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from django.utils import timezone
from django.db import transaction
from django.contrib.auth import get_user_model

from ...models import Activity, ActivityCategory, ActivitySummary
from ...signals import activity_cleanup_completed

# Get the user model
User = get_user_model()

# Set up logging
logger = logging.getLogger(__name__)


class Command(BaseCommand):
    """
    Django management command to clean up old activities.
    
    This command removes activities older than the specified
    retention period to maintain database performance and
    manage storage space effectively.
    """
    
    help = 'Clean up old activities based on retention policy'
    
    def add_arguments(self, parser):
        """
        Add command line arguments.
        
        Args:
            parser: ArgumentParser instance
        """
        parser.add_argument(
            '--days',
            type=int,
            default=getattr(settings, 'ACTIVITIES_RETENTION_DAYS', 90),
            help='Number of days to keep activities (default: 90)'
        )
        
        parser.add_argument(
            '--dry-run',
            action='store_true',
            help='Show what would be deleted without actually deleting'
        )
        
        parser.add_argument(
            '--batch-size',
            type=int,
            default=1000,
            help='Number of records to delete in each batch (default: 1000)'
        )
        
        parser.add_argument(
            '--category',
            type=str,
            help='Specific category code to clean up'
        )
        
        parser.add_argument(
            '--user',
            type=int,
            help='Specific user ID to clean up'
        )
        
        parser.add_argument(
            '--force',
            action='store_true',
            help='Skip confirmation prompt'
        )
        
        parser.add_argument(
            '--verbose',
            action='store_true',
            help='Enable verbose output'
        )
    
    def handle(self, *args, **options):
        """
        Handle the command execution.
        
        Args:
            *args: Positional arguments
            **options: Command options
        """
        # Extract options
        days_to_keep = options['days']
        dry_run = options['dry_run']
        batch_size = options['batch_size']
        category_code = options.get('category')
        user_id = options.get('user')
        force = options['force']
        verbose = options['verbose']
        
        # Validate arguments
        if days_to_keep < 1:
            raise CommandError('Days to keep must be at least 1')
        
        if batch_size < 1:
            raise CommandError('Batch size must be at least 1')
        
        # Calculate cutoff date
        cutoff_date = timezone.now() - timedelta(days=days_to_keep)
        
        self.stdout.write(
            self.style.SUCCESS(
                f'Activity Cleanup Command\n'
                f'========================\n'
                f'Retention period: {days_to_keep} days\n'
                f'Cutoff date: {cutoff_date.strftime("%Y-%m-%d %H:%M:%S")}\n'
                f'Batch size: {batch_size}\n'
                f'Dry run: {"Yes" if dry_run else "No"}\n'
            )
        )
        
        try:
            # Build query
            queryset = Activity.objects.filter(created_at__lt=cutoff_date)
            
            # Apply filters
            if category_code:
                try:
                    category = ActivityCategory.objects.get(code=category_code)
                    queryset = queryset.filter(category=category)
                    self.stdout.write(f'Filtering by category: {category.name}')
                except ActivityCategory.DoesNotExist:
                    raise CommandError(f'Category with code "{category_code}" not found')
            
            if user_id:
                try:
                    user = User.objects.get(id=user_id)
                    queryset = queryset.filter(user=user)
                    self.stdout.write(f'Filtering by user: {user.email}')
                except User.DoesNotExist:
                    raise CommandError(f'User with ID {user_id} not found')
            
            # Count activities to be deleted
            total_count = queryset.count()
            
            if total_count == 0:
                self.stdout.write(
                    self.style.WARNING('No activities found matching the criteria.')
                )
                return
            
            self.stdout.write(
                self.style.WARNING(
                    f'Found {total_count:,} activities to be deleted.'
                )
            )
            
            # Show sample activities if verbose
            if verbose:
                self.stdout.write('\nSample activities to be deleted:')
                sample_activities = queryset.select_related(
                    'user', 'category'
                )[:10]
                
                for activity in sample_activities:
                    user_email = activity.user.email if activity.user else 'System'
                    category_name = activity.category.name if activity.category else 'None'
                    self.stdout.write(
                        f'  - {activity.created_at.strftime("%Y-%m-%d %H:%M")} | '
                        f'{user_email} | {activity.action} | {category_name}'
                    )
                
                if total_count > 10:
                    self.stdout.write(f'  ... and {total_count - 10:,} more')
            
            # Confirmation prompt
            if not dry_run and not force:
                confirm = input(
                    f'\nAre you sure you want to delete {total_count:,} activities? '
                    f'This action cannot be undone. [y/N]: '
                )
                
                if confirm.lower() not in ['y', 'yes']:
                    self.stdout.write(
                        self.style.WARNING('Operation cancelled by user.')
                    )
                    return
            
            if dry_run:
                self.stdout.write(
                    self.style.SUCCESS(
                        f'\nDry run completed. {total_count:,} activities would be deleted.'
                    )
                )
                return
            
            # Perform deletion
            self.stdout.write('\nStarting deletion process...')
            start_time = timezone.now()
            deleted_count = 0
            
            # Delete in batches to avoid memory issues
            while True:
                with transaction.atomic():
                    # Get batch of activity IDs
                    batch_ids = list(
                        queryset.values_list('id', flat=True)[:batch_size]
                    )
                    
                    if not batch_ids:
                        break
                    
                    # Delete batch
                    batch_deleted, _ = Activity.objects.filter(
                        id__in=batch_ids
                    ).delete()
                    
                    deleted_count += batch_deleted
                    
                    if verbose:
                        self.stdout.write(
                            f'Deleted batch: {batch_deleted:,} activities '
                            f'(Total: {deleted_count:,}/{total_count:,})'
                        )
            
            end_time = timezone.now()
            duration = (end_time - start_time).total_seconds()
            
            # Send cleanup completed signal
            activity_cleanup_completed.send(
                sender=self.__class__,
                deleted_count=deleted_count
            )
            
            # Clean up orphaned activity summaries
            orphaned_summaries = self._cleanup_orphaned_summaries(cutoff_date)
            
            # Display results
            self.stdout.write(
                self.style.SUCCESS(
                    f'\nCleanup completed successfully!\n'
                    f'Activities deleted: {deleted_count:,}\n'
                    f'Orphaned summaries cleaned: {orphaned_summaries:,}\n'
                    f'Duration: {duration:.2f} seconds\n'
                    f'Average rate: {deleted_count/duration:.0f} activities/second'
                )
            )
            
            # Log the cleanup
            logger.info(
                f'Activity cleanup completed: {deleted_count} activities deleted '
                f'(retention: {days_to_keep} days, duration: {duration:.2f}s)'
            )
            
        except Exception as e:
            error_msg = f'Error during cleanup: {str(e)}'
            self.stdout.write(self.style.ERROR(error_msg))
            logger.error(error_msg, exc_info=True)
            raise CommandError(error_msg)
    
    def _cleanup_orphaned_summaries(self, cutoff_date: datetime) -> int:
        """
        Clean up orphaned activity summaries.
        
        Args:
            cutoff_date: Cutoff date for cleanup
        
        Returns:
            int: Number of orphaned summaries deleted
        """
        try:
            # Find summaries for dates where no activities exist
            orphaned_summaries = ActivitySummary.objects.filter(
                date__lt=cutoff_date.date()
            )
            
            # Check if there are any activities for these dates
            for summary in orphaned_summaries:
                activities_exist = Activity.objects.filter(
                    created_at__date=summary.date
                ).exists()
                
                if activities_exist:
                    # Remove from deletion if activities still exist
                    orphaned_summaries = orphaned_summaries.exclude(id=summary.id)
            
            deleted_count, _ = orphaned_summaries.delete()
            return deleted_count
            
        except Exception as e:
            logger.warning(f'Failed to cleanup orphaned summaries: {str(e)}')
            return 0