"""Unit tests for DeadLetterQueue.

Tests cover DLQ initialization, enqueue with metadata enrichment, bounded queue
behavior, persistence, inspection API, and thread safety as specified in Story 3.5.
"""

import json
import os
import stat
import tempfile
import threading
import time
from datetime import datetime
from unittest.mock import Mock, patch

import pytest

from mca_sdk.resilience.dead_letter_queue import DeadLetterQueue
from mca_sdk.utils.exceptions import BufferingError


class TestDeadLetterQueueInitialization:
    """Test DeadLetterQueue initialization and validation."""

    def test_default_initialization(self):
        """Test DLQ initializes with default values."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(persist_path=persist_path)

            assert dlq._max_size == 1000
            assert dlq._persist_path == persist_path
            assert dlq.size() == 0
            assert dlq.is_empty()
            assert not dlq.is_full()

    def test_custom_initialization(self):
        """Test DLQ with custom values."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "custom_dlq.json")
            dlq = DeadLetterQueue(max_size=500, persist_path=persist_path)

            assert dlq._max_size == 500
            assert dlq._persist_path == persist_path

    def test_invalid_max_size(self):
        """Test initialization fails with invalid max_size."""
        with pytest.raises(BufferingError, match="max_size must be positive"):
            DeadLetterQueue(max_size=0)

        with pytest.raises(BufferingError, match="max_size must be positive"):
            DeadLetterQueue(max_size=-1)

    def test_path_expansion(self):
        """Test persist_path expands user home directory."""
        dlq = DeadLetterQueue(persist_path="~/test_dlq.json")
        assert "~" not in dlq._persist_path
        assert os.path.expanduser("~") in dlq._persist_path


class TestDeadLetterQueueEnqueue:
    """Test DLQ enqueue with metadata enrichment (AC#1, AC#2)."""

    def test_enqueue_adds_metadata(self):
        """Test DLQ enriches items with failure metadata."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            error = ValueError("Test error")
            item = {"metric": "test", "value": 1}

            dlq.enqueue(item=item, error=error, retry_count=3)

            assert dlq.size() == 1
            items = dlq.inspect(limit=1)
            assert len(items) == 1

            dlq_item = items[0]
            assert dlq_item["error_type"] == "ValueError"
            assert dlq_item["error"] == "Test error"
            assert dlq_item["retry_count"] == 3
            assert dlq_item["item"] == item
            assert "timestamp" in dlq_item

            # Validate timestamp format (ISO 8601)
            timestamp = datetime.fromisoformat(dlq_item["timestamp"])
            assert isinstance(timestamp, datetime)

    def test_enqueue_multiple_items(self):
        """Test DLQ handles multiple items."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            for i in range(5):
                dlq.enqueue(
                    item={"metric": f"test_{i}", "value": i},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            assert dlq.size() == 5
            items = dlq.inspect(limit=10)
            assert len(items) == 5

            # Verify items are in FIFO order (oldest first)
            for i, item in enumerate(items):
                assert item["retry_count"] == i + 1

    def test_enqueue_different_error_types(self):
        """Test DLQ captures different exception types."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            errors = [
                ValueError("Value error"),
                ConnectionError("Connection error"),
                TimeoutError("Timeout error"),
                RuntimeError("Runtime error"),
            ]

            for error in errors:
                dlq.enqueue(item={"test": "data"}, error=error, retry_count=3)

            items = dlq.inspect(limit=10)
            error_types = [item["error_type"] for item in items]

            assert "ValueError" in error_types
            assert "ConnectionError" in error_types
            assert "TimeoutError" in error_types
            assert "RuntimeError" in error_types


class TestDeadLetterQueueBoundedBehavior:
    """Test DLQ bounded queue with size limit enforcement (AC#3)."""

    def test_bounded_queue_evicts_oldest(self):
        """Test DLQ evicts oldest items when full."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=3, persist_path=persist_path)

            # Add 5 items to a queue with max_size=3
            for i in range(5):
                dlq.enqueue(
                    item={"metric": f"test_{i}", "value": i},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            # Should only have 3 items (oldest 2 evicted)
            assert dlq.size() == 3
            assert dlq.is_full()

            # Verify we have items 2, 3, 4 (items 0, 1 were evicted)
            items = dlq.inspect(limit=10)
            retry_counts = [item["retry_count"] for item in items]
            assert retry_counts == [3, 4, 5]

    def test_bounded_queue_metrics(self):
        """Test DLQ tracks eviction metrics."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=3, persist_path=persist_path)

            # Add 5 items to trigger evictions
            for i in range(5):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            stats = dlq.stats()
            assert stats["items_enqueued"] == 5
            assert stats["items_evicted"] == 2  # Items 0 and 1 were evicted
            assert stats["size"] == 3
            assert stats["utilization"] == 100.0

    def test_is_full_detection(self):
        """Test DLQ correctly detects full state."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=2, persist_path=persist_path)

            assert not dlq.is_full()

            dlq.enqueue(item={"test": 1}, error=Exception("Error 1"), retry_count=1)
            assert not dlq.is_full()

            dlq.enqueue(item={"test": 2}, error=Exception("Error 2"), retry_count=2)
            assert dlq.is_full()


class TestDeadLetterQueuePersistence:
    """Test DLQ persistence and recovery."""

    def test_persistence_on_enqueue(self):
        """Test DLQ persists to disk on enqueue."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            dlq.enqueue(
                item={"metric": "test", "value": 1},
                error=Exception("Test error"),
                retry_count=3,
            )

            # Verify file was created
            assert os.path.exists(persist_path)

            # Verify file contains valid JSON data
            with open(persist_path, "r", encoding="utf-8") as f:
                data = json.load(f)
                assert isinstance(data, list)
                assert len(data) == 1

    def test_load_from_disk_on_startup(self):
        """Test DLQ loads persisted items on startup."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")

            # Create first DLQ and add items
            dlq1 = DeadLetterQueue(max_size=10, persist_path=persist_path)
            dlq1.enqueue(
                item={"metric": "test_1", "value": 1},
                error=Exception("Error 1"),
                retry_count=1,
            )
            dlq1.enqueue(
                item={"metric": "test_2", "value": 2},
                error=Exception("Error 2"),
                retry_count=2,
            )

            # Create second DLQ with same persist_path
            dlq2 = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Should load items from disk
            assert dlq2.size() == 2
            items = dlq2.inspect(limit=10)
            assert len(items) == 2
            assert items[0]["retry_count"] == 1
            assert items[1]["retry_count"] == 2

            # Verify recovery metrics
            stats = dlq2.stats()
            assert stats["items_recovered_on_startup"] == 2

    def test_load_respects_max_size(self):
        """Test DLQ respects max_size when loading from disk."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")

            # Create DLQ with large max_size and add many items
            dlq1 = DeadLetterQueue(max_size=100, persist_path=persist_path)
            for i in range(50):
                dlq1.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            # Create new DLQ with smaller max_size
            dlq2 = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Should only load last 10 items
            assert dlq2.size() == 10
            items = dlq2.inspect(limit=20)
            assert len(items) == 10

            # Should have items 40-49 (last 10)
            retry_counts = [item["retry_count"] for item in items]
            assert retry_counts == list(range(41, 51))

    def test_corrupted_file_handling(self):
        """Test DLQ handles corrupted persistence file gracefully."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")

            # Create corrupted file
            os.makedirs(os.path.dirname(persist_path) or ".", exist_ok=True)
            with open(persist_path, "wb") as f:
                f.write(b"corrupted data")

            # Should start with empty DLQ (not crash)
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)
            assert dlq.size() == 0

    def test_invalid_data_type_handling(self):
        """Test DLQ handles invalid data type in persistence file."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")

            # Create file with invalid data type (dict instead of list)
            os.makedirs(os.path.dirname(persist_path) or ".", exist_ok=True)
            with open(persist_path, "w", encoding="utf-8") as f:
                json.dump({"invalid": "data"}, f)

            # Should start with empty DLQ
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)
            assert dlq.size() == 0

    def test_persistence_metrics(self):
        """Test DLQ tracks persistence metrics."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Add items to trigger persistence
            for i in range(3):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            stats = dlq.stats()
            assert stats["persist_success_count"] >= 3  # At least 3 successful writes

    def test_clear_persists_empty_state(self):
        """Test clear() persists empty state to disk."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")

            # Create DLQ with items
            dlq1 = DeadLetterQueue(max_size=10, persist_path=persist_path)
            dlq1.enqueue(item={"test": 1}, error=Exception("Error"), retry_count=1)
            dlq1.clear()

            # Create new DLQ - should load empty state
            dlq2 = DeadLetterQueue(max_size=10, persist_path=persist_path)
            assert dlq2.size() == 0


class TestDeadLetterQueueInspection:
    """Test DLQ inspection API for operational visibility."""

    def test_inspect_returns_recent_items(self):
        """Test inspect() returns most recent items."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Add 5 items
            for i in range(5):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            # Inspect last 3 items
            items = dlq.inspect(limit=3)
            assert len(items) == 3

            # Should have items 2, 3, 4 (most recent)
            retry_counts = [item["retry_count"] for item in items]
            assert retry_counts == [3, 4, 5]

    def test_inspect_limit_exceeds_size(self):
        """Test inspect() when limit exceeds DLQ size."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Add 3 items
            for i in range(3):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            # Request more items than available
            items = dlq.inspect(limit=10)
            assert len(items) == 3

    def test_inspect_empty_dlq(self):
        """Test inspect() on empty DLQ."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            items = dlq.inspect(limit=10)
            assert items == []

    def test_inspect_default_limit(self):
        """Test inspect() uses default limit of 10."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=20, persist_path=persist_path)

            # Add 15 items
            for i in range(15):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            # Default limit should be 10
            items = dlq.inspect()
            assert len(items) == 10


class TestDeadLetterQueueOperations:
    """Test DLQ basic operations."""

    def test_size(self):
        """Test size() returns correct count."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            assert dlq.size() == 0

            dlq.enqueue(item={"test": 1}, error=Exception("Error"), retry_count=1)
            assert dlq.size() == 1

            dlq.enqueue(item={"test": 2}, error=Exception("Error"), retry_count=2)
            assert dlq.size() == 2

    def test_is_empty(self):
        """Test is_empty() detection."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            assert dlq.is_empty()

            dlq.enqueue(item={"test": 1}, error=Exception("Error"), retry_count=1)
            assert not dlq.is_empty()

            dlq.clear()
            assert dlq.is_empty()

    def test_clear(self):
        """Test clear() removes all items."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Add items
            for i in range(5):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            assert dlq.size() == 5

            # Clear
            removed = dlq.clear()
            assert removed == 5
            assert dlq.size() == 0
            assert dlq.is_empty()

    def test_stats(self):
        """Test stats() returns comprehensive metrics."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Add items
            for i in range(3):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            stats = dlq.stats()

            assert stats["size"] == 3
            assert stats["max_size"] == 10
            assert stats["is_full"] is False
            assert stats["is_empty"] is False
            assert stats["utilization"] == 30.0
            assert stats["items_enqueued"] == 3
            assert stats["items_evicted"] == 0
            assert "persist_success_count" in stats
            assert "persist_failure_count" in stats
            assert "items_recovered_on_startup" in stats


class TestDeadLetterQueueThreadSafety:
    """Test DLQ thread safety."""

    def test_concurrent_enqueue(self):
        """Test concurrent enqueue operations are thread-safe."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=1000, persist_path=persist_path)

            num_threads = 10
            items_per_thread = 50
            threads = []

            def enqueue_items(thread_id):
                for i in range(items_per_thread):
                    dlq.enqueue(
                        item={"thread": thread_id, "item": i},
                        error=Exception(f"Error {thread_id}_{i}"),
                        retry_count=i + 1,
                    )

            # Start threads
            for thread_id in range(num_threads):
                thread = threading.Thread(target=enqueue_items, args=(thread_id,))
                threads.append(thread)
                thread.start()

            # Wait for all threads
            for thread in threads:
                thread.join()

            # Verify all items were added
            assert dlq.size() == num_threads * items_per_thread

    def test_concurrent_inspect(self):
        """Test concurrent inspect operations are thread-safe."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=100, persist_path=persist_path)

            # Add some items
            for i in range(50):
                dlq.enqueue(
                    item={"metric": f"test_{i}"},
                    error=Exception(f"Error {i}"),
                    retry_count=i + 1,
                )

            num_threads = 10
            threads = []
            results = []

            def inspect_items():
                items = dlq.inspect(limit=10)
                results.append(len(items))

            # Start threads
            for _ in range(num_threads):
                thread = threading.Thread(target=inspect_items)
                threads.append(thread)
                thread.start()

            # Wait for all threads
            for thread in threads:
                thread.join()

            # All threads should see same number of items
            assert all(count == 10 for count in results)

    def test_concurrent_mixed_operations(self):
        """Test concurrent mixed operations (enqueue, inspect, stats)."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=500, persist_path=persist_path)

            num_threads = 5
            operations_per_thread = 20
            threads = []

            def mixed_operations(thread_id):
                for i in range(operations_per_thread):
                    # Enqueue
                    dlq.enqueue(
                        item={"thread": thread_id, "item": i},
                        error=Exception(f"Error {thread_id}_{i}"),
                        retry_count=i + 1,
                    )

                    # Inspect
                    dlq.inspect(limit=5)

                    # Stats
                    dlq.stats()

                    # Size
                    dlq.size()

            # Start threads
            for thread_id in range(num_threads):
                thread = threading.Thread(target=mixed_operations, args=(thread_id,))
                threads.append(thread)
                thread.start()

            # Wait for all threads
            for thread in threads:
                thread.join()

            # Verify final state is consistent
            assert dlq.size() == num_threads * operations_per_thread
            stats = dlq.stats()
            assert stats["items_enqueued"] == num_threads * operations_per_thread


class TestDeadLetterQueueErrorHandling:
    """Test DLQ error handling."""

    def test_disk_write_failure_handling(self):
        """Test DLQ handles disk write failures gracefully."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "readonly", "dlq.json")

            # Create readonly directory
            readonly_dir = os.path.join(tmpdir, "readonly")
            os.makedirs(readonly_dir)

            # Make directory readonly (Unix-like systems)
            if os.name != "nt":
                os.chmod(readonly_dir, 0o444)

            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Enqueue should not crash even if disk write fails
            try:
                dlq.enqueue(
                    item={"test": 1},
                    error=Exception("Test error"),
                    retry_count=1,
                )
                # Item should still be in memory
                assert dlq.size() == 1
            finally:
                # Cleanup: restore permissions
                if os.name != "nt":
                    os.chmod(readonly_dir, 0o755)

    def test_missing_parent_directory_creation(self):
        """Test DLQ creates parent directories if they don't exist."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "nested", "dir", "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            dlq.enqueue(
                item={"test": 1},
                error=Exception("Test error"),
                retry_count=1,
            )

            # Parent directories should be created
            assert os.path.exists(os.path.dirname(persist_path))
            assert os.path.exists(persist_path)


class TestDeadLetterQueueHIPAACompliance:
    """Test DLQ HIPAA compliance (logging error types only)."""

    def test_logging_excludes_error_messages(self):
        """Test DLQ logs error types only, not messages (HIPAA)."""
        with tempfile.TemporaryDirectory() as tmpdir:
            persist_path = os.path.join(tmpdir, "dlq.json")
            dlq = DeadLetterQueue(max_size=10, persist_path=persist_path)

            # Create error with PHI-like message
            error = ValueError("Patient SSN: 123-45-6789")

            with patch("mca_sdk.resilience.dead_letter_queue.logger") as mock_logger:
                dlq.enqueue(
                    item={"test": 1},
                    error=error,
                    retry_count=3,
                )

                # Verify logger.error was called
                assert mock_logger.error.called

                # Get the log call
                call_args = mock_logger.error.call_args

                # Verify error message is NOT in the log message
                log_message = call_args[0][0]
                assert "123-45-6789" not in log_message

                # Verify error_type IS in the extra data
                extra = call_args[1]["extra"]
                assert extra["error_type"] == "ValueError"


class TestDeadLetterQueueSecurityFixes:
    """Tests for adversarial review security fixes."""

    def test_require_encryption_fails_without_key(self, tmp_path):
        """Test require_encryption=True fails fast when no key provided."""
        dlq_path = str(tmp_path / "dlq_secure.json")

        with pytest.raises(BufferingError, match="Encryption required but no key provided"):
            DeadLetterQueue(max_size=10, persist_path=dlq_path, require_encryption=True)

    def test_require_encryption_fails_on_encryption_init_error(self, tmp_path):
        """Test require_encryption=True fails fast when encryption init fails."""
        dlq_path = str(tmp_path / "dlq_secure.json")

        # Use invalid key to trigger encryption manager failure
        invalid_key = b"short"

        with pytest.raises(BufferingError, match="Encryption required but failed to initialize"):
            DeadLetterQueue(
                max_size=10,
                persist_path=dlq_path,
                encryption_key=invalid_key,
                require_encryption=True,
            )

    def test_directory_permissions_restricted(self, tmp_path):
        """Test DLQ directory created with secure permissions (0700)."""
        # Use non-existent directory to test DLQ directory creation
        base_dir = tmp_path / "base_test_dir"
        dlq_path = str(base_dir / "new_secure_dir" / "dlq.json")

        # Directory should not exist yet
        assert not (base_dir / "new_secure_dir").exists()

        dlq = DeadLetterQueue(max_size=10, persist_path=dlq_path)
        dlq.enqueue(item={"test": "data"}, error=Exception("test"), retry_count=1)

        # Check DLQ-created directory permissions
        dir_path = base_dir / "new_secure_dir"
        assert dir_path.exists()

        # Get actual permissions
        stat_info = os.stat(dir_path)
        perms = stat.S_IMODE(stat_info.st_mode)

        # Should be 0700 (owner rwx only)
        assert perms == 0o700, f"Expected 0o700, got {oct(perms)}"

    def test_consecutive_persistence_failures_raise_error(self, tmp_path, mocker):
        """Test DLQ raises BufferingError after max consecutive persistence failures."""
        dlq_path = str(tmp_path / "readonly" / "dlq.json")

        # Mock tempfile.mkstemp to raise OSError to simulate disk failure/permission issue
        import errno

        mocker.patch("tempfile.mkstemp", side_effect=OSError(errno.EACCES, "Permission denied"))

        dlq = DeadLetterQueue(max_size=10, persist_path=dlq_path)

        try:
            # First 4 failures should be silent
            for i in range(4):
                dlq.enqueue(item={"test": i}, error=Exception(f"test{i}"), retry_count=1)

            # 5th consecutive failure should raise
            with pytest.raises(BufferingError, match="DLQ persistence failed 5 consecutive times"):
                dlq.enqueue(item={"test": 5}, error=Exception("test5"), retry_count=1)

        finally:
            # Cleanup: restore permissions
            os.chmod(tmp_path / "readonly", 0o700)
