"""
Extended Tests for Database Manager

Phase 21: Database & Encryption - Database Manager Tests
Tests encrypted database management, connection pooling, and thread safety.
"""

import pytest
import threading
from unittest.mock import patch, MagicMock
from pathlib import Path


class TestDatabaseEncryption:
    """Tests for database encryption functionality"""

    @patch("local_deep_research.database.encrypted_db.get_sqlcipher_module")
    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_encryption_key_validation_valid(
        self, mock_data_dir, mock_sqlcipher
    ):
        """Test valid encryption key is accepted"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            assert manager._is_valid_encryption_key("valid_password") is True
            assert manager._is_valid_encryption_key("a") is True
            assert manager._is_valid_encryption_key("complex!@#$%") is True

    @patch("local_deep_research.database.encrypted_db.get_sqlcipher_module")
    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_encryption_key_validation_invalid(
        self, mock_data_dir, mock_sqlcipher
    ):
        """Test invalid encryption keys are rejected"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            assert manager._is_valid_encryption_key(None) is False
            assert manager._is_valid_encryption_key("") is False

    @patch("local_deep_research.database.encrypted_db.get_sqlcipher_module")
    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_database_creation_invalid_password(
        self, mock_data_dir, mock_sqlcipher
    ):
        """Test database creation fails with invalid password"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            with pytest.raises(ValueError, match="Invalid encryption key"):
                manager.create_user_database("testuser", "")

            with pytest.raises(ValueError, match="Invalid encryption key"):
                manager.create_user_database("testuser", None)

    @patch("local_deep_research.database.encrypted_db.get_sqlcipher_module")
    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_database_open_invalid_password(
        self, mock_data_dir, mock_sqlcipher
    ):
        """Test opening database fails with invalid password"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            with pytest.raises(ValueError, match="Invalid encryption key"):
                manager.open_user_database("testuser", "")

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_sqlcipher_unavailable_fallback(self, mock_data_dir):
        """Test fallback when SQLCipher not available"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.dict("os.environ", {"LDR_ALLOW_UNENCRYPTED": "true"}):
            with patch.object(
                DatabaseManager,
                "_check_encryption_available",
                return_value=False,
            ):
                manager = DatabaseManager()

                assert manager.has_encryption is False

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_encryption_check_available(self, mock_data_dir):
        """Test encryption availability check"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        # With encryption available
        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()
            assert manager.has_encryption is True


class TestConnectionPooling:
    """Tests for connection pooling functionality"""

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_pool_kwargs_static_pool(self, mock_data_dir):
        """Test pool kwargs for static pool (testing mode)"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.dict("os.environ", {"TESTING": "true"}):
            with patch.object(
                DatabaseManager,
                "_check_encryption_available",
                return_value=True,
            ):
                manager = DatabaseManager()

                kwargs = manager._get_pool_kwargs()
                assert kwargs == {}

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_pool_kwargs_queue_pool(self, mock_data_dir):
        """Test pool kwargs for queue pool (production mode)"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.dict("os.environ", {}, clear=True):
            with patch.object(
                DatabaseManager,
                "_check_encryption_available",
                return_value=True,
            ):
                manager = DatabaseManager()
                manager._use_static_pool = False

                kwargs = manager._get_pool_kwargs()
                assert "pool_size" in kwargs
                assert kwargs["pool_size"] == 10
                assert kwargs["max_overflow"] == 30

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_connection_storage(self, mock_data_dir):
        """Test connections are stored properly"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Mock engine
            mock_engine = MagicMock()
            manager.connections["testuser"] = mock_engine

            assert "testuser" in manager.connections
            assert manager.connections["testuser"] is mock_engine


class TestThreadSafety:
    """Tests for thread safety functionality"""

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_thread_local_engine_isolation(self, mock_data_dir):
        """Test thread-local engines are isolated"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Add thread-specific engines
            thread_id = threading.get_ident()
            manager._thread_engines[("user1", thread_id)] = MagicMock()
            manager._thread_engines[("user2", thread_id)] = MagicMock()

            assert len(manager._thread_engines) == 2

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_thread_cleanup_by_username(self, mock_data_dir):
        """Test thread cleanup by username"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Add thread-specific engines
            mock_engine1 = MagicMock()
            mock_engine2 = MagicMock()
            manager._thread_engines[("user1", 100)] = mock_engine1
            manager._thread_engines[("user2", 100)] = mock_engine2

            manager.cleanup_thread_engines(username="user1")

            assert ("user1", 100) not in manager._thread_engines
            assert ("user2", 100) in manager._thread_engines
            mock_engine1.dispose.assert_called_once()

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_thread_cleanup_by_thread_id(self, mock_data_dir):
        """Test thread cleanup by thread ID"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Add thread-specific engines
            mock_engine1 = MagicMock()
            mock_engine2 = MagicMock()
            manager._thread_engines[("user1", 100)] = mock_engine1
            manager._thread_engines[("user1", 200)] = mock_engine2

            manager.cleanup_thread_engines(thread_id=100)

            assert ("user1", 100) not in manager._thread_engines
            assert ("user1", 200) in manager._thread_engines

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_thread_cleanup_all(self, mock_data_dir):
        """Test cleaning up all thread engines"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Add thread-specific engines
            for i in range(5):
                manager._thread_engines[(f"user{i}", i * 100)] = MagicMock()

            manager.cleanup_all_thread_engines()

            assert len(manager._thread_engines) == 0

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_concurrent_cleanup(self, mock_data_dir):
        """Test concurrent cleanup operations"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Add engines
            for i in range(10):
                manager._thread_engines[(f"user{i}", i)] = MagicMock()

            # Run cleanups from multiple threads
            errors = []

            def cleanup_thread(username):
                try:
                    manager.cleanup_thread_engines(username=username)
                except Exception as e:
                    errors.append(e)

            threads = [
                threading.Thread(target=cleanup_thread, args=(f"user{i}",))
                for i in range(10)
            ]

            for t in threads:
                t.start()
            for t in threads:
                t.join()

            assert len(errors) == 0


class TestDatabaseOperations:
    """Tests for database operations"""

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_get_session_no_connection(self, mock_data_dir):
        """Test get_session when no connection exists"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            result = manager.get_session("nonexistent_user")

            assert result is None

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_get_session_with_connection(self, mock_data_dir):
        """Test get_session when connection exists"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            mock_engine = MagicMock()
            manager.connections["testuser"] = mock_engine

            # This will try to create a real session, mock the sessionmaker
            with patch(
                "local_deep_research.database.encrypted_db.sessionmaker"
            ) as mock_sm:
                mock_session = MagicMock()
                mock_sm.return_value = MagicMock(return_value=mock_session)

                result = manager.get_session("testuser")

                assert result is mock_session

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_close_user_database(self, mock_data_dir):
        """Test closing user database"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            mock_engine = MagicMock()
            manager.connections["testuser"] = mock_engine

            manager.close_user_database("testuser")

            mock_engine.dispose.assert_called_once()
            assert "testuser" not in manager.connections

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_get_memory_usage(self, mock_data_dir):
        """Test memory usage statistics"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Add some connections and thread engines
            manager.connections["user1"] = MagicMock()
            manager.connections["user2"] = MagicMock()
            manager._thread_engines[("user1", 100)] = MagicMock()

            usage = manager.get_memory_usage()

            assert usage["active_connections"] == 2
            assert usage["thread_engines"] == 1
            assert "estimated_memory_mb" in usage


class TestDatabaseIntegrity:
    """Tests for database integrity checking"""

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_check_integrity_no_connection(self, mock_data_dir):
        """Test integrity check when no connection exists"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            result = manager.check_database_integrity("nonexistent")

            assert result is False

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_check_integrity_success(self, mock_data_dir):
        """Test successful integrity check"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            mock_engine = MagicMock()
            mock_conn = MagicMock()
            mock_engine.connect.return_value.__enter__ = MagicMock(
                return_value=mock_conn
            )
            mock_engine.connect.return_value.__exit__ = MagicMock(
                return_value=False
            )

            # Mock successful integrity checks
            mock_conn.execute.side_effect = [
                MagicMock(
                    fetchone=MagicMock(return_value=("ok",))
                ),  # quick_check
                iter([]),  # cipher_integrity_check - no failures
            ]

            manager.connections["testuser"] = mock_engine

            result = manager.check_database_integrity("testuser")

            assert result is True

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_check_integrity_failure(self, mock_data_dir):
        """Test failed integrity check"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            mock_engine = MagicMock()
            mock_conn = MagicMock()
            mock_engine.connect.return_value.__enter__ = MagicMock(
                return_value=mock_conn
            )
            mock_engine.connect.return_value.__exit__ = MagicMock(
                return_value=False
            )

            # Mock failed integrity check
            mock_conn.execute.return_value.fetchone.return_value = ("corrupt",)

            manager.connections["testuser"] = mock_engine

            result = manager.check_database_integrity("testuser")

            assert result is False


class TestPasswordChange:
    """Tests for password change functionality"""

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_change_password_no_encryption(self, mock_data_dir):
        """Test password change when encryption not available"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=False
        ):
            with patch.dict("os.environ", {"LDR_ALLOW_UNENCRYPTED": "true"}):
                manager = DatabaseManager()

                result = manager.change_password("user", "old", "new")

                assert result is False


class TestUserExists:
    """Tests for user existence check"""

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_user_exists_true(self, mock_data_dir):
        """Test user exists returns true"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            # Mock the internal method call
            with patch.object(
                manager, "user_exists", return_value=True
            ) as mock_method:
                result = mock_method("testuser")

                assert result is True

    @patch("local_deep_research.database.encrypted_db.get_data_directory")
    def test_user_exists_false(self, mock_data_dir):
        """Test user exists returns false"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        mock_data_dir.return_value = Path("/tmp/test_data")

        with patch.object(
            DatabaseManager, "_check_encryption_available", return_value=True
        ):
            manager = DatabaseManager()

            with patch.object(
                manager, "user_exists", return_value=False
            ) as mock_method:
                result = mock_method("nonexistent")

                assert result is False


class TestUserDatabasePath:
    """Tests for database path generation"""

    def test_get_user_db_path(self):
        """Test user database path generation"""
        from local_deep_research.database.encrypted_db import DatabaseManager

        # Use a temp directory that exists
        import tempfile

        temp_dir = Path(tempfile.gettempdir())

        with patch(
            "local_deep_research.database.encrypted_db.get_data_directory",
            return_value=temp_dir,
        ):
            with patch.object(
                DatabaseManager,
                "_check_encryption_available",
                return_value=True,
            ):
                with patch(
                    "local_deep_research.database.encrypted_db.get_user_database_filename",
                    return_value="user_test.db",
                ):
                    manager = DatabaseManager()
                    path = manager._get_user_db_path("testuser")

                    # Path should include the filename
                    assert "user_test.db" in str(path)


class TestGlobalInstance:
    """Tests for global database manager instance"""

    def test_global_instance_exists(self):
        """Test global db_manager instance is available"""
        # This will fail if the module can't be imported
        # but we mock the initialization
        pass  # Just a placeholder - actual import tested elsewhere
