"""
Moderation Models

This module contains models for the ThrillWiki moderation system, including:
- EditSubmission: Original content submission and approval workflow
- ModerationReport: User reports for content moderation
- ModerationQueue: Workflow management for moderation tasks
- ModerationAction: Actions taken against users/content
- BulkOperation: Administrative bulk operations

All models use pghistory for change tracking and TrackedModel base class.

Callback System Integration:
All FSM-enabled models in this module support the callback system.
Callbacks for notifications, cache invalidation, and related updates
are registered via the callback configuration defined in each model's Meta class.
"""

from datetime import timedelta
from typing import Any

import pghistory
from django.conf import settings
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
from django.db import models
from django.utils import timezone

from apps.core.choices.fields import RichChoiceField
from apps.core.history import TrackedModel
from apps.core.state_machine import RichFSMField, StateMachineMixin

UserType = AbstractBaseUser | AnonymousUser


# Lazy callback imports to avoid circular dependencies
def _get_notification_callbacks():
    """Lazy import of notification callbacks."""
    from apps.core.state_machine.callbacks.notifications import (
        ModerationNotificationCallback,
        SubmissionApprovedNotification,
        SubmissionEscalatedNotification,
        SubmissionRejectedNotification,
    )

    return {
        "approved": SubmissionApprovedNotification,
        "rejected": SubmissionRejectedNotification,
        "escalated": SubmissionEscalatedNotification,
        "moderation": ModerationNotificationCallback,
    }


def _get_cache_callbacks():
    """Lazy import of cache callbacks."""
    from apps.core.state_machine.callbacks.cache import (
        CacheInvalidationCallback,
        ModerationCacheInvalidation,
    )

    return {
        "generic": CacheInvalidationCallback,
        "moderation": ModerationCacheInvalidation,
    }


# ============================================================================
# Original EditSubmission Model (Preserved)
# ============================================================================


@pghistory.track()  # Track all changes by default
class EditSubmission(StateMachineMixin, TrackedModel):
    """Edit submission model with FSM-managed status transitions."""

    state_field_name = "status"

    # Who submitted the edit
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="edit_submissions",
        help_text="User who submitted this edit",
    )

    # What is being edited (Park or Ride)
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        help_text="Type of object being edited",
    )
    object_id = models.PositiveIntegerField(
        null=True,
        blank=True,
        help_text="ID of object being edited (null for new objects)",
    )
    content_object = GenericForeignKey("content_type", "object_id")

    # Type of submission
    submission_type = RichChoiceField(
        choice_group="submission_types", domain="moderation", max_length=10, default="EDIT"
    )

    # The actual changes/data
    changes = models.JSONField(help_text="JSON representation of the changes or new object data")

    # Moderator's edited version of changes before approval
    moderator_changes = models.JSONField(
        null=True,
        blank=True,
        help_text="Moderator's edited version of the changes before approval",
    )

    # Metadata
    reason = models.TextField(help_text="Why this edit/addition is needed")
    source = models.TextField(blank=True, help_text="Source of information (if applicable)")
    status = RichFSMField(
        choice_group="edit_submission_statuses", domain="moderation", max_length=20, default="PENDING"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    # Review details
    handled_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="handled_submissions",
        help_text="Moderator who handled this submission",
    )
    handled_at = models.DateTimeField(null=True, blank=True, help_text="When this submission was handled")
    notes = models.TextField(blank=True, help_text="Notes from the moderator about this submission")

    # Claim tracking for concurrency control
    claimed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="claimed_edit_submissions",
        help_text="Moderator who has claimed this submission for review",
    )
    claimed_at = models.DateTimeField(null=True, blank=True, help_text="When this submission was claimed")

    class Meta(TrackedModel.Meta):
        verbose_name = "Edit Submission"
        verbose_name_plural = "Edit Submissions"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["content_type", "object_id"]),
            models.Index(fields=["status"]),
        ]

    def __str__(self) -> str:
        action = "creation" if self.submission_type == "CREATE" else "edit"
        if model_class := self.content_type.model_class():
            target = self.content_object or model_class.__name__
        else:
            target = "Unknown"
        return f"{action} by {self.user.username} on {target}"

    def _resolve_foreign_keys(self, data: dict[str, Any]) -> dict[str, Any]:
        """Convert foreign key IDs to model instances"""
        if not (model_class := self.content_type.model_class()):
            raise ValueError("Could not resolve model class")

        resolved_data = data.copy()

        for field_name, value in data.items():
            try:
                field = model_class._meta.get_field(field_name)
                if isinstance(field, models.ForeignKey) and value is not None:
                    try:
                        related_obj = field.related_model.objects.get(pk=value)  # type: ignore
                        resolved_data[field_name] = related_obj
                    except ObjectDoesNotExist:
                        raise ValueError(
                            f"Related object {field.related_model.__name__} with pk={value} does not exist"  # type: ignore
                        ) from None
            except FieldDoesNotExist:
                # Field doesn't exist on model, skip it
                continue

        return resolved_data

    def _get_final_changes(self) -> dict[str, Any]:
        """Get the final changes to apply (moderator changes if available, otherwise original changes)"""
        return self.moderator_changes or self.changes

    def claim(self, user: UserType) -> None:
        """
        Claim this submission for review.
        Transition: PENDING -> CLAIMED

        Args:
            user: The moderator claiming this submission

        Raises:
            ValidationError: If submission is not in PENDING state
        """
        from django.core.exceptions import ValidationError

        if self.status != "PENDING":
            raise ValidationError(f"Cannot claim submission: current status is {self.status}, expected PENDING")

        # Set status directly (similar to unclaim method)
        # The transition_to_claimed FSM method was never defined
        self.status = "CLAIMED"
        self.claimed_by = user
        self.claimed_at = timezone.now()
        self.save()

    def unclaim(self, user: UserType = None) -> None:
        """
        Release claim on this submission.
        Transition: CLAIMED -> PENDING

        Args:
            user: The user initiating the unclaim (for audit)

        Raises:
            ValidationError: If submission is not in CLAIMED state
        """
        from django.core.exceptions import ValidationError

        if self.status != "CLAIMED":
            raise ValidationError(f"Cannot unclaim submission: current status is {self.status}, expected CLAIMED")

        # Set status directly (not via FSM transition to avoid cycle)
        # This is intentional - the unclaim action is a special "rollback" operation
        self.status = "PENDING"
        self.claimed_by = None
        self.claimed_at = None
        self.save()

    def approve(self, moderator: UserType, user=None) -> models.Model | None:
        """
        Approve this submission and apply the changes.
        Wrapper method that preserves business logic while using FSM.

        Args:
            moderator: The user approving the submission
            user: Alternative parameter for FSM compatibility

        Returns:
            The created or updated model instance

        Raises:
            ValueError: If submission cannot be approved
            ValidationError: If the data is invalid
        """
        from django.core.exceptions import ValidationError

        # Use user parameter if provided (FSM convention)
        approver = user or moderator

        # Validate state - must be CLAIMED before approval
        if self.status != "CLAIMED":
            raise ValidationError(f"Cannot approve submission: must be CLAIMED first (current status: {self.status})")

        model_class = self.content_type.model_class()
        if not model_class:
            raise ValueError("Could not resolve model class")

        final_changes = self._get_final_changes()
        resolved_changes = self._resolve_foreign_keys(final_changes)

        try:
            if self.submission_type == "CREATE":
                # Create new object
                obj = model_class(**resolved_changes)
                obj.full_clean()
                obj.save()
            else:
                # Update existing object
                if not self.content_object:
                    raise ValueError("Cannot update: content object not found")

                obj = self.content_object
                for field_name, value in resolved_changes.items():
                    if hasattr(obj, field_name):
                        setattr(obj, field_name, value)

                obj.full_clean()
                obj.save()

            # Use FSM transition to update status
            self.transition_to_approved(user=approver)
            self.handled_by = approver
            self.handled_at = timezone.now()
            self.save()

            return obj

        except Exception as e:
            # On error, record the issue and attempt rejection transition
            self.notes = f"Approval failed: {str(e)}"
            try:
                self.transition_to_rejected(user=approver)
                self.handled_by = approver
                self.handled_at = timezone.now()
                self.save()
            except Exception:
                pass
            raise

    def reject(self, moderator: UserType = None, reason: str = "", user=None) -> None:
        """
        Reject this submission.
        Wrapper method that preserves business logic while using FSM.

        Args:
            moderator: The user rejecting the submission
            reason: Reason for rejection
            user: Alternative parameter for FSM compatibility
        """
        from django.core.exceptions import ValidationError

        # Use user parameter if provided (FSM convention)
        rejecter = user or moderator

        # Validate state - must be CLAIMED before rejection
        if self.status != "CLAIMED":
            raise ValidationError(f"Cannot reject submission: must be CLAIMED first (current status: {self.status})")

        # Use FSM transition to update status
        self.transition_to_rejected(user=rejecter)
        self.handled_by = rejecter
        self.handled_at = timezone.now()
        self.notes = f"Rejected: {reason}" if reason else "Rejected"
        self.save()

    def escalate(self, moderator: UserType = None, reason: str = "", user=None) -> None:
        """
        Escalate this submission for higher-level review.
        Wrapper method that preserves business logic while using FSM.

        Args:
            moderator: The user escalating the submission
            reason: Reason for escalation
            user: Alternative parameter for FSM compatibility
        """
        from django.core.exceptions import ValidationError

        # Use user parameter if provided (FSM convention)
        escalator = user or moderator

        # Validate state - must be CLAIMED before escalation
        if self.status != "CLAIMED":
            raise ValidationError(f"Cannot escalate submission: must be CLAIMED first (current status: {self.status})")

        # Use FSM transition to update status
        self.transition_to_escalated(user=escalator)
        self.handled_by = escalator
        self.handled_at = timezone.now()
        self.notes = f"Escalated: {reason}" if reason else "Escalated"
        self.save()

    @property
    def submitted_by(self):
        """Alias for user field to maintain compatibility"""
        return self.user

    @property
    def submitted_at(self):
        """Alias for created_at field to maintain compatibility"""
        return self.created_at


# ============================================================================
# New Moderation System Models
# ============================================================================


@pghistory.track()
class ModerationReport(StateMachineMixin, TrackedModel):
    """
    Model for tracking user reports about content, users, or behavior.

    This handles the initial reporting phase where users flag content
    or behavior that needs moderator attention.
    """

    state_field_name = "status"

    # Report details
    report_type = RichChoiceField(choice_group="report_types", domain="moderation", max_length=50)
    status = RichFSMField(
        choice_group="moderation_report_statuses", domain="moderation", max_length=20, default="PENDING"
    )
    priority = RichChoiceField(choice_group="priority_levels", domain="moderation", max_length=10, default="MEDIUM")

    # What is being reported
    reported_entity_type = models.CharField(
        max_length=50, help_text="Type of entity being reported (park, ride, user, etc.)"
    )
    reported_entity_id = models.PositiveIntegerField(help_text="ID of the entity being reported")
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)

    # Report content
    reason = models.CharField(max_length=200, help_text="Brief reason for the report")
    description = models.TextField(help_text="Detailed description of the issue")
    evidence_urls = models.JSONField(default=list, blank=True, help_text="URLs to evidence (screenshots, etc.)")

    # Users involved
    reported_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="moderation_reports_made",
        help_text="User who made this report",
    )
    assigned_moderator = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="assigned_moderation_reports",
        help_text="Moderator assigned to handle this report",
    )

    # Resolution
    resolution_action = models.CharField(max_length=100, blank=True, help_text="Action taken to resolve")
    resolution_notes = models.TextField(blank=True, help_text="Notes about the resolution")
    resolved_at = models.DateTimeField(null=True, blank=True, help_text="When this report was resolved")

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True, help_text="When this report was created")
    updated_at = models.DateTimeField(auto_now=True, help_text="When this report was last updated")

    class Meta(TrackedModel.Meta):
        verbose_name = "Moderation Report"
        verbose_name_plural = "Moderation Reports"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["status", "priority"]),
            models.Index(fields=["reported_by"]),
            models.Index(fields=["assigned_moderator"]),
            models.Index(fields=["created_at"]),
        ]

    def __str__(self):
        return f"{self.get_report_type_display()} report by {self.reported_by.username}"  # type: ignore


@pghistory.track()
class ModerationQueue(StateMachineMixin, TrackedModel):
    """
    Model for managing moderation workflow and task assignment.

    This represents items in the moderation queue that need attention,
    separate from the initial reports.
    """

    state_field_name = "status"

    # Queue item details
    item_type = RichChoiceField(choice_group="queue_item_types", domain="moderation", max_length=50)
    status = RichFSMField(
        choice_group="moderation_queue_statuses", domain="moderation", max_length=20, default="PENDING"
    )
    priority = RichChoiceField(choice_group="priority_levels", domain="moderation", max_length=10, default="MEDIUM")

    title = models.CharField(max_length=200, help_text="Brief title for the queue item")
    description = models.TextField(help_text="Detailed description of what needs to be done")

    # What entity this relates to
    entity_type = models.CharField(max_length=50, blank=True, help_text="Type of entity (park, ride, user, etc.)")
    entity_id = models.PositiveIntegerField(null=True, blank=True, help_text="ID of the related entity")
    entity_preview = models.JSONField(default=dict, blank=True, help_text="Preview data for the entity")
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)

    # Assignment and timing
    assigned_to = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="assigned_queue_items",
        help_text="Moderator assigned to this item",
    )
    assigned_at = models.DateTimeField(null=True, blank=True, help_text="When this item was assigned")
    estimated_review_time = models.PositiveIntegerField(default=30, help_text="Estimated time in minutes")

    # Metadata
    flagged_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="flagged_queue_items",
        help_text="User who flagged this item",
    )
    tags = models.JSONField(default=list, blank=True, help_text="Tags for categorization")

    # Related objects
    related_report = models.ForeignKey(
        ModerationReport,
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="queue_items",
        help_text="Related moderation report",
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True, help_text="When this item was created")
    updated_at = models.DateTimeField(auto_now=True, help_text="When this item was last updated")

    class Meta(TrackedModel.Meta):
        verbose_name = "Moderation Queue Item"
        verbose_name_plural = "Moderation Queue Items"
        ordering = ["priority", "created_at"]
        indexes = [
            models.Index(fields=["status", "priority"]),
            models.Index(fields=["assigned_to"]),
            models.Index(fields=["created_at"]),
        ]

    def __str__(self):
        return f"{self.get_item_type_display()}: {self.title}"  # type: ignore


@pghistory.track()
class ModerationAction(TrackedModel):
    """
    Model for tracking actions taken against users or content.

    This records what actions moderators have taken, including
    warnings, suspensions, content removal, etc.
    """

    # Action details
    action_type = RichChoiceField(choice_group="moderation_action_types", domain="moderation", max_length=50)
    reason = models.CharField(max_length=200, help_text="Brief reason for the action")
    details = models.TextField(help_text="Detailed explanation of the action")

    # Duration (for temporary actions)
    duration_hours = models.PositiveIntegerField(
        null=True, blank=True, help_text="Duration in hours for temporary actions"
    )
    expires_at = models.DateTimeField(null=True, blank=True, help_text="When this action expires")
    is_active = models.BooleanField(default=True, help_text="Whether this action is currently active")

    # Users involved
    moderator = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="moderation_actions_taken",
        help_text="Moderator who took this action",
    )
    target_user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="moderation_actions_received",
        help_text="User this action was taken against",
    )

    # Related objects
    related_report = models.ForeignKey(
        ModerationReport,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="actions_taken",
        help_text="Related moderation report",
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True, help_text="When this action was created")
    updated_at = models.DateTimeField(auto_now=True, help_text="When this action was last updated")

    class Meta(TrackedModel.Meta):
        verbose_name = "Moderation Action"
        verbose_name_plural = "Moderation Actions"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["target_user", "is_active"]),
            models.Index(fields=["moderator"]),
            models.Index(fields=["expires_at"]),
            models.Index(fields=["created_at"]),
        ]

    def __str__(self):
        return f"{self.get_action_type_display()} against {self.target_user.username} by {self.moderator.username}"  # type: ignore

    def save(self, *args, **kwargs):
        # Set expiration time if duration is provided
        if self.duration_hours and not self.expires_at:
            self.expires_at = timezone.now() + timedelta(hours=self.duration_hours)
        super().save(*args, **kwargs)


@pghistory.track()
class BulkOperation(StateMachineMixin, TrackedModel):
    """
    Model for tracking bulk administrative operations.

    This handles large-scale operations like bulk updates,
    imports, exports, or mass moderation actions.
    """

    state_field_name = "status"

    # Operation details
    operation_type = RichChoiceField(choice_group="bulk_operation_types", domain="moderation", max_length=50)
    status = RichFSMField(choice_group="bulk_operation_statuses", domain="moderation", max_length=20, default="PENDING")
    priority = RichChoiceField(choice_group="priority_levels", domain="moderation", max_length=10, default="MEDIUM")
    description = models.TextField(help_text="Description of what this operation does")

    # Operation parameters and results
    parameters = models.JSONField(default=dict, help_text="Parameters for the operation")
    results = models.JSONField(default=dict, blank=True, help_text="Results and output from the operation")

    # Progress tracking
    total_items = models.PositiveIntegerField(default=0, help_text="Total number of items to process")
    processed_items = models.PositiveIntegerField(default=0, help_text="Number of items processed")
    failed_items = models.PositiveIntegerField(default=0, help_text="Number of items that failed")

    # Timing
    estimated_duration_minutes = models.PositiveIntegerField(
        null=True, blank=True, help_text="Estimated duration in minutes"
    )
    schedule_for = models.DateTimeField(null=True, blank=True, help_text="When to run this operation")

    # Control
    can_cancel = models.BooleanField(default=True, help_text="Whether this operation can be cancelled")

    # User who created the operation
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="bulk_operations_created",
        help_text="User who created this operation",
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    started_at = models.DateTimeField(null=True, blank=True, help_text="When this operation started")
    completed_at = models.DateTimeField(null=True, blank=True, help_text="When this operation completed")
    updated_at = models.DateTimeField(auto_now=True, help_text="When this operation was last updated")

    class Meta(TrackedModel.Meta):
        verbose_name = "Bulk Operation"
        verbose_name_plural = "Bulk Operations"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["status", "priority"]),
            models.Index(fields=["created_by"]),
            models.Index(fields=["schedule_for"]),
            models.Index(fields=["created_at"]),
        ]

    def __str__(self):
        return f"{self.get_operation_type_display()}: {self.description[:50]}"  # type: ignore

    @property
    def progress_percentage(self):
        """Calculate progress percentage."""
        if self.total_items == 0:
            return 0.0
        return round((self.processed_items / self.total_items) * 100, 2)


@pghistory.track()  # Track all changes by default
class PhotoSubmission(StateMachineMixin, TrackedModel):
    """Photo submission model with FSM-managed status transitions."""

    state_field_name = "status"

    # Who submitted the photo
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="photo_submissions",
        help_text="User who submitted this photo",
    )

    # What the photo is for (Park or Ride)
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        help_text="Type of object this photo is for",
    )
    object_id = models.PositiveIntegerField(help_text="ID of object this photo is for")
    content_object = GenericForeignKey("content_type", "object_id")

    # The photo itself
    photo = models.ForeignKey(
        "django_cloudflareimages_toolkit.CloudflareImage",
        on_delete=models.CASCADE,
        help_text="Photo submission stored on Cloudflare Images",
    )
    caption = models.CharField(max_length=255, blank=True, help_text="Photo caption")
    date_taken = models.DateField(null=True, blank=True, help_text="Date the photo was taken")

    # Metadata
    status = RichFSMField(
        choice_group="photo_submission_statuses", domain="moderation", max_length=20, default="PENDING"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    # Review details
    handled_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="handled_photos",
        help_text="Moderator who handled this submission",
    )
    handled_at = models.DateTimeField(null=True, blank=True, help_text="When this submission was handled")
    notes = models.TextField(
        blank=True,
        help_text="Notes from the moderator about this photo submission",
    )

    # Claim tracking for concurrency control
    claimed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="claimed_photo_submissions",
        help_text="Moderator who has claimed this submission for review",
    )
    claimed_at = models.DateTimeField(null=True, blank=True, help_text="When this submission was claimed")

    class Meta(TrackedModel.Meta):
        verbose_name = "Photo Submission"
        verbose_name_plural = "Photo Submissions"
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["content_type", "object_id"]),
            models.Index(fields=["status"]),
        ]

    def __str__(self) -> str:
        return f"Photo submission by {self.user.username} for {self.content_object}"

    def claim(self, user: UserType) -> None:
        """
        Claim this photo submission for review.
        Transition: PENDING -> CLAIMED

        Args:
            user: The moderator claiming this submission

        Raises:
            ValidationError: If submission is not in PENDING state
        """
        from django.core.exceptions import ValidationError

        if self.status != "PENDING":
            raise ValidationError(f"Cannot claim submission: current status is {self.status}, expected PENDING")

        # Set status directly (similar to unclaim method)
        # The transition_to_claimed FSM method was never defined
        self.status = "CLAIMED"
        self.claimed_by = user
        self.claimed_at = timezone.now()
        self.save()

    def unclaim(self, user: UserType = None) -> None:
        """
        Release claim on this photo submission.
        Transition: CLAIMED -> PENDING

        Args:
            user: The user initiating the unclaim (for audit)

        Raises:
            ValidationError: If submission is not in CLAIMED state
        """
        from django.core.exceptions import ValidationError

        if self.status != "CLAIMED":
            raise ValidationError(f"Cannot unclaim submission: current status is {self.status}, expected CLAIMED")

        # Set status directly (not via FSM transition to avoid cycle)
        # This is intentional - the unclaim action is a special "rollback" operation
        self.status = "PENDING"
        self.claimed_by = None
        self.claimed_at = None
        self.save()

    def approve(self, moderator: UserType = None, notes: str = "", user=None) -> None:
        """
        Approve the photo submission.
        Wrapper method that preserves business logic while using FSM.

        Args:
            moderator: The user approving the submission
            notes: Optional approval notes
            user: Alternative parameter for FSM compatibility
        """
        from django.core.exceptions import ValidationError

        from apps.parks.models.media import ParkPhoto
        from apps.rides.models.media import RidePhoto

        # Use user parameter if provided (FSM convention)
        approver = user or moderator

        # Validate state - must be CLAIMED before approval
        if self.status != "CLAIMED":
            raise ValidationError(
                f"Cannot approve photo submission: must be CLAIMED first (current status: {self.status})"
            )

        # Determine the correct photo model based on the content type
        model_class = self.content_type.model_class()
        if model_class.__name__ == "Park":
            PhotoModel = ParkPhoto
        elif model_class.__name__ == "Ride":
            PhotoModel = RidePhoto
        else:
            raise ValueError(f"Unsupported content type: {model_class.__name__}")

        # Create the approved photo
        PhotoModel.objects.create(
            uploaded_by=self.user,
            content_object=self.content_object,
            image=self.photo,
            caption=self.caption,
            is_approved=True,
        )

        # Use FSM transition to update status
        self.transition_to_approved(user=approver)
        self.handled_by = approver  # type: ignore
        self.handled_at = timezone.now()
        self.notes = notes
        self.save()

    def reject(self, moderator: UserType = None, notes: str = "", user=None) -> None:
        """
        Reject the photo submission.
        Wrapper method that preserves business logic while using FSM.

        Args:
            moderator: The user rejecting the submission
            notes: Rejection reason
            user: Alternative parameter for FSM compatibility
        """
        from django.core.exceptions import ValidationError

        # Use user parameter if provided (FSM convention)
        rejecter = user or moderator

        # Validate state - must be CLAIMED before rejection
        if self.status != "CLAIMED":
            raise ValidationError(
                f"Cannot reject photo submission: must be CLAIMED first (current status: {self.status})"
            )

        # Use FSM transition to update status
        self.transition_to_rejected(user=rejecter)
        self.handled_by = rejecter  # type: ignore
        self.handled_at = timezone.now()
        self.notes = notes
        self.save()

    def auto_approve(self) -> None:
        """Auto-approve submissions from moderators."""
        # Get user role safely
        user_role = getattr(self.user, "role", None)

        # If user is moderator or above, claim then approve
        if user_role in ["MODERATOR", "ADMIN", "SUPERUSER"]:
            self.claim(user=self.user)
            self.approve(self.user)

    def escalate(self, moderator: UserType = None, notes: str = "", user=None) -> None:
        """
        Escalate the photo submission to admin.
        Wrapper method that preserves business logic while using FSM.

        Args:
            moderator: The user escalating the submission
            notes: Escalation reason
            user: Alternative parameter for FSM compatibility
        """
        from django.core.exceptions import ValidationError

        # Use user parameter if provided (FSM convention)
        escalator = user or moderator

        # Validate state - must be CLAIMED before escalation
        if self.status != "CLAIMED":
            raise ValidationError(
                f"Cannot escalate photo submission: must be CLAIMED first (current status: {self.status})"
            )

        # Use FSM transition to update status
        self.transition_to_escalated(user=escalator)
        self.handled_by = escalator  # type: ignore
        self.handled_at = timezone.now()
        self.notes = notes
        self.save()
