"""
Versioning models for ThrillWiki.

This module provides automatic version tracking for all entities:
- EntityVersion: Generic version model using ContentType
- Full snapshot storage in JSON
- Changed fields tracking with old/new values
- Link to ContentSubmission when changes come from moderation
"""

import json
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.conf import settings

from apps.core.models import BaseModel


class EntityVersion(BaseModel):
    """
    Generic version tracking for all entities.
    
    Stores a complete snapshot of the entity state at the time of change,
    along with metadata about what changed and who made the change.
    """
    
    CHANGE_TYPE_CHOICES = [
        ('created', 'Created'),
        ('updated', 'Updated'),
        ('deleted', 'Deleted'),
        ('restored', 'Restored'),
    ]
    
    # Entity reference (generic)
    entity_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        related_name='entity_versions',
        help_text="Type of entity (Park, Ride, Company, etc.)"
    )
    entity_id = models.UUIDField(
        db_index=True,
        help_text="ID of the entity"
    )
    entity = GenericForeignKey('entity_type', 'entity_id')
    
    # Version info
    version_number = models.PositiveIntegerField(
        default=1,
        help_text="Sequential version number for this entity"
    )
    change_type = models.CharField(
        max_length=20,
        choices=CHANGE_TYPE_CHOICES,
        db_index=True,
        help_text="Type of change"
    )
    
    # Snapshot of entity state
    snapshot = models.JSONField(
        help_text="Complete snapshot of entity state as JSON"
    )
    
    # Changed fields tracking
    changed_fields = models.JSONField(
        default=dict,
        help_text="Dict of changed fields with old/new values: {'field': {'old': ..., 'new': ...}}"
    )
    
    # User who made the change
    changed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='entity_versions',
        help_text="User who made the change"
    )
    
    # Link to ContentSubmission (if change came from moderation)
    submission = models.ForeignKey(
        'moderation.ContentSubmission',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='versions',
        help_text="Submission that caused this version (if applicable)"
    )
    
    # Metadata
    comment = models.TextField(
        blank=True,
        help_text="Optional comment about this version"
    )
    ip_address = models.GenericIPAddressField(
        null=True,
        blank=True,
        help_text="IP address of change origin"
    )
    user_agent = models.CharField(
        max_length=500,
        blank=True,
        help_text="User agent string"
    )
    
    class Meta:
        verbose_name = 'Entity Version'
        verbose_name_plural = 'Entity Versions'
        ordering = ['-created']
        indexes = [
            models.Index(fields=['entity_type', 'entity_id', '-created']),
            models.Index(fields=['entity_type', 'entity_id', '-version_number']),
            models.Index(fields=['change_type']),
            models.Index(fields=['changed_by']),
            models.Index(fields=['submission']),
        ]
        unique_together = [['entity_type', 'entity_id', 'version_number']]
    
    def __str__(self):
        return f"{self.entity_type.model} v{self.version_number} ({self.change_type})"
    
    @property
    def entity_name(self):
        """Get display name of the entity."""
        try:
            entity = self.entity
            if entity:
                return str(entity)
        except:
            pass
        return f"{self.entity_type.model}:{self.entity_id}"
    
    def get_snapshot_dict(self):
        """
        Get snapshot as Python dict.
        
        Returns:
            dict: Entity snapshot
        """
        if isinstance(self.snapshot, str):
            return json.loads(self.snapshot)
        return self.snapshot
    
    def get_changed_fields_list(self):
        """
        Get list of changed field names.
        
        Returns:
            list: Field names that changed
        """
        return list(self.changed_fields.keys())
    
    def get_field_change(self, field_name):
        """
        Get old and new values for a specific field.
        
        Args:
            field_name: Name of the field
        
        Returns:
            dict: {'old': old_value, 'new': new_value} or None if field didn't change
        """
        return self.changed_fields.get(field_name)
    
    def compare_with(self, other_version):
        """
        Compare this version with another version.
        
        Args:
            other_version: EntityVersion to compare with
        
        Returns:
            dict: Comparison result with differences
        """
        if not other_version or self.entity_id != other_version.entity_id:
            return None
        
        this_snapshot = self.get_snapshot_dict()
        other_snapshot = other_version.get_snapshot_dict()
        
        differences = {}
        all_keys = set(this_snapshot.keys()) | set(other_snapshot.keys())
        
        for key in all_keys:
            this_val = this_snapshot.get(key)
            other_val = other_snapshot.get(key)
            
            if this_val != other_val:
                differences[key] = {
                    'this': this_val,
                    'other': other_val
                }
        
        return {
            'this_version': self.version_number,
            'other_version': other_version.version_number,
            'differences': differences,
            'changed_field_count': len(differences)
        }
    
    def get_diff_summary(self):
        """
        Get human-readable summary of changes in this version.
        
        Returns:
            str: Summary of changes
        """
        if self.change_type == 'created':
            return f"Created {self.entity_name}"
        
        if self.change_type == 'deleted':
            return f"Deleted {self.entity_name}"
        
        changed_count = len(self.changed_fields)
        if changed_count == 0:
            return f"No changes to {self.entity_name}"
        
        field_names = ', '.join(self.get_changed_fields_list()[:3])
        if changed_count > 3:
            field_names += f" and {changed_count - 3} more"
        
        return f"Updated {field_names}"
    
    @classmethod
    def get_latest_version_number(cls, entity_type, entity_id):
        """
        Get the latest version number for an entity.
        
        Args:
            entity_type: ContentType of entity
            entity_id: UUID of entity
        
        Returns:
            int: Latest version number (0 if no versions exist)
        """
        latest = cls.objects.filter(
            entity_type=entity_type,
            entity_id=entity_id
        ).aggregate(
            max_version=models.Max('version_number')
        )
        return latest['max_version'] or 0
    
    @classmethod
    def get_history(cls, entity_type, entity_id, limit=50):
        """
        Get version history for an entity.
        
        Args:
            entity_type: ContentType of entity
            entity_id: UUID of entity
            limit: Maximum number of versions to return
        
        Returns:
            QuerySet: Ordered list of versions (newest first)
        """
        return cls.objects.filter(
            entity_type=entity_type,
            entity_id=entity_id
        ).select_related(
            'changed_by',
            'submission',
            'submission__user'
        ).order_by('-version_number')[:limit]
    
    @classmethod
    def get_version_by_number(cls, entity_type, entity_id, version_number):
        """
        Get a specific version by number.
        
        Args:
            entity_type: ContentType of entity
            entity_id: UUID of entity
            version_number: Version number to retrieve
        
        Returns:
            EntityVersion or None
        """
        try:
            return cls.objects.get(
                entity_type=entity_type,
                entity_id=entity_id,
                version_number=version_number
            )
        except cls.DoesNotExist:
            return None
