"""
Django management command to synchronize visited regions and cities based on user locations.

This command processes all users' visited locations and marks their regions and cities as visited.
It's designed to be run periodically (e.g., nightly cron job) to keep visited regions/cities up to date.

Usage:
    python manage.py sync_visited_regions
    python manage.py sync_visited_regions --dry-run
    python manage.py sync_visited_regions --user-id 123
    python manage.py sync_visited_regions --batch-size 50
"""

from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth import get_user_model
from django.db import transaction
from django.db.models import Prefetch, Q
from adventures.models import Location
from worldtravel.models import Region, City, VisitedRegion, VisitedCity
from collections import defaultdict
import logging

logger = logging.getLogger(__name__)
User = get_user_model()


class Command(BaseCommand):
    help = 'Synchronize visited regions and cities based on user locations'

    def add_arguments(self, parser):
        parser.add_argument(
            '--dry-run',
            action='store_true',
            help='Show what would be created without making changes',
        )
        parser.add_argument(
            '--user-id',
            type=int,
            help='Sync visited regions for a specific user ID only',
        )
        parser.add_argument(
            '--batch-size',
            type=int,
            default=100,
            help='Number of users to process in each batch (default: 100)',
        )
        parser.add_argument(
            '--verbose',
            action='store_true',
            help='Show detailed output for each user',
        )

    def handle(self, *args, **options):
        dry_run = options['dry_run']
        user_id = options.get('user_id')
        batch_size = options['batch_size']
        verbose = options['verbose']

        if dry_run:
            self.stdout.write(
                self.style.WARNING('DRY RUN MODE - No changes will be made')
            )

        # Build user queryset
        users_queryset = User.objects.all()
        
        if user_id:
            users_queryset = users_queryset.filter(id=user_id)
            if not users_queryset.exists():
                raise CommandError(f'User with ID {user_id} not found')

        total_users = users_queryset.count()
        
        if total_users == 0:
            self.stdout.write(self.style.WARNING('No users found'))
            return

        self.stdout.write(f'Processing {total_users} user(s)...\n')

        # Track overall statistics
        total_new_regions = 0
        total_new_cities = 0
        users_processed = 0
        users_with_changes = 0

        # Process users in batches to manage memory
        user_ids = list(users_queryset.values_list('id', flat=True))
        
        for i in range(0, len(user_ids), batch_size):
            batch_user_ids = user_ids[i:i + batch_size]
            
            for user_id in batch_user_ids:
                try:
                    new_regions, new_cities = self._process_user(
                        user_id, dry_run, verbose
                    )
                    
                    total_new_regions += new_regions
                    total_new_cities += new_cities
                    users_processed += 1
                    
                    if new_regions > 0 or new_cities > 0:
                        users_with_changes += 1
                    
                    # Progress indicator for large batches
                    if users_processed % 50 == 0:
                        self.stdout.write(
                            f'Processed {users_processed}/{total_users} users...'
                        )
                        
                except Exception as e:
                    self.stdout.write(
                        self.style.ERROR(
                            f'Error processing user {user_id}: {str(e)}'
                        )
                    )
                    logger.exception(f'Error processing user {user_id}')

        # Summary
        self.stdout.write('\n' + '='*60)
        if dry_run:
            self.stdout.write(
                self.style.SUCCESS(
                    f'DRY RUN COMPLETE:\n'
                    f'  Users processed: {users_processed}\n'
                    f'  Users with changes: {users_with_changes}\n'
                    f'  Would create {total_new_regions} new visited regions\n'
                    f'  Would create {total_new_cities} new visited cities'
                )
            )
        else:
            self.stdout.write(
                self.style.SUCCESS(
                    f'SYNC COMPLETE:\n'
                    f'  Users processed: {users_processed}\n'
                    f'  Users with changes: {users_with_changes}\n'
                    f'  Created {total_new_regions} new visited regions\n'
                    f'  Created {total_new_cities} new visited cities'
                )
            )

    def _process_user(self, user_id, dry_run=False, verbose=False):
        """
        Process a single user and return counts of new regions and cities.
        Returns: (new_regions_count, new_cities_count)
        """
        # Get all visited locations with their region and city data in a single query
        visited_locations = Location.objects.filter(
            user_id=user_id
        ).select_related('region', 'city')
        
        # Collect unique regions and cities from visited locations
        regions_to_mark = set()
        cities_to_mark = set()
        
        for location in visited_locations:
            # Only process locations that are marked as visited
            if not location.is_visited_status():
                continue
            
            if location.region_id:
                regions_to_mark.add(location.region_id)
            
            if location.city_id:
                cities_to_mark.add(location.city_id)
        
        # Early exit if no regions or cities to mark
        if not regions_to_mark and not cities_to_mark:
            return 0, 0
        
        new_regions_count = 0
        new_cities_count = 0
        
        # Process regions
        if regions_to_mark:
            new_regions_count = self._sync_visited_regions(
                user_id, regions_to_mark, dry_run
            )
        
        # Process cities
        if cities_to_mark:
            new_cities_count = self._sync_visited_cities(
                user_id, cities_to_mark, dry_run
            )
        
        if verbose and (new_regions_count > 0 or new_cities_count > 0):
            self.stdout.write(
                f'User {user_id}: '
                f'{new_regions_count} new regions, '
                f'{new_cities_count} new cities'
            )
        
        return new_regions_count, new_cities_count

    def _sync_visited_regions(self, user_id, region_ids, dry_run=False):
        """Sync visited regions for a user. Returns count of new regions created."""
        # Get existing visited regions for this user in one query
        existing_visited_regions = set(
            VisitedRegion.objects.filter(
                user_id=user_id,
                region_id__in=region_ids
            ).values_list('region_id', flat=True)
        )
        
        # Determine which regions need to be created
        regions_to_create = region_ids - existing_visited_regions
        
        if not regions_to_create:
            return 0
        
        if dry_run:
            return len(regions_to_create)
        
        # Bulk create new VisitedRegion entries
        new_visited_regions = [
            VisitedRegion(region_id=region_id, user_id=user_id)
            for region_id in regions_to_create
        ]
        
        with transaction.atomic():
            VisitedRegion.objects.bulk_create(
                new_visited_regions,
                ignore_conflicts=True  # Handle race conditions gracefully
            )
        
        return len(regions_to_create)

    def _sync_visited_cities(self, user_id, city_ids, dry_run=False):
        """Sync visited cities for a user. Returns count of new cities created."""
        # Get existing visited cities for this user in one query
        existing_visited_cities = set(
            VisitedCity.objects.filter(
                user_id=user_id,
                city_id__in=city_ids
            ).values_list('city_id', flat=True)
        )
        
        # Determine which cities need to be created
        cities_to_create = city_ids - existing_visited_cities
        
        if not cities_to_create:
            return 0
        
        if dry_run:
            return len(cities_to_create)
        
        # Bulk create new VisitedCity entries
        new_visited_cities = [
            VisitedCity(city_id=city_id, user_id=user_id)
            for city_id in cities_to_create
        ]
        
        with transaction.atomic():
            VisitedCity.objects.bulk_create(
                new_visited_cities,
                ignore_conflicts=True  # Handle race conditions gracefully
            )
        
        return len(cities_to_create)
