"""
Management command to rotate existing transect geometries to vary their orientation.

This command updates existing transect events in the database, rotating each transect
geometry by a deterministic but varied amount based on the transect's ID.

Usage: python manage.py rotate_transects [--dry-run] [--rotation-range=60]
"""
import hashlib
from django.core.management.base import BaseCommand
from django.db import transaction
from shapely import wkt as shapely_wkt
from shapely import affinity
from eventhub.models import Event


def rotate_transect_geometry(wkt_str: str, identifier: str, rotation_range: float = 60.0) -> str:
    """
    Rotate a transect geometry by a varying angle based on an identifier.
    
    The rotation angle is deterministic based on the identifier, so the same transect
    will always get the same rotation.
    
    Args:
        wkt_str: WKT string of the geometry (should be in WGS84)
        identifier: Unique identifier for the transect (used to generate rotation angle)
        rotation_range: Total rotation range in degrees (default: 60, so -30 to +30 degrees)
        
    Returns:
        WKT string for the rotated geometry
    """
    try:
        geom = shapely_wkt.loads(wkt_str)
    except Exception as e:
        raise ValueError(f"Invalid WKT: {e}") from e
    
    # Generate a deterministic rotation angle based on identifier
    # Use MD5 hash to get a pseudo-random but consistent value
    hash_bytes = hashlib.md5(identifier.encode()).digest()
    # Convert first 4 bytes to an integer
    hash_int = int.from_bytes(hash_bytes[:4], byteorder='big')
    # Convert to angle between -rotation_range/2 and +rotation_range/2
    rotation_angle = (hash_int % 360) * (rotation_range / 360.0) - (rotation_range / 2.0)
    
    # Rotate around the centroid of the geometry
    centroid = geom.centroid
    rotated_geom = affinity.rotate(geom, rotation_angle, origin=centroid)
    
    return rotated_geom.wkt


class Command(BaseCommand):
    help = 'Rotate existing transect geometries to vary their orientation'

    def add_arguments(self, parser):
        parser.add_argument(
            '--dry-run',
            action='store_true',
            help='Show what would be rotated without actually updating the database',
        )
        parser.add_argument(
            '--rotation-range',
            type=float,
            default=60.0,
            help='Total rotation range in degrees (default: 60, so -30 to +30 degrees)',
        )

    def handle(self, *args, **options):
        dry_run = options['dry_run']
        rotation_range = options['rotation_range']
        
        # Get all transect events that have a geometry
        transects = Event.objects.filter(
            event_type='transect',
            footprintWKT__isnull=False
        ).exclude(footprintWKT='')
        
        total_count = transects.count()
        
        if total_count == 0:
            self.stdout.write(self.style.WARNING('No transect events with geometries found.'))
            return
        
        self.stdout.write(f'Found {total_count} transect event(s) with geometries.')
        
        if dry_run:
            self.stdout.write(self.style.WARNING('\nDRY RUN MODE - No changes will be saved\n'))
        
        updated_count = 0
        error_count = 0
        
        # Use transaction only for actual updates, not for dry-run
        if dry_run:
            for transect in transects:
                try:
                    # Use transect ID as identifier for deterministic rotation
                    identifier = f"transect_{transect.id}"
                    rotated_wkt = rotate_transect_geometry(
                        transect.footprintWKT,
                        identifier,
                        rotation_range
                    )
                    self.stdout.write(
                        f'Would rotate transect {transect.id} ({transect.name})'
                    )
                    updated_count += 1
                except Exception as e:
                    error_count += 1
                    self.stdout.write(
                        self.style.ERROR(
                            f'Error rotating transect {transect.id} ({transect.name}): {e}'
                        )
                    )
        else:
            with transaction.atomic():
                for transect in transects:
                    try:
                        # Use transect ID as identifier for deterministic rotation
                        identifier = f"transect_{transect.id}"
                        rotated_wkt = rotate_transect_geometry(
                            transect.footprintWKT,
                            identifier,
                            rotation_range
                        )
                        transect.footprintWKT = rotated_wkt
                        transect.save(update_fields=['footprintWKT'])
                        updated_count += 1
                    except Exception as e:
                        error_count += 1
                        self.stdout.write(
                            self.style.ERROR(
                                f'Error rotating transect {transect.id} ({transect.name}): {e}'
                            )
                        )
        
        # Summary
        self.stdout.write('')
        self.stdout.write('=' * 50)
        if dry_run:
            self.stdout.write(
                self.style.SUCCESS(
                    f'DRY RUN: Would update {updated_count} transect(s)'
                )
            )
        else:
            self.stdout.write(
                self.style.SUCCESS(
                    f'Successfully updated {updated_count} transect(s)'
                )
            )
        if error_count > 0:
            self.stdout.write(
                self.style.WARNING(f'Errors occurred for {error_count} transect(s)')
            )
        self.stdout.write('=' * 50)

