"""
Enterprise-Ready Common Models Module

This module provides base model classes, abstract models, and common functionality
that can be inherited by other models throughout the application. It follows
Django best practices and enterprise patterns for scalability and maintainability.

Features:
- Base timestamp models with created/updated tracking
- Soft delete functionality with audit trails
- UUID primary keys for enhanced security
- Versioning and change tracking
- Common field types and validators
- Audit logging capabilities
- Status and workflow management
- Metadata and tagging support

Author: Adtals Development Team
Version: 2.0.0
License: Proprietary
"""

import uuid 

from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.validators import MinLengthValidator, RegexValidator

from apps.common.managers import BaseManager, SoftDeleteManager


class TimestampedModel(models.Model):
    """
    Abstract base model providing timestamp fields.
    
    This model provides automatic tracking of creation and modification
    timestamps for all models that inherit from it.
    
    Attributes:
        created_at (DateTimeField): Timestamp when object was created
        updated_at (DateTimeField): Timestamp when object was last modified
    """
    
    # Timestamp fields for tracking creation 
    created_at = models.DateTimeField(
        _('Created At'),
        auto_now_add=True,
        help_text=_('Timestamp when this object was created')
    )
    # Timestamp fields for tracking modification
    updated_at = models.DateTimeField(
        _('Updated At'),
        auto_now=True,
        help_text=_('Timestamp when this object was last modified')
    )
    
    class Meta:
        abstract = True
        ordering = ['-created_at']
    
    def save(self, *args, **kwargs):
        """Override save to ensure updated_at is always set."""
        if not self.pk:
            self.created_at = timezone.now()
        self.updated_at = timezone.now()
        super().save(*args, **kwargs)


class UUIDModel(models.Model):
    """
    Abstract base model using UUID as primary key.
    
    This model provides UUID primary keys for enhanced security
    and to prevent enumeration attacks.
    
    Attributes:
        id (UUIDField): UUID primary key
    """
    
    # Primary key using UUID for better security and uniqueness
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        help_text=_('Unique identifier for this object')
    )
    
    class Meta:
        abstract = True


class SoftDeleteModel(models.Model):
    """
    Abstract base model providing soft delete functionality.
    
    This model allows objects to be marked as deleted without
    actually removing them from the database, enabling recovery
    and audit trail maintenance.
    
    Attributes:
        is_deleted (BooleanField): Whether object is soft-deleted
        deleted_at (DateTimeField): When object was soft-deleted
        deleted_by (ForeignKey): User who soft-deleted the object
    """
    
    # Soft delete functionality
    is_deleted = models.BooleanField(
        _('Is Deleted'),
        default=False,
        help_text=_('Whether this object has been soft-deleted')
    ) 

    # Timestamp fields for tracking deletion
    deleted_at = models.DateTimeField(
        _('Deleted At'),
        null=True,
        blank=True,
        help_text=_('Timestamp when this object was soft-deleted')
    )

    # User who soft-deleted the object 
    deleted_by = models.ForeignKey(
        "accounts.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='%(class)s_deleted',
        verbose_name=_('Deleted By'),
        help_text=_('User who soft-deleted this object')
    )
    
    objects = SoftDeleteManager()
    all_objects = BaseManager()
    
    class Meta:
        abstract = True
    
    def delete(self, user=None, *args, **kwargs): 
        """
        Perform soft delete instead of hard delete.
        
        Args:
            user: The user performing the delete operation
        """
        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 restore(self, user=None): 
        """
        Restore a soft deleted object.
        
        Args:
            user: The user performing the restore operation
        """ 
        self.is_deleted = False
        self.deleted_at = None
        self.deleted_by = None
        if user:
            self.updated_by = user
        self.save()
    
    def hard_delete(self):
        """Permanently delete the object from database."""
        super().delete()

    @classmethod
    def get_active_objects(cls):
        """
        Get a queryset of non-deleted objects.
        
        Returns:
            QuerySet: All objects where is_deleted=False
        """
        return cls.objects.filter(is_deleted=False)
    
    @classmethod
    def get_deleted_objects(cls):
        """
        Get a queryset of soft deleted objects.
        
        Returns:
            QuerySet: All objects where is_deleted=True
        """
        return cls.objects.filter(is_deleted=True)


class SlugModel(models.Model):
    """
    Abstract model that provides slug functionality.
    
    This model adds a slug field that can be used for SEO-friendly URLs.
    The slug is automatically generated from a specified field.
    """
    
    # Slug field for SEO-friendly URLs
    slug = models.SlugField(
        max_length=255,
        unique=True,
        verbose_name=_("Slug"),
        help_text=_("SEO-friendly URL identifier")
    )

    class Meta:
        abstract = True  # This makes it an abstract base class
    
    def save(self, *args, **kwargs):
        """
        Override save method to auto-generate slug if not provided.
        
        Child models should define a get_slug_source() method
        that returns the field value to generate the slug from.
        """
        if not self.slug and hasattr(self, 'get_slug_source'):
            from django.utils.text import slugify
            self.slug = slugify(self.get_slug_source())
        
        super().save(*args, **kwargs)
    
    def get_slug_source(self):
        """
        Get the source value for slug generation.
        
        This method should be overridden in child models
        to specify which field should be used for slug generation.
        
        Returns:
            str: The value to generate slug from
        """
        return str(self.pk)


class SortableModel(models.Model):
    """
    Abstract model that provides sorting functionality.
    
    This model adds an order field that can be used to sort records
    in a specific order defined by the user.
    """
    
    # Order field for custom sorting
    order = models.PositiveIntegerField(
        default=0,
        verbose_name=_("Order"),
        help_text=_("Order for sorting (lower numbers appear first)")
    )

    class Meta:
        abstract = True  # This makes it an abstract base class
        ordering = ['order', 'id']  # Default ordering by order field
    
    def move_up(self):
        """
        Move this item up in the sort order.
        
        Swaps the order with the item that has the next lower order value.
        """
        try:
            previous_item = self.__class__.objects.filter(
                order__lt=self.order
            ).order_by('-order').first()
            
            if previous_item:
                # Swap order values
                previous_order = previous_item.order
                previous_item.order = self.order
                self.order = previous_order
                
                # Save both items
                previous_item.save(update_fields=['order'])
                self.save(update_fields=['order'])
        except Exception:
            pass  # Silently fail if something goes wrong
    
    def move_down(self):
        """
        Move this item down in the sort order.
        
        Swaps the order with the item that has the next higher order value.
        """
        try:
            next_item = self.__class__.objects.filter(
                order__gt=self.order
            ).order_by('order').first()
            
            if next_item:
                # Swap order values
                next_order = next_item.order
                next_item.order = self.order
                self.order = next_order
                
                # Save both items
                next_item.save(update_fields=['order'])
                self.save(update_fields=['order'])
        except Exception:
            pass  # Silently fail if something goes wrong


class AuditModel(models.Model):
    """
    Abstract base model for tracking user actions.
    
    This model tracks which user created and last modified an object,
    providing audit trail capabilities.
    
    Attributes:
        created_by (ForeignKey): User who created this object
        updated_by (ForeignKey): User who last modified this object
    """
    
    created_by = models.ForeignKey(
        "accounts.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='%(class)s_created',
        verbose_name=_('Created By'),
        help_text=_('User who created this object')
    )
    updated_by = models.ForeignKey(
        "accounts.User",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='%(class)s_updated',
        verbose_name=_('Updated By'),
        help_text=_('User who last modified this object')
    )
    
    class Meta:
        abstract = True

    def save(self, *args, **kwargs):
        """
        Override save method to handle user tracking.
        
        This method automatically sets created_by and updated_by
        based on the current user in the request context.
        """
        # Get the current user from the request context if available
        user = getattr(self, '_current_user', None)
        
        # Set created_by for new records
        if not self.pk and user:
            self.created_by = user
        
        # Always set updated_by for any save operation
        if user:
            self.updated_by = user
        
        super().save(*args, **kwargs)


class StatusModel(models.Model):
    """
    Abstract base model providing status tracking.
    
    This model provides common status fields that can be used
    across different models for workflow management.
    
    Attributes:
        is_active (BooleanField): Whether object is active
        is_published (BooleanField): Whether object is published
        status (CharField): Current status of the object
    """
    
    STATUS_CHOICES = [
        ('draft', _('Draft')),
        ('pending', _('Pending')),
        ('approved', _('Approved')),
        ('rejected', _('Rejected')),
        ('published', _('Published')),
        ('archived', _('Archived')),
    ]
    
    is_active = models.BooleanField(
        _('Is Active'),
        default=True,
        help_text=_('Whether this object is active')
    )
    is_published = models.BooleanField(
        _('Is Published'),
        default=False,
        help_text=_('Whether this object is published')
    )
    status = models.CharField(
        _('Status'),
        max_length=20,
        choices=STATUS_CHOICES,
        default='draft',
        help_text=_('Current status of this object')
    )
    
    class Meta:
        abstract = True
        
    def activate(self):
        """Activate this record.""" 
        self.is_active = True
        self.save(update_fields=['is_active'])
    
    def deactivate(self):
        """Deactivate this record.""" 
        self.is_active = False
        self.save(update_fields=['is_active'])

    def suspend(self):
        """Suspend this record."""
        self.status = 'suspended'
        self.is_active = False
        self.save(update_fields=['status', 'is_active'])
    
    def archive(self):
        """Archive this record."""
        self.status = 'archived'
        self.is_active = False
        self.save(update_fields=['status', 'is_active'])


class VersionedModel(models.Model):
    """
    Abstract base model providing versioning functionality.
    
    This model tracks version numbers and changes for objects,
    enabling version control and change history.
    
    Attributes:
        version (PositiveIntegerField): Current version number
        change_reason (TextField): Reason for the last change
    """
    
    version = models.PositiveIntegerField(
        _('Version'),
        default=1,
        help_text=_('Current version number of this object')
    )
    change_reason = models.TextField(
        _('Change Reason'),
        blank=True,
        help_text=_('Reason for the last change made to this object')
    )
    
    class Meta:
        abstract = True
    
    def save(self, *args, **kwargs):
        """Increment version on save."""
        if self.pk:
            self.version += 1
        super().save(*args, **kwargs)


class MetadataModel(models.Model):
    """
    Abstract base model providing metadata functionality.
    
    This model provides flexible metadata storage using JSON fields
    for storing additional information that doesn't warrant separate fields.
    
    Attributes:
        metadata (JSONField): Flexible metadata storage
        tags (CharField): Comma-separated tags
    """
    
    metadata = models.JSONField(
        _('Metadata'),
        default=dict,
        blank=True,
        help_text=_('Additional metadata for this object')
    )
    tags = models.CharField(
        _('Tags'),
        max_length=500,
        blank=True,
        help_text=_('Comma-separated tags for categorization')
    )
    
    class Meta:
        abstract = True
    
    def get_tags_list(self):
        """Return tags as a list."""
        if self.tags:
            return [tag.strip() for tag in self.tags.split(',') if tag.strip()]
        return []
    
    def add_tag(self, tag):
        """Add a tag to the object."""
        tags = self.get_tags_list()
        if tag not in tags:
            tags.append(tag)
            self.tags = ', '.join(tags)
    
    def remove_tag(self, tag):
        """Remove a tag from the object."""
        tags = self.get_tags_list()
        if tag in tags:
            tags.remove(tag)
            self.tags = ', '.join(tags)

    def get_metadata(self, key, default=None):
        """Get a metadata value."""
        return self.metadata.get(key, default)
    
    def set_metadata(self, key, value):
        """Set a metadata value."""
        self.metadata[key] = value
        self.save(update_fields=['metadata'])


class AddressModel(models.Model):
    """
    Abstract model for address information.
    
    This model provides common address fields that can be
    used by various models that need location information.
    
    Attributes:
        street_address (CharField): Street address
        city (CharField): City name
        state_province (CharField): State or province
        postal_code (CharField): Postal/ZIP code
        country (CharField): Country name
        latitude (DecimalField): Latitude coordinate
        longitude (DecimalField): Longitude coordinate
    """
    
    # Address fields
    street_address = models.CharField(
        _('street address'),
        max_length=255,
        blank=True,
        help_text=_('Street address including number and street name')
    )
    
    city = models.CharField(
        _('city'),
        max_length=100,
        blank=True,
        help_text=_('City name')
    )
    
    state_province = models.CharField(
        _('state/province'),
        max_length=100,
        blank=True,
        help_text=_('State or province name')
    )
    
    postal_code = models.CharField(
        _('postal code'),
        max_length=20,
        blank=True,
        help_text=_('Postal or ZIP code')
    )
    
    country = models.CharField(
        _('country'),
        max_length=100,
        blank=True,
        help_text=_('Country name')
    )
    
    # Geographic coordinates
    latitude = models.DecimalField(
        _('latitude'),
        max_digits=10,
        decimal_places=8,
        null=True,
        blank=True,
        help_text=_('Latitude coordinate')
    )
    
    longitude = models.DecimalField(
        _('longitude'),
        max_digits=11,
        decimal_places=8,
        null=True,
        blank=True,
        help_text=_('Longitude coordinate')
    )
    
    class Meta:
        """Meta configuration for the AddressModel."""
        abstract = True
    
    def get_full_address(self):
        """
        Get the complete address as a formatted string.
        
        Returns:
            str: Formatted address string
        """
        address_parts = [
            self.street_address,
            self.city,
            self.state_province,
            self.postal_code,
            self.country
        ]
        return ', '.join(filter(None, address_parts))
    
    def has_coordinates(self):
        """
        Check if the address has geographic coordinates.
        
        Returns:
            bool: True if both latitude and longitude are set
        """
        return self.latitude is not None and self.longitude is not None


class BaseModel(UUIDModel, TimestampedModel, AuditModel, SoftDeleteModel):
    """
    Comprehensive base model combining all common functionality.
    
    This model combines UUID primary keys, timestamps, user tracking,
    soft delete, and status management into a single base class that
    provides enterprise-level functionality for all models.

    This model includes:    
    - UUID :: UUID primary key for better security
    - Timestamps :: Created and updated timestamps
    - Soft delete :: Soft delete functionality
    - Audit trail :: Created and updated by user tracking 
    - Common methods for all models 
    """
    
    class Meta:
        abstract = True  # This makes it an abstract base class s

    def __str__(self):
        """Default string representation using the model name and ID."""
        return f"{self.__class__.__name__} ({self.id})"
    
    def get_absolute_url(self):
        """
        Get the absolute URL for this model instance.
        
        This method should be overridden in child models to provide
        the correct URL for the model's detail view.
        """
        from django.urls import reverse
        model_name = self.__class__.__name__.lower()
        try:
            return reverse(f'{model_name}-detail', kwargs={'pk': self.pk})
        except:
            return '#'  # Fallback if URL pattern doesn't exist


class FullBaseModel(BaseModel, SlugModel, StatusModel, SortableModel, MetadataModel):
    """Complete base model with all functionality.
    
    This model combines all available base functionality:
    - UUID primary key
    - Timestamps
    - Soft delete
    - Audit trail
    - Status management
    - Metadata storage
    
    Use this for models that need comprehensive functionality.
    """
    
    class Meta:
        abstract = True

