import uuid
from datetime import datetime 

from app.config.extensions import db 


class UniqueIDMixin(db.DynamicDocument):
    """
    A mixin class that provides a unique identifier for the document.

    Base class for MongoDB documents with an automatically generated unique public ID.

    Attributes:
        public_id (str): A unique identifier for the document.

    Methods:
        generate_public_id(): Generates a random UUID and assigns it to the public_id field.
        to_dict(): Converts the document to a dictionary for serialization.

    """

    meta = {
        'allow_inheritance': True, 
        'abstract': True,
        'indexes': ['public_id'],
    }
    
    public_id = db.StringField(
        default=str(uuid.uuid4()),
        unique=True, 
        required=True,
        help_text="Unique identifier for the document."
    ) 

    def generate_public_id(self):
        """
        Generates a random UUID and assigns it to the public_id field.

        """
        self.public_id = str(uuid.uuid4())

    def __init__(self, *args, **kwargs):
        """
        Initialize a new ExtendedModel instance.

        Args:
            *args: Additional positional arguments.
            **kwargs: Additional keyword arguments.

        """
        super(UniqueIDMixin, self).__init__(*args, **kwargs)
        if not self.public_id:
            self.generate_public_id()


    def to_dict(self):
        """
        Converts the document to a dictionary for serialization.

        Returns:
            dict: A dictionary representation of the document.

        """
        document_dict = self.to_mongo().to_dict()
        document_dict['id'] = str(self.public_id)
        # Remove any '_id' field
        document_dict.pop('_id', None)
        # Remove any '_cls' field
        document_dict.pop('_cls', None)
        return document_dict
    

class TimestampedMixin(UniqueIDMixin):
    """
    A mixin class that adds timestamps for document creation and update.
    """
    
    deleted = db.BooleanField(
        default=False,
        help_text="Indicates if the document is deleted."
    )
    
    created_at = db.DateTimeField(
        default=datetime.now,
        help_text="Timestamp for document creation."
    )
    updated_at = db.DateTimeField(
        help_text="Timestamp for the last document update."
    )
    deleted_at = db.DateTimeField(
        default=None,
        help_text="Timestamp for document deletion."
    )
    
    def save(self, *args, **kwargs):
        """
        Saves the document.
        """ 
        super().save(*args, **kwargs)

    def update(self):
        """
        Updates the document and the document's `updated_at` field with the current timestamp.
        """
        self.updated_at = datetime.now()
        self.save() 

    def archive(self):
        """
        Soft delete the document by setting the 'deleted' flag and timestamp. 
        Archives the document by setting the `deleted` field to True and updating the `deleted_at` field with the current timestamp.
        """
        self.deleted = True
        self.deleted_at = datetime.now()
        self.save()

    def restore(self):
        """
        Restore a soft-deleted document.
        Restores the document by setting the `deleted` field to False and clearing the `deleted_at` field.
        """
        self.deleted = False
        self.deleted_at = None
        self.save()
        
    def to_dict(self):
        """
        Convert the document to a dictionary.
        """ 
        document_dict = super(TimestampedMixin, self).to_dict()  
        # Convert datetime objects to ISO 8601 strings
        document_dict['created_at'] = self._format_datetime(document_dict.get('created_at'))
        document_dict['updated_at'] = self._format_datetime(document_dict.get('updated_at'))
        document_dict['deleted_at'] = self._format_datetime(document_dict.get('deleted_at'))
        # Remove any '_cls' field
        document_dict.pop('_cls', None)

        return document_dict
        
    def _format_datetime(self, dt):
        """
        Helper method to format a datetime object to ISO 8601 string.
        """
        if dt and isinstance(dt, datetime):
            return dt.isoformat()
        return None
    
    @classmethod
    def get_paginated(cls, page, per_page):
        """
        Get a paginated list of documents.
        """
        page = max(1, page)
        per_page = min(max(1, per_page), 100)  # Limit per_page to a reasonable value
        total = cls.objects(deleted=False).count()
        documents = cls.objects(deleted=False).skip((page - 1) * per_page).limit(per_page)
        return {
            'total': total,
            'page': page,
            'per_page': per_page,
            'items': [doc.to_dict() for doc in documents]
        }

    meta = {
        'abstract': True,
        'ordering': ['-created_at', '-updated_at']
    }


class TaggedMixin(TimestampedMixin):
    """
    Base class for MongoDB documents with tags.

    Attributes:
        tags (list): A list of tags associated with the document.

    Methods:
        add_tag(tag): Adds a tag to the document if it doesn't already exist.
        remove_tag(tag): Removes a tag from the document if it exists.
        to_dict(): Converts the document to a dictionary for serialization.

    """

    tags = db.ListField(db.StringField())

    def add_tag(self, tag):
        """
        Adds a tag to the document if it doesn't already exist.

        Args:
            tag (str): The tag to add.

        """
        if tag not in self.tags:
            self.tags.append(tag)

    def remove_tag(self, tag):
        """
        Removes a tag from the document if it exists.

        Args:
            tag (str): The tag to remove.

        """
        if tag in self.tags:
            self.tags.remove(tag)

    def to_dict(self):
        """
        Converts the document to a dictionary for serialization.

        Returns:
            dict: A dictionary representation of the document.

        """
        document_dict = super(TaggedMixin, self).to_dict()  
        # Remove any '_cls' field
        document_dict.pop('_cls', None)
        return document_dict