from django.core.management.base import BaseCommand, CommandError
from bazi.models import Person
from django.db import transaction
import time
import sys


class Command(BaseCommand):
    help = 'Updates all Person records with missing number_result values'

    def add_arguments(self, parser):
        parser.add_argument(
            '--batch-size',
            type=int,
            default=100,
            help='Number of records to process in each batch',
        )
        parser.add_argument(
            '--sleep',
            type=float,
            default=0.1,
            help='Sleep time between batches to reduce server load (in seconds)',
        )
        parser.add_argument(
            '--dry-run',
            action='store_true',
            help='Perform a dry run without saving changes',
        )
        parser.add_argument(
            '--all',
            action='store_true',
            help='Recalculate all records, not just those with missing number_result',
        )

    def handle(self, *args, **options):
        batch_size = options['batch_size']
        sleep_time = options['sleep']
        dry_run = options['dry_run']
        recalculate_all = options['all']
        
        # Get record counts
        records_with_birth_date = Person.objects.filter(birth_date__isnull=False)
        total_records_with_birth_date = records_with_birth_date.count()
        
        if recalculate_all:
            # Recalculate all records with birth_date
            query = records_with_birth_date
            total_records = total_records_with_birth_date
            self.stdout.write(self.style.SUCCESS(f"Found {total_records} total records with birth_date to recalculate"))
        else:
            # Only calculate for records missing number_result
            total_records = Person.objects.filter(number_result__isnull=True, birth_date__isnull=False).count()
            self.stdout.write(self.style.SUCCESS(f"Found {total_records_with_birth_date} total records with birth_date"))
            self.stdout.write(self.style.SUCCESS(f"Found {total_records} records without number_result"))
            query = Person.objects.filter(
                number_result__isnull=True,
                birth_date__isnull=False
            )
        
        if dry_run:
            self.stdout.write(self.style.WARNING("DRY RUN - No changes will be saved"))
        
        if total_records == 0:
            self.stdout.write(self.style.SUCCESS("No records need updating. Exiting."))
            return
            
        # Confirm with user
        if not dry_run and not options.get('no_input', False):
            confirm = input(f"Update {total_records} records? [y/N]: ")
            if confirm.lower() != 'y':
                self.stdout.write(self.style.WARNING("Operation cancelled."))
                return
        
        # Process in batches to avoid memory issues
        processed = 0
        success = 0
        errors = 0
        start_time = time.time()
        
        # Order by ID for consistent batching
        query = query.order_by('id')
        
        # Process in batches
        offset = 0
        while offset < total_records:
            batch = query[offset:offset+batch_size]
            
            # Update records in the batch
            with transaction.atomic():
                for person in batch:
                    try:
                        self.stdout.write(f"Processing {person.name} (ID: {person.id})")
                        number_result = person.calculate_number_result()
                        
                        if not dry_run:
                            person.number_result = number_result
                            person.save(update_fields=['number_result'])
                        
                        success += 1
                    except Exception as e:
                        errors += 1
                        self.stdout.write(self.style.ERROR(
                            f"Error processing person ID {person.id}: {str(e)}"
                        ))
            
            processed += len(batch)
            offset += batch_size
            
            # Update progress
            elapsed = time.time() - start_time
            percent_complete = (processed / total_records) * 100
            remaining = 0 if processed == 0 else (elapsed / processed) * (total_records - processed)
            
            self.stdout.write(
                f"Progress: {processed}/{total_records} ({percent_complete:.1f}%) | "
                f"Success: {success} | Errors: {errors} | "
                f"Elapsed: {elapsed:.1f}s | Est. remaining: {remaining:.1f}s"
            )
            
            # Sleep between batches to reduce server load
            if sleep_time > 0 and offset < total_records:
                time.sleep(sleep_time)
        
        # Final report
        total_time = time.time() - start_time
        
        if dry_run:
            self.stdout.write(self.style.SUCCESS(
                f"DRY RUN COMPLETED: Would have updated {success} records ({errors} errors) in {total_time:.1f} seconds"
            ))
        else:
            self.stdout.write(self.style.SUCCESS(
                f"Successfully updated {success} records ({errors} errors) in {total_time:.1f} seconds"
            )) 
"""
## How to Update Number Results in Production

I've created a Django management command that will bulk update all Person records with the new number_result feature. Here's how to use it on your production server:

### Command Overview

The management command is located at `bazi/management/commands/update_number_results.py` and provides the following options:

- `--batch-size`: Number of records to process in each batch (default: 100)
- `--sleep`: Time to sleep between batches to reduce server load (default: 0.1 seconds)
- `--dry-run`: Run without saving any changes (for testing)
- `--all`: Process all records, not just those with missing number_result

### Steps for Production Deployment

1. **First, back up your database**:
   ```bash
   # For SQLite
   cp db.sqlite3 db.sqlite3.backup
   
   # For MySQL/PostgreSQL
   # Use the appropriate database backup command
   # e.g., mysqldump or pg_dump
   ```

2. **Test the command with a dry run**:
   ```bash
   python manage.py update_number_results --dry-run
   ```

3. **Run the command to update records with missing number_result**:
   ```bash
   python manage.py update_number_results
   ```

   OR to recalculate all number_results:
   ```bash
   python manage.py update_number_results --all
   ```

4. **For large databases, adjust batch size and sleep time**:
   ```bash
   # Process in batches of 50 with 0.2 seconds sleep between batches
   python manage.py update_number_results --batch-size=50 --sleep=0.2
   ```

### Production Considerations

1. **Run with minimal load**: Schedule the update during off-peak hours to minimize impact on users.

2. **Resource monitoring**: Monitor server resources during the update, especially if you have a large database.

3. **Transaction safety**: The command uses transactions to ensure database consistency if an error occurs during processing.

4. **Resume capability**: If interrupted, you can safely run the command again - it will only process records that still need updating (unless using `--all`).

5. **Command timeout**: For very large databases, consider using a tool like `screen` or `tmux` to prevent SSH timeout issues:
   ```bash
   # Start a screen session
   screen
   
   # Run the command
   python manage.py update_number_results
   
   # Detach from screen with Ctrl+A, D
   # Reattach later with:
   screen -r
   ```

For deployment in production with a web server like Gunicorn or uWSGI, make sure to activate your virtual environment before running the command:

```bash
source /path/to/your/venv/bin/activate
cd /path/to/your/project
python manage.py update_number_results
```

The command includes progress reporting and error handling, so you'll see real-time updates as it processes your database records.
"""