"""
Versioning services for ThrillWiki.

This module provides the business logic for creating and managing entity versions:
- Creating versions automatically via lifecycle hooks
- Generating snapshots and tracking changed fields
- Linking versions to content submissions
- Retrieving version history and diffs
- Restoring previous versions
"""

import json
from decimal import Decimal
from datetime import date, datetime
from django.db import models, transaction
from django.contrib.contenttypes.models import ContentType
from django.core.serializers.json import DjangoJSONEncoder
from django.core.exceptions import ValidationError

from apps.versioning.models import EntityVersion


class VersionService:
    """
    Service class for versioning operations.
    
    All methods handle automatic version creation and tracking.
    """
    
    @staticmethod
    @transaction.atomic
    def create_version(
        entity,
        change_type='updated',
        changed_fields=None,
        user=None,
        submission=None,
        comment='',
        ip_address=None,
        user_agent=''
    ):
        """
        Create a version record for an entity.
        
        This is called automatically by the VersionedModel lifecycle hooks,
        but can also be called manually when needed.
        
        Args:
            entity: Entity instance (Park, Ride, Company, etc.)
            change_type: Type of change ('created', 'updated', 'deleted', 'restored')
            changed_fields: Dict of dirty fields from DirtyFieldsMixin
            user: User who made the change (optional)
            submission: ContentSubmission that caused this change (optional)
            comment: Optional comment about the change
            ip_address: IP address of the change origin
            user_agent: User agent string
        
        Returns:
            EntityVersion instance
        """
        # Get ContentType for entity
        entity_type = ContentType.objects.get_for_model(entity)
        
        # Get next version number
        version_number = EntityVersion.get_latest_version_number(
            entity_type, entity.id
        ) + 1
        
        # Create snapshot of current entity state
        snapshot = VersionService._create_snapshot(entity)
        
        # Build changed_fields dict with old/new values
        changed_fields_data = {}
        if changed_fields and change_type == 'updated':
            changed_fields_data = VersionService._build_changed_fields(
                entity, changed_fields
            )
        
        # Try to get user from submission if not provided
        if not user and submission:
            user = submission.user
        
        # Create version record
        version = EntityVersion.objects.create(
            entity_type=entity_type,
            entity_id=entity.id,
            version_number=version_number,
            change_type=change_type,
            snapshot=snapshot,
            changed_fields=changed_fields_data,
            changed_by=user,
            submission=submission,
            comment=comment,
            ip_address=ip_address,
            user_agent=user_agent
        )
        
        return version
    
    @staticmethod
    def _create_snapshot(entity):
        """
        Create a JSON snapshot of the entity's current state.
        
        Args:
            entity: Entity instance
        
        Returns:
            dict: Serializable snapshot of entity
        """
        snapshot = {}
        
        # Get all model fields
        for field in entity._meta.get_fields():
            # Skip reverse relations
            if field.is_relation and field.many_to_one is False and field.one_to_many is True:
                continue
            if field.is_relation and field.many_to_many is True:
                continue
            
            field_name = field.name
            
            try:
                value = getattr(entity, field_name)
                
                # Handle different field types
                if value is None:
                    snapshot[field_name] = None
                elif isinstance(value, (str, int, float, bool)):
                    snapshot[field_name] = value
                elif isinstance(value, Decimal):
                    snapshot[field_name] = float(value)
                elif isinstance(value, (date, datetime)):
                    snapshot[field_name] = value.isoformat()
                elif isinstance(value, models.Model):
                    # Store FK as ID
                    snapshot[field_name] = str(value.id) if value.id else None
                elif isinstance(value, dict):
                    # JSONField
                    snapshot[field_name] = value
                elif isinstance(value, list):
                    # JSONField array
                    snapshot[field_name] = value
                else:
                    # Try to serialize as string
                    snapshot[field_name] = str(value)
            except Exception:
                # Skip fields that can't be serialized
                continue
        
        return snapshot
    
    @staticmethod
    def _build_changed_fields(entity, dirty_fields):
        """
        Build a dict of changed fields with old and new values.
        
        Args:
            entity: Entity instance
            dirty_fields: Dict from DirtyFieldsMixin.get_dirty_fields()
        
        Returns:
            dict: Changed fields with old/new values
        """
        changed = {}
        
        for field_name, old_value in dirty_fields.items():
            try:
                new_value = getattr(entity, field_name)
                
                # Normalize values for JSON
                old_normalized = VersionService._normalize_value(old_value)
                new_normalized = VersionService._normalize_value(new_value)
                
                changed[field_name] = {
                    'old': old_normalized,
                    'new': new_normalized
                }
            except Exception:
                continue
        
        return changed
    
    @staticmethod
    def _normalize_value(value):
        """
        Normalize a value for JSON serialization.
        
        Args:
            value: Value to normalize
        
        Returns:
            Normalized value
        """
        if value is None:
            return None
        elif isinstance(value, (str, int, float, bool)):
            return value
        elif isinstance(value, Decimal):
            return float(value)
        elif isinstance(value, (date, datetime)):
            return value.isoformat()
        elif isinstance(value, models.Model):
            return str(value.id) if value.id else None
        elif isinstance(value, (dict, list)):
            return value
        else:
            return str(value)
    
    @staticmethod
    def get_version_history(entity, limit=50):
        """
        Get version history for an entity.
        
        Args:
            entity: Entity instance
            limit: Maximum number of versions to return
        
        Returns:
            QuerySet: Ordered list of versions (newest first)
        """
        entity_type = ContentType.objects.get_for_model(entity)
        return EntityVersion.get_history(entity_type, entity.id, limit)
    
    @staticmethod
    def get_version_by_number(entity, version_number):
        """
        Get a specific version by number.
        
        Args:
            entity: Entity instance
            version_number: Version number to retrieve
        
        Returns:
            EntityVersion or None
        """
        entity_type = ContentType.objects.get_for_model(entity)
        return EntityVersion.get_version_by_number(entity_type, entity.id, version_number)
    
    @staticmethod
    def get_latest_version(entity):
        """
        Get the latest version for an entity.
        
        Args:
            entity: Entity instance
        
        Returns:
            EntityVersion or None
        """
        entity_type = ContentType.objects.get_for_model(entity)
        return EntityVersion.objects.filter(
            entity_type=entity_type,
            entity_id=entity.id
        ).order_by('-version_number').first()
    
    @staticmethod
    def compare_versions(version1, version2):
        """
        Compare two versions of the same entity.
        
        Args:
            version1: First EntityVersion
            version2: Second EntityVersion
        
        Returns:
            dict: Comparison result with differences
        """
        if version1.entity_id != version2.entity_id:
            raise ValidationError("Versions must be for the same entity")
        
        return version1.compare_with(version2)
    
    @staticmethod
    def get_diff_with_current(version):
        """
        Compare a version with the current entity state.
        
        Args:
            version: EntityVersion to compare
        
        Returns:
            dict: Differences between version and current state
        """
        entity = version.entity
        if not entity:
            raise ValidationError("Entity no longer exists")
        
        current_snapshot = VersionService._create_snapshot(entity)
        version_snapshot = version.get_snapshot_dict()
        
        differences = {}
        all_keys = set(current_snapshot.keys()) | set(version_snapshot.keys())
        
        for key in all_keys:
            current_val = current_snapshot.get(key)
            version_val = version_snapshot.get(key)
            
            if current_val != version_val:
                differences[key] = {
                    'current': current_val,
                    'version': version_val
                }
        
        return {
            'version_number': version.version_number,
            'differences': differences,
            'changed_field_count': len(differences)
        }
    
    @staticmethod
    @transaction.atomic
    def restore_version(version, user=None, comment=''):
        """
        Restore an entity to a previous version.
        
        This creates a new version with change_type='restored'.
        
        Args:
            version: EntityVersion to restore
            user: User performing the restore
            comment: Optional comment about the restore
        
        Returns:
            EntityVersion: New version created by restore
        
        Raises:
            ValidationError: If entity doesn't exist
        """
        entity = version.entity
        if not entity:
            raise ValidationError("Entity no longer exists")
        
        # Get snapshot to restore
        snapshot = version.get_snapshot_dict()
        
        # Track which fields are changing
        changed_fields = {}
        
        # Apply snapshot values to entity
        for field_name, value in snapshot.items():
            # Skip metadata fields
            if field_name in ['id', 'created', 'modified']:
                continue
            
            try:
                # Get current value
                current_value = getattr(entity, field_name, None)
                current_normalized = VersionService._normalize_value(current_value)
                
                # Check if value is different
                if current_normalized != value:
                    changed_fields[field_name] = {
                        'old': current_normalized,
                        'new': value
                    }
                    
                    # Apply restored value
                    # Handle special field types
                    field = entity._meta.get_field(field_name)
                    
                    if isinstance(field, models.ForeignKey):
                        # FK fields need model instance
                        if value:
                            related_model = field.related_model
                            try:
                                related_obj = related_model.objects.get(id=value)
                                setattr(entity, field_name, related_obj)
                            except:
                                pass
                        else:
                            setattr(entity, field_name, None)
                    elif isinstance(field, models.DateField):
                        # Date fields
                        if value:
                            setattr(entity, field_name, datetime.fromisoformat(value).date())
                        else:
                            setattr(entity, field_name, None)
                    elif isinstance(field, models.DateTimeField):
                        # DateTime fields
                        if value:
                            setattr(entity, field_name, datetime.fromisoformat(value))
                        else:
                            setattr(entity, field_name, None)
                    elif isinstance(field, models.DecimalField):
                        # Decimal fields
                        if value is not None:
                            setattr(entity, field_name, Decimal(str(value)))
                        else:
                            setattr(entity, field_name, None)
                    else:
                        # Regular fields
                        setattr(entity, field_name, value)
            except Exception:
                # Skip fields that can't be restored
                continue
        
        # Save entity (this will trigger lifecycle hooks)
        # But we need to create the version manually to mark it as 'restored'
        entity.save()
        
        # Create restore version
        entity_type = ContentType.objects.get_for_model(entity)
        version_number = EntityVersion.get_latest_version_number(
            entity_type, entity.id
        ) + 1
        
        restored_version = EntityVersion.objects.create(
            entity_type=entity_type,
            entity_id=entity.id,
            version_number=version_number,
            change_type='restored',
            snapshot=VersionService._create_snapshot(entity),
            changed_fields=changed_fields,
            changed_by=user,
            comment=f"Restored from version {version.version_number}. {comment}".strip()
        )
        
        return restored_version
    
    @staticmethod
    def get_version_count(entity):
        """
        Get total number of versions for an entity.
        
        Args:
            entity: Entity instance
        
        Returns:
            int: Number of versions
        """
        entity_type = ContentType.objects.get_for_model(entity)
        return EntityVersion.objects.filter(
            entity_type=entity_type,
            entity_id=entity.id
        ).count()
    
    @staticmethod
    def get_versions_by_user(user, limit=50):
        """
        Get versions created by a specific user.
        
        Args:
            user: User instance
            limit: Maximum number of versions to return
        
        Returns:
            QuerySet: Versions by user (newest first)
        """
        return EntityVersion.objects.filter(
            changed_by=user
        ).select_related(
            'entity_type',
            'submission'
        ).order_by('-created')[:limit]
    
    @staticmethod
    def get_versions_by_submission(submission):
        """
        Get all versions created by a content submission.
        
        Args:
            submission: ContentSubmission instance
        
        Returns:
            QuerySet: Versions from submission
        """
        return EntityVersion.objects.filter(
            submission=submission
        ).select_related(
            'entity_type',
            'changed_by'
        ).order_by('-created')
