"""
Tests for web/queue/manager.py

Tests cover:
- QueueManager.add_to_queue
- QueueManager.get_queue_position
- QueueManager.remove_from_queue
- QueueManager.get_user_queue
"""

from contextlib import contextmanager
from unittest.mock import MagicMock, patch

import pytest

from local_deep_research.database.session_context import DatabaseSessionError


def _mock_user_db_session(mock_session):
    """Create a mock context manager that yields the given session."""

    @contextmanager
    def _ctx(username=None, password=None):
        yield mock_session

    return _ctx


def _mock_user_db_session_error():
    """Create a mock context manager that raises DatabaseSessionError."""

    @contextmanager
    def _ctx(username=None, password=None):
        raise DatabaseSessionError("No database connection")
        yield  # noqa: unreachable — needed for generator syntax

    return _ctx


class TestQueueManagerAddToQueue:
    """Tests for QueueManager.add_to_queue method."""

    def test_add_to_queue_success(self):
        """Test successful addition to queue."""
        mock_session = MagicMock()

        # Mock query chain for max position
        mock_query = MagicMock()
        mock_query.filter_by.return_value.scalar.return_value = 0
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            with patch(
                "local_deep_research.web.queue.manager.queue_processor"
            ) as mock_processor:
                from local_deep_research.web.queue.manager import (
                    QueueManager,
                )

                result = QueueManager.add_to_queue(
                    username="testuser",
                    research_id="test-id-123",
                    query="test query",
                    mode="detailed",
                    settings={"key": "value"},
                )

                assert result == 1
                mock_session.add.assert_called_once()
                mock_session.commit.assert_called_once()
                mock_processor.notify_research_queued.assert_called_once_with(
                    "testuser", "test-id-123"
                )

    def test_add_to_queue_no_connection(self):
        """Test add_to_queue raises DatabaseSessionError when no connection."""
        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session_error(),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            with pytest.raises(DatabaseSessionError):
                QueueManager.add_to_queue(
                    username="testuser",
                    research_id="test-id-123",
                    query="test query",
                    mode="detailed",
                    settings={},
                )

    def test_add_to_queue_with_existing_items(self):
        """Test add_to_queue with existing items in queue."""
        mock_session = MagicMock()

        # Mock query chain for max position - already has 3 items
        mock_query = MagicMock()
        mock_query.filter_by.return_value.scalar.return_value = 3
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            with patch("local_deep_research.web.queue.manager.queue_processor"):
                from local_deep_research.web.queue.manager import (
                    QueueManager,
                )

                result = QueueManager.add_to_queue(
                    username="testuser",
                    research_id="test-id-456",
                    query="another query",
                    mode="quick",
                    settings={},
                )

                # Position should be 4 (max + 1)
                assert result == 4


class TestQueueManagerGetQueuePosition:
    """Tests for QueueManager.get_queue_position method."""

    def test_get_queue_position_success(self):
        """Test getting queue position successfully."""
        mock_session = MagicMock()

        # Mock queued research item
        mock_queued = MagicMock()
        mock_queued.position = 3

        # Setup query chain
        mock_query = MagicMock()
        mock_filter = MagicMock()
        mock_filter.first.return_value = mock_queued
        mock_filter.count.return_value = 2  # 2 items ahead
        mock_query.filter_by.return_value = mock_filter
        mock_query.filter.return_value = mock_filter
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            result = QueueManager.get_queue_position(
                username="testuser", research_id="test-id-123"
            )

            # Position should be ahead_count + 1
            assert result == 3

    def test_get_queue_position_not_found(self):
        """Test get_queue_position when research not in queue."""
        mock_session = MagicMock()

        mock_query = MagicMock()
        mock_query.filter_by.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            result = QueueManager.get_queue_position(
                username="testuser", research_id="nonexistent"
            )

            assert result is None

    def test_get_queue_position_no_connection(self):
        """Test get_queue_position raises DatabaseSessionError when no connection."""
        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session_error(),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            with pytest.raises(DatabaseSessionError):
                QueueManager.get_queue_position(
                    username="testuser", research_id="test-id-123"
                )


class TestQueueManagerRemoveFromQueue:
    """Tests for QueueManager.remove_from_queue method."""

    def test_remove_from_queue_success(self):
        """Test successful removal from queue."""
        mock_session = MagicMock()

        # Mock queued research item
        mock_queued = MagicMock()
        mock_queued.position = 2

        mock_query = MagicMock()
        mock_query.filter_by.return_value.first.return_value = mock_queued
        mock_query.filter.return_value.update.return_value = None
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            result = QueueManager.remove_from_queue(
                username="testuser", research_id="test-id-123"
            )

            assert result is True
            mock_session.delete.assert_called_once_with(mock_queued)
            mock_session.commit.assert_called_once()

    def test_remove_from_queue_not_found(self):
        """Test remove_from_queue when research not in queue."""
        mock_session = MagicMock()

        mock_query = MagicMock()
        mock_query.filter_by.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            result = QueueManager.remove_from_queue(
                username="testuser", research_id="nonexistent"
            )

            assert result is False

    def test_remove_from_queue_no_connection(self):
        """Test remove_from_queue raises DatabaseSessionError when no connection."""
        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session_error(),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            with pytest.raises(DatabaseSessionError):
                QueueManager.remove_from_queue(
                    username="testuser", research_id="test-id-123"
                )


class TestQueueManagerGetUserQueue:
    """Tests for QueueManager.get_user_queue method."""

    def test_get_user_queue_success(self):
        """Test getting user queue successfully."""
        mock_session = MagicMock()

        # Mock queued items
        from datetime import datetime

        mock_item1 = MagicMock()
        mock_item1.research_id = "id-1"
        mock_item1.query = "query 1"
        mock_item1.mode = "detailed"
        mock_item1.position = 1
        mock_item1.created_at = datetime(2025, 1, 1, 12, 0, 0)
        mock_item1.is_processing = False

        mock_item2 = MagicMock()
        mock_item2.research_id = "id-2"
        mock_item2.query = "query 2"
        mock_item2.mode = "quick"
        mock_item2.position = 2
        mock_item2.created_at = None
        mock_item2.is_processing = True

        mock_research1 = MagicMock()
        mock_research2 = MagicMock()

        def query_side_effect(model):
            q = MagicMock()
            if "QueuedResearch" in str(model):
                q.filter_by.return_value.order_by.return_value.all.return_value = [
                    mock_item1,
                    mock_item2,
                ]
            else:  # ResearchHistory
                # Return research on first call, then second
                q.filter_by.return_value.first.side_effect = [
                    mock_research1,
                    mock_research2,
                ]
            return q

        mock_session.query.side_effect = query_side_effect

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            result = QueueManager.get_user_queue(username="testuser")

            assert len(result) == 2
            assert result[0]["research_id"] == "id-1"
            assert result[0]["query"] == "query 1"
            assert result[0]["mode"] == "detailed"
            assert result[0]["position"] == 1
            assert result[0]["is_processing"] is False
            assert result[1]["research_id"] == "id-2"
            assert result[1]["created_at"] is None

    def test_get_user_queue_empty(self):
        """Test get_user_queue when queue is empty."""
        mock_session = MagicMock()

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session(mock_session),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            result = QueueManager.get_user_queue(username="testuser")

            assert result == []

    def test_get_user_queue_no_connection(self):
        """Test get_user_queue raises DatabaseSessionError when no connection."""
        with patch(
            "local_deep_research.web.queue.manager.get_user_db_session",
            _mock_user_db_session_error(),
        ):
            from local_deep_research.web.queue.manager import QueueManager

            with pytest.raises(DatabaseSessionError):
                QueueManager.get_user_queue(username="testuser")
