"""
Tests for GateWay Job Queue Module.
"""

import asyncio
import time

import pytest

from verlex_server.gateway.core.job_queue import (
    DuplicateMessageError,
    JobPriority,
    JobQueueManager,
    MemoryJobQueue,
    QueueMessage,
    QueueStats,
    get_queue_manager,
)


class TestJobPriority:
    """Tests for JobPriority enum."""

    def test_priority_values(self):
        """Test priority numeric values."""
        assert JobPriority.CRITICAL.value < JobPriority.HIGH.value
        assert JobPriority.HIGH.value < JobPriority.NORMAL.value
        assert JobPriority.NORMAL.value < JobPriority.LOW.value

    def test_priority_comparison(self):
        """Test priority comparison works correctly."""
        # Lower value = higher priority
        assert JobPriority.CRITICAL.value < JobPriority.LOW.value


class TestQueueMessage:
    """Tests for QueueMessage dataclass."""

    def test_message_creation(self):
        """Test creating a queue message."""
        message = QueueMessage(
            message_id="msg_1",
            job_id="job_1",
            user_id="user_1",
            priority=JobPriority.HIGH,
            payload={"function": "test"},
        )

        assert message.message_id == "msg_1"
        assert message.job_id == "job_1"
        assert message.user_id == "user_1"
        assert message.priority == JobPriority.HIGH
        assert message.created_at > 0

    def test_message_to_dict(self):
        """Test message serialization."""
        message = QueueMessage(
            message_id="msg_2",
            job_id="job_2",
            user_id="user_2",
            payload={"data": "test"},
        )

        data = message.to_dict()
        assert data["message_id"] == "msg_2"
        assert data["payload"]["data"] == "test"

    def test_message_priority_ordering(self):
        """Test message ordering by priority."""
        msg_low = QueueMessage(
            message_id="msg_low",
            job_id="job_low",
            user_id="user_1",
            priority=JobPriority.LOW,
        )
        msg_high = QueueMessage(
            message_id="msg_high",
            job_id="job_high",
            user_id="user_1",
            priority=JobPriority.HIGH,
        )

        # Higher priority (lower value) should sort first
        assert msg_high < msg_low

    def test_message_visibility(self):
        """Test message visibility check."""
        message = QueueMessage(
            message_id="msg_1",
            job_id="job_1",
            user_id="user_1",
            visible_after=time.time() - 1,  # Past
        )
        assert message.is_visible()

        message.visible_after = time.time() + 100  # Future
        assert not message.is_visible()


class TestMemoryJobQueue:
    """Tests for MemoryJobQueue."""

    @pytest.fixture
    def queue(self):
        return MemoryJobQueue(name="test_queue")

    @pytest.mark.asyncio
    async def test_send_receive(self, queue):
        """Test basic send and receive."""
        await queue.send(
            job_id="job_1",
            user_id="user_1",
            payload={"data": "test"},
        )

        messages = await queue.receive(max_messages=1)

        assert len(messages) == 1
        assert messages[0].job_id == "job_1"
        assert messages[0].payload["data"] == "test"

    @pytest.mark.asyncio
    async def test_priority_ordering(self, queue):
        """Test messages are received by priority."""
        await queue.send("job_low", "user_1", {}, priority=JobPriority.LOW)
        await queue.send("job_high", "user_1", {}, priority=JobPriority.HIGH)
        await queue.send("job_critical", "user_1", {}, priority=JobPriority.CRITICAL)

        msgs = await queue.receive(max_messages=3)

        assert len(msgs) == 3
        assert msgs[0].job_id == "job_critical"
        assert msgs[1].job_id == "job_high"
        assert msgs[2].job_id == "job_low"

    @pytest.mark.asyncio
    async def test_fifo_within_priority(self, queue):
        """Test FIFO ordering within same priority."""
        await queue.send("job_1", "user_1", {}, priority=JobPriority.NORMAL)
        await asyncio.sleep(0.01)
        await queue.send("job_2", "user_1", {}, priority=JobPriority.NORMAL)
        await asyncio.sleep(0.01)
        await queue.send("job_3", "user_1", {}, priority=JobPriority.NORMAL)

        msgs = await queue.receive(max_messages=3)

        assert msgs[0].job_id == "job_1"
        assert msgs[1].job_id == "job_2"
        assert msgs[2].job_id == "job_3"

    @pytest.mark.asyncio
    async def test_visibility_timeout(self, queue):
        """Test visibility timeout behavior."""
        await queue.send("job_1", "user_1", {})

        # First receive with short timeout
        msgs1 = await queue.receive(max_messages=1, visibility_timeout=1)
        assert len(msgs1) == 1

        # Immediately try again - should be empty (in flight)
        msgs2 = await queue.receive(max_messages=1)
        assert len(msgs2) == 0

        # Simulate worker crash/failure - mark job as no longer executing
        # This allows visibility timeout re-queue (otherwise the active
        # execution tracking would prevent re-queue)
        queue.mark_job_completed("job_1")

        # Wait for visibility timeout
        await asyncio.sleep(1.1)

        # Should be available again
        msgs3 = await queue.receive(max_messages=1)
        assert len(msgs3) == 1
        assert msgs3[0].job_id == "job_1"

    @pytest.mark.asyncio
    async def test_delete(self, queue):
        """Test message deletion (acknowledge)."""
        await queue.send("job_1", "user_1", {})

        messages = await queue.receive(max_messages=1)
        assert len(messages) == 1

        result = await queue.delete(messages[0].message_id)
        assert result is True

        # Should not return after delete
        msgs = await queue.receive(max_messages=1)
        assert len(msgs) == 0

    @pytest.mark.asyncio
    async def test_extend_visibility(self, queue):
        """Test extending visibility timeout."""
        await queue.send("job_1", "user_1", {})
        messages = await queue.receive(max_messages=1, visibility_timeout=1)

        # Extend visibility
        result = await queue.extend_visibility(messages[0].message_id, 10)
        assert result is True

        # Wait past original timeout
        await asyncio.sleep(1.1)

        # Should still be invisible
        msgs = await queue.receive(max_messages=1)
        assert len(msgs) == 0

    @pytest.mark.asyncio
    async def test_purge(self, queue):
        """Test purging queue."""
        await queue.send("job_1", "user_1", {})
        await queue.send("job_2", "user_1", {})

        count = await queue.purge()
        assert count >= 0

        stats = await queue.get_stats()
        assert stats.visible_messages == 0

    @pytest.mark.asyncio
    async def test_deduplication(self, queue):
        """Test message deduplication."""
        await queue.send(
            "job_1",
            "user_1",
            {},
            dedup_id="dedup_1",
        )

        # Same dedup ID should raise error
        with pytest.raises(DuplicateMessageError):
            await queue.send(
                "job_1_duplicate",
                "user_1",
                {},
                dedup_id="dedup_1",
            )

    @pytest.mark.asyncio
    async def test_delayed_delivery(self, queue):
        """Test delayed message delivery."""
        await queue.send(
            "job_1",
            "user_1",
            {},
            delay_seconds=1,
        )

        # Should not be available immediately
        msgs1 = await queue.receive(max_messages=1)
        assert len(msgs1) == 0

        # Wait for delay
        await asyncio.sleep(1.1)

        # Now should be available
        msgs2 = await queue.receive(max_messages=1)
        assert len(msgs2) == 1
        assert msgs2[0].job_id == "job_1"

    @pytest.mark.asyncio
    async def test_empty_queue_receive(self, queue):
        """Test receiving from empty queue."""
        messages = await queue.receive(max_messages=1)
        assert len(messages) == 0

    @pytest.mark.asyncio
    async def test_get_stats(self, queue):
        """Test queue statistics."""
        await queue.send("job_1", "user_1", {})
        await queue.send("job_2", "user_1", {})

        stats = await queue.get_stats()

        assert isinstance(stats, QueueStats)
        assert stats.name == "test_queue"


class TestJobQueueManager:
    """Tests for JobQueueManager."""

    @pytest.fixture
    def manager(self):
        return JobQueueManager()

    def test_get_queue(self, manager):
        """Test getting a queue by name."""
        queue = manager.get_queue("default")
        assert queue is not None

    def test_add_queue(self, manager):
        """Test adding a named queue."""
        new_queue = MemoryJobQueue("custom")
        manager.add_queue("custom", new_queue)

        retrieved = manager.get_queue("custom")
        assert retrieved is new_queue

    def test_select_queue_by_priority(self, manager):
        """Test queue selection by priority."""
        # High priority should get priority queue
        queue = manager.select_queue(priority=JobPriority.HIGH)
        assert queue == manager.priority_queue

        # Normal priority should get default
        queue = manager.select_queue(priority=JobPriority.NORMAL)
        assert queue == manager.default_queue

    @pytest.mark.asyncio
    async def test_send_to_appropriate_queue(self, manager):
        """Test sending routes to appropriate queue."""
        msg = await manager.send(
            job_id="job_1",
            user_id="user_1",
            payload={},
            priority=JobPriority.CRITICAL,
        )

        assert msg is not None
        assert msg.job_id == "job_1"

    @pytest.mark.asyncio
    async def test_receive_any(self, manager):
        """Test receiving from any queue."""
        await manager.send(
            job_id="job_1",
            user_id="user_1",
            payload={},
        )

        results = await manager.receive_any(max_messages=1)

        assert len(results) >= 0  # May be 0 if already processed


class TestQueueManagerSingleton:
    """Tests for queue manager singleton."""

    def test_get_queue_manager(self):
        """Test singleton pattern."""
        manager1 = get_queue_manager()
        manager2 = get_queue_manager()

        assert manager1 is manager2


class TestConcurrentQueueOperations:
    """Tests for concurrent queue operations."""

    @pytest.mark.asyncio
    async def test_concurrent_send(self):
        """Test concurrent send operations."""
        queue = MemoryJobQueue("concurrent_queue")

        async def send_job(job_id):
            await queue.send(job_id, "user_1", {"id": job_id})

        # Send many jobs concurrently
        await asyncio.gather(*[
            send_job(f"job_{i}")
            for i in range(100)
        ])

        stats = await queue.get_stats()
        assert stats.visible_messages == 100
