"""
Celery tasks for moderation app.

This module contains background tasks for moderation management including:
- Automatic expiration of stale claim locks
- Cleanup of orphaned submissions
"""

import logging
from datetime import timedelta

from celery import shared_task
from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils import timezone

from apps.core.utils import capture_and_log

logger = logging.getLogger(__name__)
User = get_user_model()

# Default lock duration in minutes (matching views.py)
DEFAULT_LOCK_DURATION_MINUTES = 15


@shared_task(name="moderation.expire_stale_claims")
def expire_stale_claims(lock_duration_minutes: int = None) -> dict:
    """
    Expire claims on submissions that have been locked for too long without action.
    
    This task finds submissions in CLAIMED status where claimed_at is older than
    the lock duration (default 15 minutes) and releases them back to PENDING
    so other moderators can claim them.
    
    This task should be run every 5 minutes via Celery Beat.
    
    Args:
        lock_duration_minutes: Override the default lock duration (15 minutes)
    
    Returns:
        dict: Summary with counts of processed, succeeded, and failed releases
    """
    from apps.moderation.models import EditSubmission, PhotoSubmission
    
    if lock_duration_minutes is None:
        lock_duration_minutes = DEFAULT_LOCK_DURATION_MINUTES
    
    logger.info("Starting stale claims expiration check (timeout: %d minutes)", lock_duration_minutes)
    
    # Calculate cutoff time (claims older than this should be released)
    cutoff_time = timezone.now() - timedelta(minutes=lock_duration_minutes)
    
    result = {
        "edit_submissions": {"processed": 0, "released": 0, "failed": 0},
        "photo_submissions": {"processed": 0, "released": 0, "failed": 0},
        "failures": [],
        "cutoff_time": cutoff_time.isoformat(),
    }
    
    # Process EditSubmissions with stale claims
    # Query without lock first, then lock each row individually in transaction
    stale_edit_ids = list(
        EditSubmission.objects.filter(
            status="CLAIMED",
            claimed_at__lt=cutoff_time,
        ).values_list("id", flat=True)
    )
    
    for submission_id in stale_edit_ids:
        result["edit_submissions"]["processed"] += 1
        try:
            with transaction.atomic():
                # Lock and fetch the specific row
                submission = EditSubmission.objects.select_for_update(skip_locked=True).filter(
                    id=submission_id,
                    status="CLAIMED",  # Re-verify status in case it changed
                ).first()
                
                if submission:
                    _release_claim(submission)
                    result["edit_submissions"]["released"] += 1
                    logger.info(
                        "Released stale claim on EditSubmission %s (claimed by %s at %s)",
                        submission_id,
                        submission.claimed_by,
                        submission.claimed_at,
                    )
        except Exception as e:
            result["edit_submissions"]["failed"] += 1
            error_msg = f"EditSubmission {submission_id}: {str(e)}"
            result["failures"].append(error_msg)
            capture_and_log(
                e,
                f"Release stale claim on EditSubmission {submission_id}",
                source="task",
            )
    
    # Process PhotoSubmissions with stale claims
    stale_photo_ids = list(
        PhotoSubmission.objects.filter(
            status="CLAIMED",
            claimed_at__lt=cutoff_time,
        ).values_list("id", flat=True)
    )
    
    for submission_id in stale_photo_ids:
        result["photo_submissions"]["processed"] += 1
        try:
            with transaction.atomic():
                # Lock and fetch the specific row
                submission = PhotoSubmission.objects.select_for_update(skip_locked=True).filter(
                    id=submission_id,
                    status="CLAIMED",  # Re-verify status in case it changed
                ).first()
                
                if submission:
                    _release_claim(submission)
                    result["photo_submissions"]["released"] += 1
                    logger.info(
                        "Released stale claim on PhotoSubmission %s (claimed by %s at %s)",
                        submission_id,
                        submission.claimed_by,
                        submission.claimed_at,
                    )
        except Exception as e:
            result["photo_submissions"]["failed"] += 1
            error_msg = f"PhotoSubmission {submission_id}: {str(e)}"
            result["failures"].append(error_msg)
            capture_and_log(
                e,
                f"Release stale claim on PhotoSubmission {submission_id}",
                source="task",
            )
    
    total_released = result["edit_submissions"]["released"] + result["photo_submissions"]["released"]
    total_failed = result["edit_submissions"]["failed"] + result["photo_submissions"]["failed"]
    
    logger.info(
        "Completed stale claims expiration: %s released, %s failed",
        total_released,
        total_failed,
    )
    
    return result


def _release_claim(submission):
    """
    Release a stale claim on a submission.
    
    Uses the unclaim() FSM method to properly transition from CLAIMED to PENDING
    and clear the claimed_by and claimed_at fields.
    
    Args:
        submission: EditSubmission or PhotoSubmission instance
    """
    # Store info for logging before clearing
    claimed_by = submission.claimed_by
    claimed_at = submission.claimed_at
    
    # Use the FSM unclaim method - pass None for system-initiated unclaim
    submission.unclaim(user=None)
    
    # Log the automatic release
    logger.debug(
        "Auto-released claim: submission=%s, was_claimed_by=%s, claimed_at=%s",
        submission.id,
        claimed_by,
        claimed_at,
    )
