from django.db import models, transaction
from django.core.exceptions import ValidationError


class FormattedAutoField(models.CharField):
    """
    A Django CharField that automatically generates formatted IDs using a sequence counter.

    This field generates unique formatted identifiers like "INV-2024-00001" or "PO-12345"
    using a database-backed sequence that is safe for concurrent access.

    Args:
        format_string (str): Python format string with placeholders. Must include {seq} for
            the sequence number. Example: "INV-{year}-{seq:05d}"
        placeholders (dict, optional): Dictionary mapping placeholder names to values or
            callables. Callables are invoked at generation time. Example:
            {"year": lambda: datetime.now().year}
        start_at (int, optional): The first number to use for this sequence. Default: 1
        max_length (int, optional): Maximum length of the formatted string. Default: 100

    Example:
        class PurchaseOrder(models.Model):
            order_number = FormattedAutoField(
                format_string="PO-{year}-{seq:05d}",
                placeholders={
                    "year": lambda: datetime.now().year
                },
                primary_key=True
            )

    Notes:
        - The field is automatically set to blank=True and editable=False
        - Values are only generated on INSERT, not UPDATE
        - Manual values can be set before save() to override auto-generation
        - Thread-safe and process-safe (uses database row-level locking)
        - Requires database support for SELECT FOR UPDATE (PostgreSQL, MySQL, Oracle)
    """

    def __init__(self, format_string="{seq}", placeholders=None, start_at=1, *args, **kwargs):
        """
        Initialize the FormattedAutoField.

        Args:
            format_string: Python format string (must include {seq})
            placeholders: Dict of placeholder names to values/callables
            start_at: Starting value for the sequence (default: 1)
        """
        # Check if 'seq' is a field in the format string
        import string
        field_names = [fname for _, fname, _, _ in string.Formatter().parse(format_string) if fname]
        if 'seq' not in field_names:
            raise ValueError(f"format_string must contain {{seq}} placeholder. Got: {format_string}")

        self.format_string = format_string
        self.placeholders = placeholders or {}
        self.start_at = start_at

        # Set CharField-specific defaults
        kwargs.setdefault('max_length', 100)
        kwargs['blank'] = True  # Allow blank since value is auto-generated
        kwargs['editable'] = False  # Don't show in forms/admin

        super().__init__(*args, **kwargs)

    def deconstruct(self):
        """
        Return the field definition for migrations.

        This is required for Django migrations to properly recreate the field.
        Note: Callable placeholders (lambdas) are not serialized in migrations
        as they cannot be serialized. They must be redefined in the model.
        """
        name, path, args, kwargs = super().deconstruct()

        # Add our custom parameters
        kwargs['format_string'] = self.format_string

        # Only include non-callable placeholders in migrations
        # Django cannot serialize lambda functions
        if self.placeholders:
            serializable_placeholders = {
                k: v for k, v in self.placeholders.items()
                if not callable(v)
            }
            if serializable_placeholders:
                kwargs['placeholders'] = serializable_placeholders

        if self.start_at != 1:
            kwargs['start_at'] = self.start_at

        # Remove auto-set kwargs that are always the same
        kwargs.pop('blank', None)
        kwargs.pop('editable', None)

        return name, path, args, kwargs

    def pre_save(self, model_instance, add):
        """
        Generate the formatted value before saving.

        This method is called by Django just before saving a model instance.
        It only generates a value on INSERT (add=True), not on UPDATE.

        Args:
            model_instance: The model instance being saved
            add: True if this is a new record (INSERT), False if updating

        Returns:
            The formatted value to be saved in this field
        """
        # Check if value already exists (manual override)
        existing_value = getattr(model_instance, self.attname)
        if existing_value:
            # Value already set, don't override
            return existing_value

        if add:
            # Generate new value only for new records
            value = self._generate_next_value(model_instance)
            setattr(model_instance, self.attname, value)
            return value
        else:
            # For updates, return existing value (should not be empty at this point)
            return existing_value

    def _generate_next_value(self, model_instance):
        """
        Generate the next formatted value using the sequence.

        This method implements the core logic:
        1. Build a unique key for this field
        2. Atomically fetch and lock the sequence record
        3. Increment the counter
        4. Format the value with placeholders
        5. Return the formatted string

        Args:
            model_instance: The model instance being saved

        Returns:
            str: The formatted value
        """
        from .models import Sequence

        # Build unique key: app_label.model_name.field_name
        meta = model_instance._meta
        sequence_key = f"{meta.app_label}.{meta.model_name}.{self.name}"

        # Get the database alias for this model
        db_alias = model_instance._state.db or 'default'

        # Use atomic transaction with row-level locking for concurrency safety
        with transaction.atomic(using=db_alias):
            # Get or create the sequence record with lock
            sequence, created = Sequence.objects.select_for_update().get_or_create(
                key=sequence_key,
                defaults={'last_value': self.start_at - 1}
            )

            # If the sequence already existed but we have a custom start_at,
            # the existing sequence will continue from its current value
            # (this is intentional to avoid duplicates)

            # Get the next value atomically
            next_value = sequence.get_next_value()

        # Resolve placeholders (evaluate callables)
        resolved_placeholders = {}
        for key, value in self.placeholders.items():
            if callable(value):
                resolved_placeholders[key] = value()
            else:
                resolved_placeholders[key] = value

        # Add the sequence number to placeholders
        resolved_placeholders['seq'] = next_value

        # Format the final value
        try:
            formatted_value = self.format_string.format(**resolved_placeholders)
        except KeyError as e:
            raise ValidationError(
                f"Missing placeholder in format_string: {e}. "
                f"Available placeholders: {list(resolved_placeholders.keys())}"
            )

        return formatted_value

    def get_internal_type(self):
        """Return the database column type."""
        return "CharField"
