"""
Moderation services for ThrillWiki.

This module provides business logic for the content moderation workflow:
- Creating submissions
- Starting reviews with locks
- Approving submissions with atomic transactions
- Selective approval of individual items
- Rejecting submissions
- Unlocking expired submissions
"""

import logging
from datetime import timedelta
from django.db import transaction
from django.utils import timezone
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError, PermissionDenied

from apps.moderation.models import ContentSubmission, SubmissionItem, ModerationLock

logger = logging.getLogger(__name__)


class ModerationService:
    """
    Service class for moderation operations.
    
    All public methods use atomic transactions to ensure data integrity.
    """
    
    @staticmethod
    @transaction.atomic
    def create_submission(
        user,
        entity,
        submission_type,
        title,
        description='',
        items_data=None,
        metadata=None,
        auto_submit=True,
        **kwargs
    ):
        """
        Create a new content submission with items.
        
        Args:
            user: User creating the submission
            entity: Entity being modified (Park, Ride, Company, etc.)
            submission_type: 'create', 'update', or 'delete'
            title: Brief description of changes
            description: Detailed description (optional)
            items_data: List of dicts with item details:
                [
                    {
                        'field_name': 'name',
                        'field_label': 'Park Name',
                        'old_value': 'Old Name',
                        'new_value': 'New Name',
                        'change_type': 'modify',
                        'is_required': False,
                        'order': 0
                    },
                    ...
                ]
            metadata: Additional metadata dict
            auto_submit: Whether to automatically submit (move to pending state)
            **kwargs: Additional submission fields (source, ip_address, user_agent)
        
        Returns:
            ContentSubmission instance
        
        Raises:
            ValidationError: If validation fails
        """
        # Get ContentType for entity
        entity_type = ContentType.objects.get_for_model(entity)
        
        # Create submission
        submission = ContentSubmission.objects.create(
            user=user,
            entity_type=entity_type,
            entity_id=entity.id,
            submission_type=submission_type,
            title=title,
            description=description,
            metadata=metadata or {},
            source=kwargs.get('source', 'web'),
            ip_address=kwargs.get('ip_address'),
            user_agent=kwargs.get('user_agent', '')
        )
        
        # Create submission items
        if items_data:
            for item_data in items_data:
                SubmissionItem.objects.create(
                    submission=submission,
                    field_name=item_data['field_name'],
                    field_label=item_data.get('field_label', item_data['field_name']),
                    old_value=item_data.get('old_value'),
                    new_value=item_data.get('new_value'),
                    change_type=item_data.get('change_type', 'modify'),
                    is_required=item_data.get('is_required', False),
                    order=item_data.get('order', 0)
                )
        
        # Auto-submit if requested
        if auto_submit:
            submission.submit()
            submission.save()
        
        return submission
    
    @staticmethod
    @transaction.atomic
    def start_review(submission_id, reviewer):
        """
        Start reviewing a submission (lock it).
        
        Args:
            submission_id: UUID of submission
            reviewer: User starting the review
        
        Returns:
            ContentSubmission instance
        
        Raises:
            ValidationError: If submission cannot be reviewed
            PermissionDenied: If user lacks permission
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        # Check if user has permission to review
        if not ModerationService._can_moderate(reviewer):
            raise PermissionDenied("User does not have moderation permission")
        
        # Check if submission is in correct state
        if submission.status != ContentSubmission.STATE_PENDING:
            raise ValidationError(f"Submission must be pending to start review (current: {submission.status})")
        
        # Check if already locked by another user
        if submission.locked_by and submission.locked_by != reviewer:
            if submission.is_locked():
                raise ValidationError(f"Submission is locked by {submission.locked_by.email}")
        
        # Start review (FSM transition)
        submission.start_review(reviewer)
        submission.save()
        
        # Create lock record
        expires_at = timezone.now() + timedelta(minutes=15)
        ModerationLock.objects.update_or_create(
            submission=submission,
            defaults={
                'locked_by': reviewer,
                'expires_at': expires_at,
                'is_active': True,
                'released_at': None
            }
        )
        
        return submission
    
    @staticmethod
    @transaction.atomic
    def approve_submission(submission_id, reviewer):
        """
        Approve an entire submission and apply all changes.
        
        This method uses atomic transactions to ensure all-or-nothing behavior.
        If any part fails, the entire operation is rolled back.
        
        Args:
            submission_id: UUID of submission
            reviewer: User approving the submission
        
        Returns:
            ContentSubmission instance
        
        Raises:
            ValidationError: If submission cannot be approved
            PermissionDenied: If user lacks permission
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        # Check permission
        if not ModerationService._can_moderate(reviewer):
            raise PermissionDenied("User does not have moderation permission")
        
        # Check if submission can be reviewed
        if not submission.can_review(reviewer):
            raise ValidationError("Submission cannot be reviewed at this time")
        
        # Apply all changes
        entity = submission.entity
        if not entity:
            raise ValidationError("Entity no longer exists")
        
        # Get all pending items
        items = submission.items.filter(status='pending')
        
        for item in items:
            # Apply change to entity
            if item.change_type in ['add', 'modify']:
                setattr(entity, item.field_name, item.new_value)
            elif item.change_type == 'remove':
                setattr(entity, item.field_name, None)
            
            # Mark item as approved
            item.approve(reviewer)
        
        # Save entity (this will trigger versioning through lifecycle hooks)
        entity.save()
        
        # Approve submission (FSM transition)
        submission.approve(reviewer)
        submission.save()
        
        # Release lock
        try:
            lock = ModerationLock.objects.get(submission=submission, is_active=True)
            lock.release()
        except ModerationLock.DoesNotExist:
            pass
        
        # Send notification email asynchronously
        try:
            from apps.moderation.tasks import send_moderation_notification
            send_moderation_notification.delay(str(submission.id), 'approved')
        except Exception as e:
            # Don't fail the approval if email fails to queue
            logger.warning(f"Failed to queue approval notification: {str(e)}")
        
        return submission
    
    @staticmethod
    @transaction.atomic
    def approve_selective(submission_id, reviewer, item_ids):
        """
        Approve only specific items in a submission (selective approval).
        
        This allows moderators to approve some changes while rejecting others.
        Uses atomic transactions for data integrity.
        
        Args:
            submission_id: UUID of submission
            reviewer: User approving the items
            item_ids: List of item UUIDs to approve
        
        Returns:
            dict with counts: {'approved': N, 'total': M}
        
        Raises:
            ValidationError: If submission cannot be reviewed
            PermissionDenied: If user lacks permission
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        # Check permission
        if not ModerationService._can_moderate(reviewer):
            raise PermissionDenied("User does not have moderation permission")
        
        # Check if submission can be reviewed
        if not submission.can_review(reviewer):
            raise ValidationError("Submission cannot be reviewed at this time")
        
        # Get entity
        entity = submission.entity
        if not entity:
            raise ValidationError("Entity no longer exists")
        
        # Get items to approve
        items_to_approve = submission.items.filter(
            id__in=item_ids,
            status='pending'
        )
        
        approved_count = 0
        for item in items_to_approve:
            # Apply change to entity
            if item.change_type in ['add', 'modify']:
                setattr(entity, item.field_name, item.new_value)
            elif item.change_type == 'remove':
                setattr(entity, item.field_name, None)
            
            # Mark item as approved
            item.approve(reviewer)
            approved_count += 1
        
        # Save entity if any changes were made
        if approved_count > 0:
            entity.save()
        
        # Check if all items are now reviewed
        pending_count = submission.items.filter(status='pending').count()
        
        if pending_count == 0:
            # All items reviewed - mark submission as approved
            submission.approve(reviewer)
            submission.save()
            
            # Release lock
            try:
                lock = ModerationLock.objects.get(submission=submission, is_active=True)
                lock.release()
            except ModerationLock.DoesNotExist:
                pass
        
        return {
            'approved': approved_count,
            'total': submission.items.count(),
            'pending': pending_count,
            'submission_approved': pending_count == 0
        }
    
    @staticmethod
    @transaction.atomic
    def reject_submission(submission_id, reviewer, reason):
        """
        Reject an entire submission.
        
        Args:
            submission_id: UUID of submission
            reviewer: User rejecting the submission
            reason: Reason for rejection
        
        Returns:
            ContentSubmission instance
        
        Raises:
            ValidationError: If submission cannot be rejected
            PermissionDenied: If user lacks permission
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        # Check permission
        if not ModerationService._can_moderate(reviewer):
            raise PermissionDenied("User does not have moderation permission")
        
        # Check if submission can be reviewed
        if not submission.can_review(reviewer):
            raise ValidationError("Submission cannot be reviewed at this time")
        
        # Reject all pending items
        items = submission.items.filter(status='pending')
        for item in items:
            item.reject(reviewer, reason)
        
        # Reject submission (FSM transition)
        submission.reject(reviewer, reason)
        submission.save()
        
        # Release lock
        try:
            lock = ModerationLock.objects.get(submission=submission, is_active=True)
            lock.release()
        except ModerationLock.DoesNotExist:
            pass
        
        # Send notification email asynchronously
        try:
            from apps.moderation.tasks import send_moderation_notification
            send_moderation_notification.delay(str(submission.id), 'rejected')
        except Exception as e:
            # Don't fail the rejection if email fails to queue
            logger.warning(f"Failed to queue rejection notification: {str(e)}")
        
        return submission
    
    @staticmethod
    @transaction.atomic
    def reject_selective(submission_id, reviewer, item_ids, reason=''):
        """
        Reject specific items in a submission.
        
        Args:
            submission_id: UUID of submission
            reviewer: User rejecting the items
            item_ids: List of item UUIDs to reject
            reason: Reason for rejection (optional)
        
        Returns:
            dict with counts: {'rejected': N, 'total': M}
        
        Raises:
            ValidationError: If submission cannot be reviewed
            PermissionDenied: If user lacks permission
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        # Check permission
        if not ModerationService._can_moderate(reviewer):
            raise PermissionDenied("User does not have moderation permission")
        
        # Check if submission can be reviewed
        if not submission.can_review(reviewer):
            raise ValidationError("Submission cannot be reviewed at this time")
        
        # Get items to reject
        items_to_reject = submission.items.filter(
            id__in=item_ids,
            status='pending'
        )
        
        rejected_count = 0
        for item in items_to_reject:
            item.reject(reviewer, reason)
            rejected_count += 1
        
        # Check if all items are now reviewed
        pending_count = submission.items.filter(status='pending').count()
        
        if pending_count == 0:
            # All items reviewed
            approved_count = submission.items.filter(status='approved').count()
            
            if approved_count > 0:
                # Some items approved - mark submission as approved
                submission.approve(reviewer)
                submission.save()
            else:
                # All items rejected - mark submission as rejected
                submission.reject(reviewer, "All items rejected")
                submission.save()
            
            # Release lock
            try:
                lock = ModerationLock.objects.get(submission=submission, is_active=True)
                lock.release()
            except ModerationLock.DoesNotExist:
                pass
        
        return {
            'rejected': rejected_count,
            'total': submission.items.count(),
            'pending': pending_count,
            'submission_complete': pending_count == 0
        }
    
    @staticmethod
    @transaction.atomic
    def unlock_submission(submission_id):
        """
        Manually unlock a submission.
        
        Args:
            submission_id: UUID of submission
        
        Returns:
            ContentSubmission instance
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        if submission.status == ContentSubmission.STATE_REVIEWING:
            submission.unlock()
            submission.save()
            
            # Release lock record
            try:
                lock = ModerationLock.objects.get(submission=submission, is_active=True)
                lock.release()
            except ModerationLock.DoesNotExist:
                pass
        
        return submission
    
    @staticmethod
    def cleanup_expired_locks():
        """
        Cleanup expired locks and unlock submissions.
        
        This should be called periodically (e.g., every 5 minutes via Celery).
        
        Returns:
            int: Number of locks cleaned up
        """
        return ModerationLock.cleanup_expired()
    
    @staticmethod
    def get_queue(status=None, user=None, limit=50, offset=0):
        """
        Get moderation queue with filters.
        
        Args:
            status: Filter by status (optional)
            user: Filter by submitter (optional)
            limit: Maximum results
            offset: Pagination offset
        
        Returns:
            QuerySet of ContentSubmission objects
        """
        queryset = ContentSubmission.objects.select_related(
            'user',
            'entity_type',
            'locked_by',
            'reviewed_by'
        ).prefetch_related('items')
        
        if status:
            queryset = queryset.filter(status=status)
        
        if user:
            queryset = queryset.filter(user=user)
        
        return queryset[offset:offset + limit]
    
    @staticmethod
    def get_submission_details(submission_id):
        """
        Get full submission details with all items.
        
        Args:
            submission_id: UUID of submission
        
        Returns:
            ContentSubmission instance with prefetched items
        """
        return ContentSubmission.objects.select_related(
            'user',
            'entity_type',
            'locked_by',
            'reviewed_by'
        ).prefetch_related(
            'items',
            'items__reviewed_by'
        ).get(id=submission_id)
    
    @staticmethod
    def _can_moderate(user):
        """
        Check if user has moderation permission.
        
        Args:
            user: User to check
        
        Returns:
            bool: True if user can moderate
        """
        if not user or not user.is_authenticated:
            return False
        
        # Check if user is superuser
        if user.is_superuser:
            return True
        
        # Check if user has moderator or admin role
        try:
            return user.role.is_moderator
        except:
            return False
    
    @staticmethod
    @transaction.atomic
    def delete_submission(submission_id, user):
        """
        Delete a submission (only if draft or by owner).
        
        Args:
            submission_id: UUID of submission
            user: User attempting to delete
        
        Returns:
            bool: True if deleted
        
        Raises:
            PermissionDenied: If user cannot delete
            ValidationError: If submission cannot be deleted
        """
        submission = ContentSubmission.objects.select_for_update().get(id=submission_id)
        
        # Check permission
        is_owner = submission.user == user
        is_moderator = ModerationService._can_moderate(user)
        
        if not (is_owner or is_moderator):
            raise PermissionDenied("Only the owner or a moderator can delete this submission")
        
        # Check state
        if submission.status not in [ContentSubmission.STATE_DRAFT, ContentSubmission.STATE_PENDING]:
            if not is_moderator:
                raise ValidationError("Only moderators can delete submissions under review")
        
        # Delete submission (cascades to items and lock)
        submission.delete()
        return True
