"""Tests for database/thread_local_session.py."""

import threading
from unittest.mock import Mock, patch


class TestThreadLocalSessionManager:
    """Tests for ThreadLocalSessionManager class."""

    def test_init_creates_thread_local_storage(self):
        """Test that initialization creates thread-local storage."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        assert hasattr(manager, "_local")
        assert isinstance(manager._local, threading.local)

    def test_init_creates_credentials_tracking(self):
        """Test that initialization creates credentials tracking dict."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        assert hasattr(manager, "_thread_credentials")
        assert isinstance(manager._thread_credentials, dict)

    def test_init_creates_lock(self):
        """Test that initialization creates a threading lock."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        assert hasattr(manager, "_lock")
        assert isinstance(manager._lock, type(threading.Lock()))

    def test_get_session_creates_new_session(self):
        """Test that get_session creates a new session when none exists."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()

        with patch(
            "local_deep_research.database.thread_local_session.db_manager"
        ) as mock_db:
            mock_engine = Mock()
            mock_session = Mock()
            mock_db.open_user_database.return_value = mock_engine
            mock_db.create_thread_safe_session_for_metrics.return_value = (
                mock_session
            )

            result = manager.get_session("testuser", "testpass")

            assert result is mock_session
            mock_db.open_user_database.assert_called_once_with(
                "testuser", "testpass"
            )

    def test_get_session_reuses_existing_session(self):
        """Test that get_session reuses an existing valid session."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        mock_existing_session = Mock()
        mock_existing_session.execute.return_value = None

        # Manually set up an existing session
        manager._local.session = mock_existing_session

        result = manager.get_session("testuser", "testpass")

        # Should return the existing session
        assert result is mock_existing_session
        # Verify text() wrapper type and content explicitly
        from sqlalchemy.sql.elements import TextClause

        call_args = mock_existing_session.execute.call_args[0][0]
        assert isinstance(call_args, TextClause)
        assert call_args.text == "SELECT 1"

    def test_get_session_creates_new_when_existing_invalid(self):
        """Test that get_session creates new session when existing is invalid."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        mock_invalid_session = Mock()
        mock_invalid_session.execute.side_effect = Exception("Connection lost")

        manager._local.session = mock_invalid_session

        with patch(
            "local_deep_research.database.thread_local_session.db_manager"
        ) as mock_db:
            mock_engine = Mock()
            mock_new_session = Mock()
            mock_db.open_user_database.return_value = mock_engine
            mock_db.create_thread_safe_session_for_metrics.return_value = (
                mock_new_session
            )

            result = manager.get_session("testuser", "testpass")

            # Should create a new session
            assert result is mock_new_session

    def test_get_session_returns_none_on_db_open_failure(self):
        """Test that get_session returns None when database fails to open."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()

        with patch(
            "local_deep_research.database.thread_local_session.db_manager"
        ) as mock_db:
            mock_db.open_user_database.return_value = None

            result = manager.get_session("testuser", "testpass")

            assert result is None

    def test_get_current_session_returns_none_when_no_session(self):
        """Test that get_current_session returns None when no session exists."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        result = manager.get_current_session()
        assert result is None

    def test_get_current_session_returns_existing_session(self):
        """Test that get_current_session returns the existing session."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        mock_session = Mock()
        manager._local.session = mock_session

        result = manager.get_current_session()
        assert result is mock_session

    def test_cleanup_thread_cleans_current_thread(self):
        """Test that cleanup_thread cleans up the current thread's session."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        mock_session = Mock()
        manager._local.session = mock_session
        manager._local.username = "testuser"
        thread_id = threading.get_ident()
        manager._thread_credentials[thread_id] = ("testuser", "testpass")

        with patch(
            "local_deep_research.database.thread_local_session.db_manager"
        ):
            manager.cleanup_thread()

            mock_session.close.assert_called_once()
            assert manager._local.session is None
            assert thread_id not in manager._thread_credentials

    def test_cleanup_all_cleans_all_threads(self):
        """Test that cleanup_all cleans up all tracked sessions."""
        from local_deep_research.database.thread_local_session import (
            ThreadLocalSessionManager,
        )

        manager = ThreadLocalSessionManager()
        manager._thread_credentials = {
            1: ("user1", "pass1"),
            2: ("user2", "pass2"),
        }

        with patch(
            "local_deep_research.database.thread_local_session.db_manager"
        ) as mock_db:
            manager.cleanup_all()

            mock_db.cleanup_all_thread_engines.assert_called_once()


class TestThreadSessionContext:
    """Tests for ThreadSessionContext context manager."""

    def test_context_manager_returns_session(self):
        """Test that context manager returns a session on enter."""
        from local_deep_research.database.thread_local_session import (
            ThreadSessionContext,
        )

        with patch(
            "local_deep_research.database.thread_local_session.get_metrics_session"
        ) as mock_get:
            mock_session = Mock()
            mock_get.return_value = mock_session

            with ThreadSessionContext("testuser", "testpass") as session:
                assert session is mock_session

    def test_context_manager_stores_credentials(self):
        """Test that context manager stores username and password."""
        from local_deep_research.database.thread_local_session import (
            ThreadSessionContext,
        )

        ctx = ThreadSessionContext("myuser", "mypass")
        assert ctx.username == "myuser"
        assert ctx.password == "mypass"


class TestModuleFunctions:
    """Tests for module-level functions."""

    def test_get_metrics_session_delegates_to_manager(self):
        """Test that get_metrics_session delegates to thread_session_manager."""
        from local_deep_research.database.thread_local_session import (
            get_metrics_session,
            thread_session_manager,
        )

        with patch.object(thread_session_manager, "get_session") as mock_get:
            mock_session = Mock()
            mock_get.return_value = mock_session

            result = get_metrics_session("testuser", "testpass")

            mock_get.assert_called_once_with("testuser", "testpass")
            assert result is mock_session

    def test_get_current_thread_session_delegates_to_manager(self):
        """Test that get_current_thread_session delegates to manager."""
        from local_deep_research.database.thread_local_session import (
            get_current_thread_session,
            thread_session_manager,
        )

        with patch.object(
            thread_session_manager, "get_current_session"
        ) as mock_get:
            mock_session = Mock()
            mock_get.return_value = mock_session

            result = get_current_thread_session()

            mock_get.assert_called_once()
            assert result is mock_session

    def test_cleanup_current_thread_delegates_to_manager(self):
        """Test that cleanup_current_thread delegates to manager."""
        from local_deep_research.database.thread_local_session import (
            cleanup_current_thread,
            thread_session_manager,
        )

        with patch.object(
            thread_session_manager, "cleanup_thread"
        ) as mock_cleanup:
            cleanup_current_thread()
            mock_cleanup.assert_called_once()


class TestGlobalInstance:
    """Tests for the global thread_session_manager instance."""

    def test_global_instance_exists(self):
        """Test that global thread_session_manager instance exists."""
        from local_deep_research.database.thread_local_session import (
            thread_session_manager,
        )

        assert thread_session_manager is not None

    def test_global_instance_is_correct_type(self):
        """Test that global instance is ThreadLocalSessionManager."""
        from local_deep_research.database.thread_local_session import (
            thread_session_manager,
            ThreadLocalSessionManager,
        )

        assert isinstance(thread_session_manager, ThreadLocalSessionManager)
