"""
Tests for SQLCipher performance pragmas.

These tests verify that performance-related PRAGMA settings are
correctly applied for optimal database performance.
"""

import shutil
import tempfile
from pathlib import Path

import pytest

from src.local_deep_research.database.sqlcipher_utils import (
    apply_cipher_defaults_before_key,
    apply_performance_pragmas,
    apply_sqlcipher_pragmas,
    set_sqlcipher_key,
)
from src.local_deep_research.database.sqlcipher_compat import (
    get_sqlcipher_module,
)


@pytest.fixture
def temp_db_path():
    """Create a temporary database path."""
    temp_dir = tempfile.mkdtemp()
    db_path = Path(temp_dir) / "test_performance.db"
    yield db_path
    shutil.rmtree(temp_dir)


@pytest.fixture
def sqlcipher_module():
    """Get the SQLCipher module."""
    return get_sqlcipher_module()


@pytest.fixture
def configured_connection(sqlcipher_module, temp_db_path):
    """Create a fully configured database connection."""
    conn = sqlcipher_module.connect(str(temp_db_path))
    cursor = conn.cursor()
    # New database: cipher_default_* before key
    apply_cipher_defaults_before_key(cursor)
    set_sqlcipher_key(cursor, "testpassword")
    apply_sqlcipher_pragmas(cursor, creation_mode=True)
    apply_performance_pragmas(cursor)
    cursor.close()
    return conn


class TestWALMode:
    """Tests for WAL (Write-Ahead Logging) mode."""

    def test_wal_mode_enabled(self, configured_connection):
        """Verify WAL journal mode is enabled by default."""
        result = configured_connection.execute("PRAGMA journal_mode").fetchone()
        assert result is not None
        assert result[0].lower() == "wal", f"Expected WAL mode, got {result[0]}"
        configured_connection.close()

    def test_wal_creates_additional_files(self, sqlcipher_module, temp_db_path):
        """Verify WAL mode creates -wal and -shm files."""
        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()
        # New database: cipher_default_* before key
        apply_cipher_defaults_before_key(cursor)
        set_sqlcipher_key(cursor, "testpassword")
        apply_performance_pragmas(cursor)
        cursor.close()

        # Create a table and write data to trigger WAL file creation
        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
        conn.execute("INSERT INTO test VALUES (1, 'test_data')")
        conn.commit()

        # Note: WAL files (-wal, -shm) may not exist immediately or may be cleaned up
        # The main verification is that WAL mode is enabled
        assert (
            conn.execute("PRAGMA journal_mode").fetchone()[0].lower() == "wal"
        )
        conn.close()

    def test_concurrent_reads_with_wal(self, sqlcipher_module, temp_db_path):
        """Verify WAL mode allows concurrent read connections."""
        # Create and populate database
        conn1 = sqlcipher_module.connect(str(temp_db_path))
        cursor1 = conn1.cursor()
        # New database: cipher_default_* before key
        apply_cipher_defaults_before_key(cursor1)
        set_sqlcipher_key(cursor1, "testpassword")
        apply_performance_pragmas(cursor1)
        cursor1.close()

        conn1.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
        conn1.execute("INSERT INTO test VALUES (1, 'data1')")
        conn1.commit()

        # Open second read connection while first is open
        conn2 = sqlcipher_module.connect(str(temp_db_path))
        cursor2 = conn2.cursor()
        # Existing database: key first, then cipher_* pragmas
        set_sqlcipher_key(cursor2, "testpassword")
        apply_sqlcipher_pragmas(cursor2, creation_mode=False)
        apply_performance_pragmas(cursor2)
        cursor2.close()

        # Both connections should be able to read
        result1 = conn1.execute("SELECT * FROM test").fetchall()
        result2 = conn2.execute("SELECT * FROM test").fetchall()

        assert result1 == result2 == [(1, "data1")]

        conn2.close()
        conn1.close()


class TestCacheSettings:
    """Tests for cache size settings."""

    def test_cache_size_applied(self, configured_connection):
        """Verify cache_size pragma is applied."""
        result = configured_connection.execute("PRAGMA cache_size").fetchone()
        assert result is not None
        # Default is 64MB = 65536KB, stored as negative value
        assert result[0] < 0, "Cache size should be negative (KB format)"
        configured_connection.close()

    def test_temp_store_memory(self, configured_connection):
        """Verify temp_store is set to MEMORY."""
        result = configured_connection.execute("PRAGMA temp_store").fetchone()
        assert result is not None
        # 2 = MEMORY
        assert result[0] == 2, (
            f"Expected temp_store=2 (MEMORY), got {result[0]}"
        )
        configured_connection.close()


class TestBusyTimeout:
    """Tests for busy timeout setting."""

    def test_busy_timeout_applied(self, configured_connection):
        """Verify busy_timeout is set to prevent immediate lock failures."""
        result = configured_connection.execute("PRAGMA busy_timeout").fetchone()
        assert result is not None
        assert result[0] == 10000, (
            f"Expected busy_timeout=10000, got {result[0]}"
        )
        configured_connection.close()

    def test_busy_timeout_prevents_immediate_lock_error(
        self, sqlcipher_module, temp_db_path
    ):
        """Verify busy_timeout allows waiting for locks."""

        # Create database
        conn1 = sqlcipher_module.connect(str(temp_db_path))
        cursor1 = conn1.cursor()
        # New database: cipher_default_* before key
        apply_cipher_defaults_before_key(cursor1)
        set_sqlcipher_key(cursor1, "testpassword")
        apply_performance_pragmas(cursor1)
        cursor1.close()

        conn1.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT)")
        conn1.execute("INSERT INTO test VALUES (1, 'data1')")
        conn1.commit()

        # The busy_timeout should allow retries
        # This is a basic test that the setting is applied
        result = conn1.execute("PRAGMA busy_timeout").fetchone()
        assert result[0] == 10000

        conn1.close()


class TestSynchronousMode:
    """Tests for synchronous mode setting."""

    def test_synchronous_mode_normal(self, configured_connection):
        """Verify synchronous mode is set to NORMAL (good balance)."""
        result = configured_connection.execute("PRAGMA synchronous").fetchone()
        assert result is not None
        # Default is NORMAL (1)
        assert result[0] == 1, (
            f"Expected synchronous=1 (NORMAL), got {result[0]}"
        )
        configured_connection.close()


class TestPerformanceIntegration:
    """Integration tests for performance settings."""

    def test_all_performance_pragmas_applied(self, configured_connection):
        """Verify all performance pragmas are applied correctly."""
        pragmas = {
            "journal_mode": lambda x: x.lower() == "wal",
            "temp_store": lambda x: x == 2,
            "busy_timeout": lambda x: x == 10000,
            "synchronous": lambda x: x == 1,
        }

        for pragma, validator in pragmas.items():
            result = configured_connection.execute(
                f"PRAGMA {pragma}"
            ).fetchone()
            assert result is not None, f"PRAGMA {pragma} returned None"
            assert validator(result[0]), (
                f"PRAGMA {pragma} has unexpected value: {result[0]}"
            )

        configured_connection.close()

    def test_performance_after_reopen(self, sqlcipher_module, temp_db_path):
        """Verify performance settings persist across connections."""
        # Create database with performance pragmas
        conn1 = sqlcipher_module.connect(str(temp_db_path))
        cursor1 = conn1.cursor()
        # New database: cipher_default_* before key
        apply_cipher_defaults_before_key(cursor1)
        set_sqlcipher_key(cursor1, "testpassword")
        apply_performance_pragmas(cursor1)
        cursor1.close()

        conn1.execute("CREATE TABLE test (id INTEGER)")
        conn1.commit()
        conn1.close()

        # Reopen and verify
        conn2 = sqlcipher_module.connect(str(temp_db_path))
        cursor2 = conn2.cursor()
        # Existing database: key first, then cipher_* pragmas
        set_sqlcipher_key(cursor2, "testpassword")
        apply_sqlcipher_pragmas(cursor2, creation_mode=False)
        apply_performance_pragmas(cursor2)
        cursor2.close()

        # WAL mode persists (stored in database file)
        result = conn2.execute("PRAGMA journal_mode").fetchone()
        assert result[0].lower() == "wal"

        # Other settings are per-connection but should still be applied
        result = conn2.execute("PRAGMA busy_timeout").fetchone()
        assert result[0] == 10000

        conn2.close()

    def test_write_performance_with_settings(self, configured_connection):
        """Verify database performs well with applied settings."""
        import time

        configured_connection.execute(
            "CREATE TABLE perf_test (id INTEGER PRIMARY KEY, data TEXT)"
        )

        # Insert 100 rows and measure time
        start = time.time()
        for i in range(100):
            configured_connection.execute(
                f"INSERT INTO perf_test VALUES ({i}, 'test_data_{i}')"
            )
        configured_connection.commit()
        elapsed = time.time() - start

        # Should complete in reasonable time (< 5 seconds with WAL)
        assert elapsed < 5, f"100 inserts took {elapsed:.2f}s - too slow"

        # Verify all data was written
        count = configured_connection.execute(
            "SELECT COUNT(*) FROM perf_test"
        ).fetchone()[0]
        assert count == 100

        configured_connection.close()
