"""
Comprehensive tests for the moderation app.

This module contains tests for:
- EditSubmission state machine transitions
- PhotoSubmission state machine transitions
- ModerationReport state machine transitions
- ModerationQueue state machine transitions
- BulkOperation state machine transitions
- FSM transition logging with django-fsm-log
- Mixin functionality tests
"""

import json

from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.http import HttpRequest, JsonResponse
from django.test import Client, RequestFactory, TestCase
from django.utils import timezone
from django.views.generic import DetailView
from django_fsm import TransitionNotAllowed

from apps.parks.models import Company as Operator

from ..mixins import (
    AdminRequiredMixin,
    EditSubmissionMixin,
    HistoryMixin,
    InlineEditMixin,
    ModeratorRequiredMixin,
    PhotoSubmissionMixin,
)
from ..models import (
    BulkOperation,
    EditSubmission,
    ModerationAction,
    ModerationQueue,
    ModerationReport,
    PhotoSubmission,
)

User = get_user_model()


class TestView(
    EditSubmissionMixin,
    PhotoSubmissionMixin,
    InlineEditMixin,
    HistoryMixin,
    DetailView,
):
    model = Operator
    template_name = "test.html"
    pk_url_kwarg = "pk"
    slug_url_kwarg = "slug"

    def get_context_data(self, **kwargs):
        if not hasattr(self, "object"):
            self.object = self.get_object()
        return super().get_context_data(**kwargs)

    def setup(self, request: HttpRequest, *args, **kwargs):
        super().setup(request, *args, **kwargs)
        self.request = request


class ModerationMixinsTests(TestCase):
    def setUp(self):
        self.client = Client()
        self.factory = RequestFactory()

        # Create users with different roles
        self.user = User.objects.create_user(
            username="testuser",
            email="test@example.com",
            password="testpass123",
        )
        self.moderator = User.objects.create_user(
            username="moderator",
            email="moderator@example.com",
            password="modpass123",
            role="MODERATOR",
        )
        self.admin = User.objects.create_user(
            username="admin",
            email="admin@example.com",
            password="adminpass123",
            role="ADMIN",
        )

        # Create test company
        self.operator = Operator.objects.create(
            name="Test Operator",
            website="http://example.com",
            description="Test Description",
        )

    def test_edit_submission_mixin_unauthenticated(self):
        """Test edit submission when not logged in"""
        view = TestView()
        request = self.factory.post(f"/test/{self.operator.pk}/")
        request.user = AnonymousUser()
        view.setup(request, pk=self.operator.pk)
        view.kwargs = {"pk": self.operator.pk}
        response = view.handle_edit_submission(request, {})
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 403)

    def test_edit_submission_mixin_no_changes(self):
        """Test edit submission with no changes"""
        view = TestView()
        request = self.factory.post(
            f"/test/{self.operator.pk}/",
            data=json.dumps({}),
            content_type="application/json",
        )
        request.user = self.user
        view.setup(request, pk=self.operator.pk)
        view.kwargs = {"pk": self.operator.pk}
        response = view.post(request)
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 400)

    def test_edit_submission_mixin_invalid_json(self):
        """Test edit submission with invalid JSON"""
        view = TestView()
        request = self.factory.post(
            f"/test/{self.operator.pk}/",
            data="invalid json",
            content_type="application/json",
        )
        request.user = self.user
        view.setup(request, pk=self.operator.pk)
        view.kwargs = {"pk": self.operator.pk}
        response = view.post(request)
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 400)

    def test_edit_submission_mixin_regular_user(self):
        """Test edit submission as regular user"""
        view = TestView()
        request = self.factory.post(f"/test/{self.operator.pk}/")
        request.user = self.user
        view.setup(request, pk=self.operator.pk)
        view.kwargs = {"pk": self.operator.pk}
        changes = {"name": "New Name"}
        response = view.handle_edit_submission(request, changes, "Test reason", "Test source")
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content.decode())
        self.assertFalse(data["auto_approved"])

    def test_edit_submission_mixin_moderator(self):
        """Test edit submission as moderator"""
        view = TestView()
        request = self.factory.post(f"/test/{self.operator.pk}/")
        request.user = self.moderator
        view.setup(request, pk=self.operator.pk)
        view.kwargs = {"pk": self.operator.pk}
        changes = {"name": "New Name"}
        response = view.handle_edit_submission(request, changes, "Test reason", "Test source")
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content.decode())
        self.assertTrue(data["auto_approved"])

    def test_photo_submission_mixin_unauthenticated(self):
        """Test photo submission when not logged in"""
        view = TestView()
        view.kwargs = {"pk": self.operator.pk}
        view.object = self.operator

        request = self.factory.post(f"/test/{self.operator.pk}/", data={}, format="multipart")
        request.user = AnonymousUser()
        view.setup(request, pk=self.operator.pk)
        response = view.handle_photo_submission(request)
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 403)

    def test_photo_submission_mixin_no_photo(self):
        """Test photo submission with no photo"""
        view = TestView()
        view.kwargs = {"pk": self.operator.pk}
        view.object = self.operator

        request = self.factory.post(f"/test/{self.operator.pk}/", data={}, format="multipart")
        request.user = self.user
        view.setup(request, pk=self.operator.pk)
        response = view.handle_photo_submission(request)
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 400)

    def test_photo_submission_mixin_regular_user(self):
        """Test photo submission as regular user"""
        view = TestView()
        view.kwargs = {"pk": self.operator.pk}
        view.object = self.operator

        # Create a test photo file
        photo = SimpleUploadedFile(
            "test.gif",
            b"GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;",
            content_type="image/gif",
        )

        request = self.factory.post(
            f"/test/{self.operator.pk}/",
            data={
                "photo": photo,
                "caption": "Test Photo",
                "date_taken": "2024-01-01",
            },
            format="multipart",
        )
        request.user = self.user
        view.setup(request, pk=self.operator.pk)

        response = view.handle_photo_submission(request)
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content.decode())
        self.assertFalse(data["auto_approved"])

    def test_photo_submission_mixin_moderator(self):
        """Test photo submission as moderator"""
        view = TestView()
        view.kwargs = {"pk": self.operator.pk}
        view.object = self.operator

        # Create a test photo file
        photo = SimpleUploadedFile(
            "test.gif",
            b"GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;",
            content_type="image/gif",
        )

        request = self.factory.post(
            f"/test/{self.operator.pk}/",
            data={
                "photo": photo,
                "caption": "Test Photo",
                "date_taken": "2024-01-01",
            },
            format="multipart",
        )
        request.user = self.moderator
        view.setup(request, pk=self.operator.pk)

        response = view.handle_photo_submission(request)
        self.assertIsInstance(response, JsonResponse)
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content.decode())
        self.assertTrue(data["auto_approved"])

    def test_moderator_required_mixin(self):
        """Test moderator required mixin"""

        class TestModeratorView(ModeratorRequiredMixin):
            pass

        view = TestModeratorView()

        # Test unauthenticated user
        request = self.factory.get("/test/")
        request.user = AnonymousUser()
        view.request = request
        self.assertFalse(view.test_func())

        # Test regular user
        request.user = self.user
        view.request = request
        self.assertFalse(view.test_func())

        # Test moderator
        request.user = self.moderator
        view.request = request
        self.assertTrue(view.test_func())

        # Test admin
        request.user = self.admin
        view.request = request
        self.assertTrue(view.test_func())

    def test_admin_required_mixin(self):
        """Test admin required mixin"""

        class TestAdminView(AdminRequiredMixin):
            pass

        view = TestAdminView()

        # Test unauthenticated user
        request = self.factory.get("/test/")
        request.user = AnonymousUser()
        view.request = request
        self.assertFalse(view.test_func())

        # Test regular user
        request.user = self.user
        view.request = request
        self.assertFalse(view.test_func())

        # Test moderator
        request.user = self.moderator
        view.request = request
        self.assertFalse(view.test_func())

        # Test admin
        request.user = self.admin
        view.request = request
        self.assertTrue(view.test_func())

    def test_inline_edit_mixin(self):
        """Test inline edit mixin"""
        view = TestView()
        view.kwargs = {"pk": self.operator.pk}
        view.object = self.operator

        # Test unauthenticated user
        request = self.factory.get(f"/test/{self.operator.pk}/")
        request.user = AnonymousUser()
        view.setup(request, pk=self.operator.pk)
        context = view.get_context_data()
        self.assertNotIn("can_edit", context)

        # Test regular user
        request.user = self.user
        view.setup(request, pk=self.operator.pk)
        context = view.get_context_data()
        self.assertTrue(context["can_edit"])
        self.assertFalse(context["can_auto_approve"])

        # Test moderator
        request.user = self.moderator
        view.setup(request, pk=self.operator.pk)
        context = view.get_context_data()
        self.assertTrue(context["can_edit"])
        self.assertTrue(context["can_auto_approve"])

    def test_history_mixin(self):
        """Test history mixin"""
        view = TestView()
        view.kwargs = {"pk": self.operator.pk}
        view.object = self.operator
        request = self.factory.get(f"/test/{self.operator.pk}/")
        request.user = self.user
        view.setup(request, pk=self.operator.pk)

        # Create some edit submissions
        EditSubmission.objects.create(
            user=self.user,
            content_type=ContentType.objects.get_for_model(Operator),
            object_id=getattr(self.operator, "id", None),
            submission_type="EDIT",
            changes={"name": "New Name"},
            status="APPROVED",
        )

        context = view.get_context_data()
        self.assertIn("history", context)
        self.assertIn("edit_submissions", context)
        self.assertEqual(len(context["edit_submissions"]), 1)


# ============================================================================
# EditSubmission FSM Transition Tests
# ============================================================================


class EditSubmissionTransitionTests(TestCase):
    """Comprehensive tests for EditSubmission FSM transitions."""

    def setUp(self):
        """Set up test fixtures."""
        self.user = User.objects.create_user(
            username="testuser", email="test@example.com", password="testpass123", role="USER"
        )
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.admin = User.objects.create_user(
            username="admin", email="admin@example.com", password="testpass123", role="ADMIN"
        )
        self.operator = Operator.objects.create(name="Test Operator", description="Test Description")
        self.content_type = ContentType.objects.get_for_model(Operator)

    def _create_submission(self, status="PENDING"):
        """Helper to create an EditSubmission."""
        return EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status=status,
            reason="Test reason",
        )

    def test_pending_to_claimed_to_approved_transition(self):
        """Test transition from PENDING to CLAIMED to APPROVED (mandatory flow)."""
        submission = self._create_submission()
        self.assertEqual(submission.status, "PENDING")

        # Must claim first
        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        self.assertEqual(submission.status, "CLAIMED")

        # Now can approve
        submission.transition_to_approved(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "APPROVED")
        self.assertEqual(submission.handled_by, self.moderator)
        self.assertIsNotNone(submission.handled_at)

    def test_pending_to_claimed_to_rejected_transition(self):
        """Test transition from PENDING to CLAIMED to REJECTED (mandatory flow)."""
        submission = self._create_submission()
        self.assertEqual(submission.status, "PENDING")

        # Must claim first
        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        self.assertEqual(submission.status, "CLAIMED")

        # Now can reject
        submission.transition_to_rejected(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.notes = "Rejected: Insufficient evidence"
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "REJECTED")
        self.assertEqual(submission.handled_by, self.moderator)
        self.assertIn("Rejected", submission.notes)

    def test_pending_to_claimed_to_escalated_transition(self):
        """Test transition from PENDING to CLAIMED to ESCALATED (mandatory flow)."""
        submission = self._create_submission()
        self.assertEqual(submission.status, "PENDING")

        # Must claim first
        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        self.assertEqual(submission.status, "CLAIMED")

        # Now can escalate
        submission.transition_to_escalated(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.notes = "Escalated: Needs admin review"
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "ESCALATED")

    def test_escalated_to_approved_transition(self):
        """Test transition from ESCALATED to APPROVED."""
        submission = self._create_submission(status="ESCALATED")

        submission.transition_to_approved(user=self.admin)
        submission.handled_by = self.admin
        submission.handled_at = timezone.now()
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "APPROVED")
        self.assertEqual(submission.handled_by, self.admin)

    def test_escalated_to_rejected_transition(self):
        """Test transition from ESCALATED to REJECTED."""
        submission = self._create_submission(status="ESCALATED")

        submission.transition_to_rejected(user=self.admin)
        submission.handled_by = self.admin
        submission.handled_at = timezone.now()
        submission.notes = "Rejected by admin"
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "REJECTED")

    def test_invalid_transition_from_approved(self):
        """Test that transitions from APPROVED state fail."""
        submission = self._create_submission(status="APPROVED")

        # Attempting to transition from APPROVED should raise TransitionNotAllowed
        with self.assertRaises(TransitionNotAllowed):
            submission.transition_to_rejected(user=self.moderator)

    def test_invalid_transition_from_rejected(self):
        """Test that transitions from REJECTED state fail."""
        submission = self._create_submission(status="REJECTED")

        # Attempting to transition from REJECTED should raise TransitionNotAllowed
        with self.assertRaises(TransitionNotAllowed):
            submission.transition_to_approved(user=self.moderator)

    def test_approve_wrapper_method(self):
        """Test the approve() wrapper method (requires CLAIMED state first)."""
        submission = self._create_submission()

        # Must claim first
        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        self.assertEqual(submission.status, "CLAIMED")

        # Now can approve
        submission.approve(self.moderator)

        submission.refresh_from_db()
        self.assertEqual(submission.status, "APPROVED")
        self.assertEqual(submission.handled_by, self.moderator)
        self.assertIsNotNone(submission.handled_at)

    def test_reject_wrapper_method(self):
        """Test the reject() wrapper method (requires CLAIMED state first)."""
        submission = self._create_submission()

        # Must claim first
        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        self.assertEqual(submission.status, "CLAIMED")

        # Now can reject
        submission.reject(self.moderator, reason="Not enough evidence")

        submission.refresh_from_db()
        self.assertEqual(submission.status, "REJECTED")
        self.assertIn("Not enough evidence", submission.notes)

    def test_escalate_wrapper_method(self):
        """Test the escalate() wrapper method (requires CLAIMED state first)."""
        submission = self._create_submission()

        # Must claim first
        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        self.assertEqual(submission.status, "CLAIMED")

        # Now can escalate
        submission.escalate(self.moderator, reason="Needs admin approval")

        submission.refresh_from_db()
        self.assertEqual(submission.status, "ESCALATED")
        self.assertIn("Needs admin approval", submission.notes)


# ============================================================================
# ModerationReport FSM Transition Tests
# ============================================================================


class ModerationReportTransitionTests(TestCase):
    """Comprehensive tests for ModerationReport FSM transitions."""

    def setUp(self):
        """Set up test fixtures."""
        self.user = User.objects.create_user(
            username="reporter", email="reporter@example.com", password="testpass123", role="USER"
        )
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.operator = Operator.objects.create(name="Test Operator", description="Test Description")
        self.content_type = ContentType.objects.get_for_model(Operator)

    def _create_report(self, status="PENDING"):
        """Helper to create a ModerationReport."""
        return ModerationReport.objects.create(
            report_type="CONTENT",
            status=status,
            priority="MEDIUM",
            reported_entity_type="company",
            reported_entity_id=self.operator.id,
            content_type=self.content_type,
            reason="Inaccurate information",
            description="The company information is incorrect",
            reported_by=self.user,
        )

    def test_pending_to_under_review_transition(self):
        """Test transition from PENDING to UNDER_REVIEW."""
        report = self._create_report()
        self.assertEqual(report.status, "PENDING")

        report.transition_to_under_review(user=self.moderator)
        report.assigned_moderator = self.moderator
        report.save()

        report.refresh_from_db()
        self.assertEqual(report.status, "UNDER_REVIEW")
        self.assertEqual(report.assigned_moderator, self.moderator)

    def test_under_review_to_resolved_transition(self):
        """Test transition from UNDER_REVIEW to RESOLVED."""
        report = self._create_report(status="UNDER_REVIEW")
        report.assigned_moderator = self.moderator
        report.save()

        report.transition_to_resolved(user=self.moderator)
        report.resolution_action = "Content updated"
        report.resolution_notes = "Fixed the incorrect information"
        report.resolved_at = timezone.now()
        report.save()

        report.refresh_from_db()
        self.assertEqual(report.status, "RESOLVED")
        self.assertIsNotNone(report.resolved_at)

    def test_under_review_to_dismissed_transition(self):
        """Test transition from UNDER_REVIEW to DISMISSED."""
        report = self._create_report(status="UNDER_REVIEW")
        report.assigned_moderator = self.moderator
        report.save()

        report.transition_to_dismissed(user=self.moderator)
        report.resolution_notes = "Report is not valid"
        report.resolved_at = timezone.now()
        report.save()

        report.refresh_from_db()
        self.assertEqual(report.status, "DISMISSED")

    def test_invalid_transition_from_resolved(self):
        """Test that transitions from RESOLVED state fail."""
        report = self._create_report(status="RESOLVED")

        with self.assertRaises(TransitionNotAllowed):
            report.transition_to_dismissed(user=self.moderator)

    def test_invalid_transition_from_dismissed(self):
        """Test that transitions from DISMISSED state fail."""
        report = self._create_report(status="DISMISSED")

        with self.assertRaises(TransitionNotAllowed):
            report.transition_to_resolved(user=self.moderator)


# ============================================================================
# ModerationQueue FSM Transition Tests
# ============================================================================


class ModerationQueueTransitionTests(TestCase):
    """Comprehensive tests for ModerationQueue FSM transitions."""

    def setUp(self):
        """Set up test fixtures."""
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )

    def _create_queue_item(self, status="PENDING"):
        """Helper to create a ModerationQueue item."""
        return ModerationQueue.objects.create(
            item_type="EDIT_SUBMISSION",
            status=status,
            priority="MEDIUM",
            title="Review edit submission",
            description="User submitted an edit that needs review",
            flagged_by=self.moderator,
        )

    def test_pending_to_in_progress_transition(self):
        """Test transition from PENDING to IN_PROGRESS."""
        item = self._create_queue_item()
        self.assertEqual(item.status, "PENDING")

        item.transition_to_in_progress(user=self.moderator)
        item.assigned_to = self.moderator
        item.assigned_at = timezone.now()
        item.save()

        item.refresh_from_db()
        self.assertEqual(item.status, "IN_PROGRESS")
        self.assertEqual(item.assigned_to, self.moderator)

    def test_in_progress_to_completed_transition(self):
        """Test transition from IN_PROGRESS to COMPLETED."""
        item = self._create_queue_item(status="IN_PROGRESS")
        item.assigned_to = self.moderator
        item.save()

        item.transition_to_completed(user=self.moderator)
        item.save()

        item.refresh_from_db()
        self.assertEqual(item.status, "COMPLETED")

    def test_in_progress_to_cancelled_transition(self):
        """Test transition from IN_PROGRESS to CANCELLED."""
        item = self._create_queue_item(status="IN_PROGRESS")
        item.assigned_to = self.moderator
        item.save()

        item.transition_to_cancelled(user=self.moderator)
        item.save()

        item.refresh_from_db()
        self.assertEqual(item.status, "CANCELLED")

    def test_pending_to_cancelled_transition(self):
        """Test transition from PENDING to CANCELLED."""
        item = self._create_queue_item()

        item.transition_to_cancelled(user=self.moderator)
        item.save()

        item.refresh_from_db()
        self.assertEqual(item.status, "CANCELLED")

    def test_invalid_transition_from_completed(self):
        """Test that transitions from COMPLETED state fail."""
        item = self._create_queue_item(status="COMPLETED")

        with self.assertRaises(TransitionNotAllowed):
            item.transition_to_in_progress(user=self.moderator)


# ============================================================================
# BulkOperation FSM Transition Tests
# ============================================================================


class BulkOperationTransitionTests(TestCase):
    """Comprehensive tests for BulkOperation FSM transitions."""

    def setUp(self):
        """Set up test fixtures."""
        self.admin = User.objects.create_user(
            username="admin", email="admin@example.com", password="testpass123", role="ADMIN"
        )

    def _create_bulk_operation(self, status="PENDING"):
        """Helper to create a BulkOperation."""
        return BulkOperation.objects.create(
            operation_type="BULK_UPDATE",
            status=status,
            priority="MEDIUM",
            description="Bulk update park statuses",
            parameters={"target": "parks", "action": "update_status"},
            created_by=self.admin,
            total_items=100,
        )

    def test_pending_to_running_transition(self):
        """Test transition from PENDING to RUNNING."""
        operation = self._create_bulk_operation()
        self.assertEqual(operation.status, "PENDING")

        operation.transition_to_running(user=self.admin)
        operation.started_at = timezone.now()
        operation.save()

        operation.refresh_from_db()
        self.assertEqual(operation.status, "RUNNING")
        self.assertIsNotNone(operation.started_at)

    def test_running_to_completed_transition(self):
        """Test transition from RUNNING to COMPLETED."""
        operation = self._create_bulk_operation(status="RUNNING")
        operation.started_at = timezone.now()
        operation.save()

        operation.transition_to_completed(user=self.admin)
        operation.completed_at = timezone.now()
        operation.processed_items = 100
        operation.save()

        operation.refresh_from_db()
        self.assertEqual(operation.status, "COMPLETED")
        self.assertIsNotNone(operation.completed_at)
        self.assertEqual(operation.processed_items, 100)

    def test_running_to_failed_transition(self):
        """Test transition from RUNNING to FAILED."""
        operation = self._create_bulk_operation(status="RUNNING")
        operation.started_at = timezone.now()
        operation.save()

        operation.transition_to_failed(user=self.admin)
        operation.completed_at = timezone.now()
        operation.results = {"error": "Database connection failed"}
        operation.failed_items = 50
        operation.save()

        operation.refresh_from_db()
        self.assertEqual(operation.status, "FAILED")
        self.assertEqual(operation.failed_items, 50)

    def test_pending_to_cancelled_transition(self):
        """Test transition from PENDING to CANCELLED."""
        operation = self._create_bulk_operation()

        operation.transition_to_cancelled(user=self.admin)
        operation.save()

        operation.refresh_from_db()
        self.assertEqual(operation.status, "CANCELLED")

    def test_running_to_cancelled_transition(self):
        """Test transition from RUNNING to CANCELLED when cancellable."""
        operation = self._create_bulk_operation(status="RUNNING")
        operation.can_cancel = True
        operation.save()

        operation.transition_to_cancelled(user=self.admin)
        operation.save()

        operation.refresh_from_db()
        self.assertEqual(operation.status, "CANCELLED")

    def test_invalid_transition_from_completed(self):
        """Test that transitions from COMPLETED state fail."""
        operation = self._create_bulk_operation(status="COMPLETED")

        with self.assertRaises(TransitionNotAllowed):
            operation.transition_to_running(user=self.admin)

    def test_invalid_transition_from_failed(self):
        """Test that transitions from FAILED state fail."""
        operation = self._create_bulk_operation(status="FAILED")

        with self.assertRaises(TransitionNotAllowed):
            operation.transition_to_completed(user=self.admin)

    def test_progress_percentage_calculation(self):
        """Test progress percentage property."""
        operation = self._create_bulk_operation()
        operation.total_items = 100
        operation.processed_items = 50

        self.assertEqual(operation.progress_percentage, 50.0)

        operation.processed_items = 0
        self.assertEqual(operation.progress_percentage, 0.0)

        operation.total_items = 0
        self.assertEqual(operation.progress_percentage, 0.0)


# ============================================================================
# FSM Transition Logging Tests
# ============================================================================


class TransitionLoggingTestCase(TestCase):
    """Test cases for FSM transition logging with django-fsm-log."""

    def setUp(self):
        """Set up test fixtures."""
        self.user = User.objects.create_user(
            username="testuser", email="test@example.com", password="testpass123", role="USER"
        )
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.operator = Operator.objects.create(name="Test Operator", description="Test Description")
        self.content_type = ContentType.objects.get_for_model(Operator)

    def test_transition_creates_log(self):
        """Test that transitions create StateLog entries."""
        from django_fsm_log.models import StateLog

        # Create a submission
        submission = EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status="PENDING",
            reason="Test reason",
        )

        # Must claim first (FSM requirement)
        submission.claim(user=self.moderator)
        submission.refresh_from_db()

        # Perform transition
        submission.transition_to_approved(user=self.moderator)
        submission.save()

        # Check log was created
        submission_ct = ContentType.objects.get_for_model(submission)
        log = StateLog.objects.filter(
            content_type=submission_ct, object_id=submission.id, state="APPROVED"
        ).first()

        self.assertIsNotNone(log, "StateLog entry should be created")
        self.assertEqual(log.state, "APPROVED")
        self.assertEqual(log.by, self.moderator)

    def test_multiple_transitions_logged(self):
        """Test that multiple transitions are all logged."""
        from django_fsm_log.models import StateLog

        submission = EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status="PENDING",
            reason="Test reason",
        )

        submission_ct = ContentType.objects.get_for_model(submission)

        # First claim (FSM requirement)
        submission.claim(user=self.moderator)
        submission.refresh_from_db()

        # First transition: CLAIMED -> ESCALATED
        submission.transition_to_escalated(user=self.moderator)
        submission.save()

        # Second transition: ESCALATED -> APPROVED
        submission.transition_to_approved(user=self.moderator)
        submission.save()

        # Check logs created (excluding the claim transition log)
        logs = StateLog.objects.filter(
            content_type=submission_ct, object_id=submission.id
        ).order_by("timestamp")

        # Should have at least 2 entries for ESCALATED and APPROVED
        self.assertGreaterEqual(logs.count(), 2, "Should have at least 2 log entries")
        states = [log.state for log in logs]
        self.assertIn("ESCALATED", states)
        self.assertIn("APPROVED", states)

    def test_history_endpoint_returns_logs(self):
        """Test history API endpoint returns transition logs."""
        from rest_framework.test import APIClient

        api_client = APIClient()
        api_client.force_authenticate(user=self.moderator)

        submission = EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status="PENDING",
            reason="Test reason",
        )

        # Must claim first (FSM requirement)
        submission.claim(user=self.moderator)
        submission.refresh_from_db()

        # Perform transition to create log
        submission.transition_to_approved(user=self.moderator)
        submission.save()

        # Note: This assumes EditSubmission has a history endpoint
        # Adjust URL pattern based on actual implementation
        response = api_client.get("/api/moderation/reports/all_history/")

        self.assertEqual(response.status_code, 200)

    def test_system_transitions_without_user(self):
        """Test that system transitions work without a user (admin/cron operations)."""
        from django_fsm_log.models import StateLog

        submission = EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status="PENDING",
            reason="Test reason",
        )

        # Must claim first (FSM requirement)
        submission.claim(user=self.moderator)
        submission.refresh_from_db()

        # Perform transition without user (simulating system/cron action)
        submission.transition_to_rejected(user=None)
        submission.save()

        # Check log was created even without user
        submission_ct = ContentType.objects.get_for_model(submission)
        log = StateLog.objects.filter(
            content_type=submission_ct, object_id=submission.id, state="REJECTED"
        ).first()

        self.assertIsNotNone(log)
        self.assertEqual(log.state, "REJECTED")
        self.assertIsNone(log.by, "System transitions should have no user")

    def test_transition_log_includes_description(self):
        """Test that transition logs can include descriptions."""
        from django_fsm_log.models import StateLog

        submission = EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status="PENDING",
            reason="Test reason",
        )

        # Must claim first (FSM requirement)
        submission.claim(user=self.moderator)
        submission.refresh_from_db()

        # Perform transition
        submission.transition_to_approved(user=self.moderator)
        submission.save()

        # Check log
        submission_ct = ContentType.objects.get_for_model(submission)
        log = StateLog.objects.filter(
            content_type=submission_ct, object_id=submission.id, state="APPROVED"
        ).first()

        self.assertIsNotNone(log)
        # Description field exists and can be used for audit trails
        self.assertTrue(hasattr(log, "description"))

    def test_log_ordering_by_timestamp(self):
        """Test that logs are properly ordered by timestamp."""

        from django_fsm_log.models import StateLog

        submission = EditSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            submission_type="EDIT",
            changes={"name": "Updated Name"},
            status="PENDING",
            reason="Test reason",
        )

        submission_ct = ContentType.objects.get_for_model(submission)

        # Must claim first (FSM requirement)
        submission.claim(user=self.moderator)
        submission.refresh_from_db()

        # Create multiple transitions
        submission.transition_to_escalated(user=self.moderator)
        submission.save()

        submission.transition_to_approved(user=self.moderator)
        submission.save()

        # Get logs ordered by timestamp
        logs = list(StateLog.objects.filter(content_type=submission_ct, object_id=submission.id).order_by("timestamp"))

        # Verify ordering - should have at least 2 logs (escalated and approved)
        self.assertGreaterEqual(len(logs), 2)
        # Verify timestamps are ordered
        for i in range(len(logs) - 1):
            self.assertTrue(logs[i].timestamp <= logs[i + 1].timestamp)


# ============================================================================
# ModerationAction Model Tests
# ============================================================================


class ModerationActionTests(TestCase):
    """Tests for ModerationAction model."""

    def setUp(self):
        """Set up test fixtures."""
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.target_user = User.objects.create_user(
            username="target", email="target@example.com", password="testpass123", role="USER"
        )

    def test_create_action_with_duration(self):
        """Test creating an action with duration sets expires_at."""
        action = ModerationAction.objects.create(
            action_type="TEMPORARY_BAN",
            reason="Spam",
            details="User was spamming the forums",
            duration_hours=24,
            moderator=self.moderator,
            target_user=self.target_user,
        )

        self.assertIsNotNone(action.expires_at)
        # expires_at should be approximately 24 hours from now
        time_diff = action.expires_at - timezone.now()
        self.assertAlmostEqual(time_diff.total_seconds(), 24 * 3600, delta=60)

    def test_create_action_without_duration(self):
        """Test creating an action without duration has no expires_at."""
        action = ModerationAction.objects.create(
            action_type="WARNING",
            reason="First offense",
            details="Warning issued for minor violation",
            moderator=self.moderator,
            target_user=self.target_user,
        )

        self.assertIsNone(action.expires_at)

    def test_action_is_active_by_default(self):
        """Test that new actions are active by default."""
        action = ModerationAction.objects.create(
            action_type="WARNING",
            reason="Test",
            details="Test warning",
            moderator=self.moderator,
            target_user=self.target_user,
        )

        self.assertTrue(action.is_active)


# ============================================================================
# PhotoSubmission FSM Transition Tests
# ============================================================================


class PhotoSubmissionTransitionTests(TestCase):
    """Comprehensive tests for PhotoSubmission FSM transitions.
    
    Note: All approve/reject/escalate transitions require CLAIMED state first.
    """

    def setUp(self):
        """Set up test fixtures."""
        from datetime import timedelta
        from django_cloudflareimages_toolkit.models import CloudflareImage
        
        self.user = User.objects.create_user(
            username="testuser", email="test@example.com", password="testpass123", role="USER"
        )
        self.moderator = User.objects.create_user(
            username="moderator", email="moderator@example.com", password="testpass123", role="MODERATOR"
        )
        self.admin = User.objects.create_user(
            username="admin", email="admin@example.com", password="testpass123", role="ADMIN"
        )
        self.operator = Operator.objects.create(
            name="Test Operator", description="Test Description", roles=["OPERATOR"]
        )
        self.content_type = ContentType.objects.get_for_model(Operator)
        
        # Create a real CloudflareImage for tests (required by FK constraint)
        self.mock_image = CloudflareImage.objects.create(
            cloudflare_id=f"test-cf-photo-{id(self)}",
            user=self.user,
            expires_at=timezone.now() + timedelta(days=365),
        )

    def _create_submission(self, status="PENDING"):
        """Helper to create a PhotoSubmission with proper CloudflareImage."""
        submission = PhotoSubmission.objects.create(
            user=self.user,
            content_type=self.content_type,
            object_id=self.operator.id,
            photo=self.mock_image,
            caption="Test Photo",
            status="PENDING",  # Always create as PENDING first
        )
        
        # For non-PENDING states, we need to transition through CLAIMED
        if status == "CLAIMED":
            submission.claim(user=self.moderator)
            submission.refresh_from_db()
        elif status in ("APPROVED", "REJECTED", "ESCALATED"):
            # First claim, then transition to target state
            submission.claim(user=self.moderator)
            if status == "APPROVED":
                submission.transition_to_approved(user=self.moderator)
            elif status == "REJECTED":
                submission.transition_to_rejected(user=self.moderator)
            elif status == "ESCALATED":
                submission.transition_to_escalated(user=self.moderator)
            submission.save()
            submission.refresh_from_db()
        
        return submission

    def test_pending_to_claimed_transition(self):
        """Test transition from PENDING to CLAIMED."""
        submission = self._create_submission()
        self.assertEqual(submission.status, "PENDING")

        submission.claim(user=self.moderator)
        submission.refresh_from_db()
        
        self.assertEqual(submission.status, "CLAIMED")
        self.assertEqual(submission.claimed_by, self.moderator)
        self.assertIsNotNone(submission.claimed_at)

    def test_claimed_to_approved_transition(self):
        """Test transition from CLAIMED to APPROVED (mandatory flow)."""
        submission = self._create_submission(status="CLAIMED")
        self.assertEqual(submission.status, "CLAIMED")

        submission.transition_to_approved(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "APPROVED")
        self.assertEqual(submission.handled_by, self.moderator)
        self.assertIsNotNone(submission.handled_at)

    def test_claimed_to_rejected_transition(self):
        """Test transition from CLAIMED to REJECTED (mandatory flow)."""
        submission = self._create_submission(status="CLAIMED")
        self.assertEqual(submission.status, "CLAIMED")

        submission.transition_to_rejected(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.notes = "Rejected: Image quality too low"
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "REJECTED")
        self.assertEqual(submission.handled_by, self.moderator)
        self.assertIn("Rejected", submission.notes)

    def test_claimed_to_escalated_transition(self):
        """Test transition from CLAIMED to ESCALATED (mandatory flow)."""
        submission = self._create_submission(status="CLAIMED")
        self.assertEqual(submission.status, "CLAIMED")

        submission.transition_to_escalated(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.notes = "Escalated: Copyright concerns"
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "ESCALATED")

    def test_escalated_to_approved_transition(self):
        """Test transition from ESCALATED to APPROVED."""
        submission = self._create_submission(status="ESCALATED")

        submission.transition_to_approved(user=self.admin)
        submission.handled_by = self.admin
        submission.handled_at = timezone.now()
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "APPROVED")
        self.assertEqual(submission.handled_by, self.admin)

    def test_escalated_to_rejected_transition(self):
        """Test transition from ESCALATED to REJECTED."""
        submission = self._create_submission(status="ESCALATED")

        submission.transition_to_rejected(user=self.admin)
        submission.handled_by = self.admin
        submission.handled_at = timezone.now()
        submission.notes = "Rejected by admin after review"
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.status, "REJECTED")

    def test_invalid_transition_from_approved(self):
        """Test that transitions from APPROVED state fail."""
        submission = self._create_submission(status="APPROVED")

        with self.assertRaises(TransitionNotAllowed):
            submission.transition_to_rejected(user=self.moderator)

    def test_invalid_transition_from_rejected(self):
        """Test that transitions from REJECTED state fail."""
        submission = self._create_submission(status="REJECTED")

        with self.assertRaises(TransitionNotAllowed):
            submission.transition_to_approved(user=self.moderator)


    def test_reject_wrapper_method(self):
        """Test the reject() wrapper method (requires CLAIMED state first)."""
        submission = self._create_submission(status="CLAIMED")

        submission.reject(self.moderator, notes="Not suitable")

        submission.refresh_from_db()
        self.assertEqual(submission.status, "REJECTED")
        self.assertIn("Not suitable", submission.notes)

    def test_escalate_wrapper_method(self):
        """Test the escalate() wrapper method (requires CLAIMED state first)."""
        submission = self._create_submission(status="CLAIMED")

        submission.escalate(self.moderator, notes="Needs admin review")

        submission.refresh_from_db()
        self.assertEqual(submission.status, "ESCALATED")
        self.assertIn("Needs admin review", submission.notes)

    def test_transition_creates_state_log(self):
        """Test that transitions create StateLog entries."""
        from django_fsm_log.models import StateLog

        submission = self._create_submission(status="CLAIMED")

        # Perform transition
        submission.transition_to_approved(user=self.moderator)
        submission.save()

        # Check log was created
        submission_ct = ContentType.objects.get_for_model(submission)
        log = StateLog.objects.filter(content_type=submission_ct, object_id=submission.id).first()

        self.assertIsNotNone(log, "StateLog entry should be created")
        self.assertEqual(log.state, "APPROVED")
        self.assertEqual(log.by, self.moderator)

    def test_multiple_transitions_logged(self):
        """Test that multiple transitions are all logged."""
        from django_fsm_log.models import StateLog

        submission = self._create_submission(status="CLAIMED")
        submission_ct = ContentType.objects.get_for_model(submission)

        # First transition: CLAIMED -> ESCALATED
        submission.transition_to_escalated(user=self.moderator)
        submission.save()

        # Second transition: ESCALATED -> APPROVED
        submission.transition_to_approved(user=self.admin)
        submission.save()

        # Check multiple logs created
        logs = StateLog.objects.filter(content_type=submission_ct, object_id=submission.id).order_by("timestamp")

        self.assertEqual(logs.count(), 2, "Should have 2 log entries")
        self.assertEqual(logs[0].state, "ESCALATED")
        self.assertEqual(logs[1].state, "APPROVED")

    def test_handled_by_and_handled_at_updated(self):
        """Test that handled_by and handled_at are properly updated."""
        submission = self._create_submission(status="CLAIMED")

        before_time = timezone.now()
        submission.transition_to_approved(user=self.moderator)
        submission.handled_by = self.moderator
        submission.handled_at = timezone.now()
        submission.save()
        after_time = timezone.now()

        submission.refresh_from_db()
        self.assertEqual(submission.handled_by, self.moderator)
        self.assertIsNotNone(submission.handled_at)
        self.assertTrue(before_time <= submission.handled_at <= after_time)

    def test_notes_field_updated_on_rejection(self):
        """Test that notes field is updated with rejection reason."""
        submission = self._create_submission(status="CLAIMED")
        rejection_reason = "Image contains watermarks"

        submission.transition_to_rejected(user=self.moderator)
        submission.notes = rejection_reason
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.notes, rejection_reason)

    def test_notes_field_updated_on_escalation(self):
        """Test that notes field is updated with escalation reason."""
        submission = self._create_submission(status="CLAIMED")
        escalation_reason = "Potentially copyrighted content"

        submission.transition_to_escalated(user=self.moderator)
        submission.notes = escalation_reason
        submission.save()

        submission.refresh_from_db()
        self.assertEqual(submission.notes, escalation_reason)

