"""
Core base models and utilities for ThrillWiki.
These abstract models provide common functionality for all entities.
"""

import uuid
from django.db import models
from model_utils.models import TimeStampedModel
from django_lifecycle import LifecycleModel, hook, AFTER_CREATE, AFTER_UPDATE
from dirtyfields import DirtyFieldsMixin


class BaseModel(LifecycleModel, TimeStampedModel):
    """
    Abstract base model for all entities.
    
    Provides:
    - UUID primary key
    - created_at and updated_at timestamps (from TimeStampedModel)
    - Lifecycle hooks for versioning
    """
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False
    )
    
    class Meta:
        abstract = True
        
    def __str__(self):
        return f"{self.__class__.__name__}({self.id})"


class VersionedModel(DirtyFieldsMixin, BaseModel):
    """
    Abstract base model for entities that need version tracking.
    
    Automatically creates a version record whenever the model is created or updated.
    Uses DirtyFieldsMixin to track which fields changed.
    """
    
    @hook(AFTER_CREATE)
    def create_version_on_create(self):
        """Create initial version when entity is created"""
        self._create_version('created')
    
    @hook(AFTER_UPDATE)
    def create_version_on_update(self):
        """Create version when entity is updated"""
        if self.get_dirty_fields():
            self._create_version('updated')
    
    def _create_version(self, change_type):
        """
        Create a version record for this entity.
        Deferred import to avoid circular dependencies.
        """
        try:
            from apps.versioning.services import VersionService
            VersionService.create_version(
                entity=self,
                change_type=change_type,
                changed_fields=self.get_dirty_fields() if change_type == 'updated' else {}
            )
        except ImportError:
            # Versioning app not yet available (e.g., during initial migrations)
            pass
    
    class Meta:
        abstract = True


# Location Models

class Country(BaseModel):
    """
    Country reference data (ISO 3166-1).
    
    Examples: United States, Canada, United Kingdom, etc.
    """
    name = models.CharField(max_length=255, unique=True)
    code = models.CharField(
        max_length=2,
        unique=True,
        help_text="ISO 3166-1 alpha-2 country code"
    )
    code3 = models.CharField(
        max_length=3,
        blank=True,
        help_text="ISO 3166-1 alpha-3 country code"
    )
    
    class Meta:
        db_table = 'countries'
        ordering = ['name']
        verbose_name_plural = 'countries'
    
    def __str__(self):
        return self.name


class Subdivision(BaseModel):
    """
    State/Province/Region reference data (ISO 3166-2).
    
    Examples: California, Ontario, England, etc.
    """
    country = models.ForeignKey(
        Country,
        on_delete=models.CASCADE,
        related_name='subdivisions'
    )
    name = models.CharField(max_length=255)
    code = models.CharField(
        max_length=10,
        help_text="ISO 3166-2 subdivision code (without country prefix)"
    )
    subdivision_type = models.CharField(
        max_length=50,
        blank=True,
        help_text="Type of subdivision (state, province, region, etc.)"
    )
    
    class Meta:
        db_table = 'subdivisions'
        ordering = ['country', 'name']
        unique_together = [['country', 'code']]
    
    def __str__(self):
        return f"{self.name}, {self.country.code}"


class Locality(BaseModel):
    """
    City/Town reference data.
    
    Examples: Los Angeles, Toronto, London, etc.
    """
    subdivision = models.ForeignKey(
        Subdivision,
        on_delete=models.CASCADE,
        related_name='localities'
    )
    name = models.CharField(max_length=255)
    latitude = models.DecimalField(
        max_digits=9,
        decimal_places=6,
        null=True,
        blank=True
    )
    longitude = models.DecimalField(
        max_digits=9,
        decimal_places=6,
        null=True,
        blank=True
    )
    
    class Meta:
        db_table = 'localities'
        ordering = ['subdivision', 'name']
        verbose_name_plural = 'localities'
        indexes = [
            models.Index(fields=['subdivision', 'name']),
        ]
    
    def __str__(self):
        return f"{self.name}, {self.subdivision.code}"
    
    @property
    def full_location(self):
        """Return full location string: City, State, Country"""
        return f"{self.name}, {self.subdivision.name}, {self.subdivision.country.name}"


# Date Precision Tracking

class DatePrecisionMixin(models.Model):
    """
    Mixin for models that need to track date precision.
    
    Allows tracking whether a date is known to year, month, or day precision.
    This is important for historical records where exact dates may not be known.
    """
    
    DATE_PRECISION_CHOICES = [
        ('year', 'Year'),
        ('month', 'Month'),
        ('day', 'Day'),
    ]
    
    class Meta:
        abstract = True
    
    @classmethod
    def add_date_precision_field(cls, field_name):
        """
        Helper to add a precision field for a date field.
        
        Usage in subclass:
            opening_date = models.DateField(null=True, blank=True)
            opening_date_precision = models.CharField(...)
        """
        return models.CharField(
            max_length=20,
            choices=cls.DATE_PRECISION_CHOICES,
            default='day',
            help_text=f"Precision level for {field_name}"
        )


# Soft Delete Mixin

class SoftDeleteMixin(models.Model):
    """
    Mixin for soft-deletable models.
    
    Instead of actually deleting records, mark them as deleted.
    This preserves data integrity and allows for undelete functionality.
    """
    is_deleted = models.BooleanField(default=False, db_index=True)
    deleted_at = models.DateTimeField(null=True, blank=True)
    deleted_by = models.ForeignKey(
        'users.User',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='%(class)s_deletions'
    )
    
    class Meta:
        abstract = True
    
    def soft_delete(self, user=None):
        """Mark this record as deleted"""
        from django.utils import timezone
        self.is_deleted = True
        self.deleted_at = timezone.now()
        if user:
            self.deleted_by = user
        self.save(update_fields=['is_deleted', 'deleted_at', 'deleted_by'])
    
    def undelete(self):
        """Restore a soft-deleted record"""
        self.is_deleted = False
        self.deleted_at = None
        self.deleted_by = None
        self.save(update_fields=['is_deleted', 'deleted_at', 'deleted_by'])


# Model Managers

class ActiveManager(models.Manager):
    """Manager that filters out soft-deleted records by default"""
    
    def get_queryset(self):
        return super().get_queryset().filter(is_deleted=False)


class AllObjectsManager(models.Manager):
    """Manager that includes all records, even soft-deleted ones"""
    
    def get_queryset(self):
        return super().get_queryset()
