"""
Tests for SQLCipher 4.6+ Community Edition compatibility.

These tests verify that the cipher_default_* pragmas are used correctly
and that the PRAGMA ordering (before/after key) is correct.
"""

import shutil
import tempfile
from pathlib import Path

import pytest

from src.local_deep_research.database.encrypted_db import DatabaseManager
from src.local_deep_research.database.sqlcipher_compat import (
    get_sqlcipher_module,
)
from src.local_deep_research.database.sqlcipher_utils import (
    apply_cipher_defaults_before_key,
    apply_sqlcipher_pragmas,
    get_sqlcipher_settings,
    set_sqlcipher_key,
)


@pytest.fixture
def temp_data_dir():
    """Create a temporary data directory for testing."""
    temp_dir = tempfile.mkdtemp()
    yield Path(temp_dir)
    shutil.rmtree(temp_dir)


@pytest.fixture
def temp_db_path(temp_data_dir):
    """Create a temporary database path."""
    return temp_data_dir / "test_cipher.db"


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


@pytest.fixture
def db_manager(temp_data_dir, monkeypatch):
    """Create a DatabaseManager with test configuration."""
    monkeypatch.setenv("LDR_DATA_DIR", str(temp_data_dir))
    manager = DatabaseManager()
    manager.data_dir = temp_data_dir / "encrypted_databases"
    manager.data_dir.mkdir(parents=True, exist_ok=True)
    return manager


@pytest.fixture
def auth_user(temp_data_dir, monkeypatch):
    """Create a test user in auth database."""
    monkeypatch.setenv("LDR_DATA_DIR", str(temp_data_dir))

    from src.local_deep_research.database.auth_db import (
        get_auth_db_session,
        init_auth_database,
    )
    from src.local_deep_research.database.models.auth import User

    init_auth_database()
    auth_db = get_auth_db_session()
    user = User(username="testuser")
    auth_db.add(user)
    auth_db.commit()
    auth_db.close()
    return user


class TestSQLCipher46Compatibility:
    """Tests for SQLCipher 4.6+ compatibility with cipher_default_* pragmas."""

    def test_cipher_default_page_size_applied(
        self, sqlcipher_module, temp_db_path
    ):
        """Verify cipher_default_page_size pragma is applied correctly."""
        settings = get_sqlcipher_settings()
        expected_page_size = settings["page_size"]

        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        # Apply cipher settings BEFORE key (SQLCipher 4.6+ requirement)
        apply_cipher_defaults_before_key(cursor)
        set_sqlcipher_key(cursor, "testpassword")
        cursor.close()

        # Verify the page size is applied
        result = conn.execute("PRAGMA cipher_page_size").fetchone()
        assert result is not None
        assert str(result[0]) == str(expected_page_size), (
            f"Expected cipher_page_size={expected_page_size}, got {result[0]}"
        )

        conn.close()

    def test_cipher_default_hmac_algorithm_applied(
        self, sqlcipher_module, temp_db_path
    ):
        """Verify cipher_default_hmac_algorithm pragma is applied correctly."""
        settings = get_sqlcipher_settings()
        expected_hmac = settings["hmac_algorithm"]

        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        apply_cipher_defaults_before_key(cursor)
        set_sqlcipher_key(cursor, "testpassword")
        cursor.close()

        result = conn.execute("PRAGMA cipher_hmac_algorithm").fetchone()
        assert result is not None
        assert result[0] == expected_hmac, (
            f"Expected cipher_hmac_algorithm={expected_hmac}, got {result[0]}"
        )

        conn.close()

    def test_cipher_default_kdf_algorithm_applied(
        self, sqlcipher_module, temp_db_path
    ):
        """Verify cipher_default_kdf_algorithm pragma is applied correctly."""
        settings = get_sqlcipher_settings()
        expected_kdf = settings["kdf_algorithm"]

        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        apply_cipher_defaults_before_key(cursor)
        set_sqlcipher_key(cursor, "testpassword")
        cursor.close()

        result = conn.execute("PRAGMA cipher_kdf_algorithm").fetchone()
        assert result is not None
        assert result[0] == expected_kdf, (
            f"Expected cipher_kdf_algorithm={expected_kdf}, got {result[0]}"
        )

        conn.close()

    def test_pragma_order_cipher_settings_before_key(
        self, sqlcipher_module, temp_db_path
    ):
        """Verify that cipher_default_* pragmas work when applied BEFORE key."""
        settings = get_sqlcipher_settings()

        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        # Correct order: cipher settings BEFORE key
        apply_cipher_defaults_before_key(cursor)
        set_sqlcipher_key(cursor, "testpassword")

        # Should be able to create tables and query
        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)")
        conn.execute("INSERT INTO test VALUES (1, 'hello')")
        result = conn.execute("SELECT * FROM test").fetchone()

        assert result == (1, "hello"), (
            "Database should be accessible with correct order"
        )

        # Verify settings were applied
        page_size = conn.execute("PRAGMA cipher_page_size").fetchone()[0]
        assert str(page_size) == str(settings["page_size"])

        cursor.close()
        conn.close()

    def test_pragma_order_kdf_iter_after_key(
        self, sqlcipher_module, temp_db_path
    ):
        """Verify that kdf_iter pragma works when applied AFTER key."""
        settings = get_sqlcipher_settings()

        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        # Correct order
        apply_cipher_defaults_before_key(cursor)
        set_sqlcipher_key(cursor, "testpassword")
        apply_sqlcipher_pragmas(cursor, creation_mode=True)
        cursor.close()

        # Verify kdf_iter was applied
        result = conn.execute("PRAGMA kdf_iter").fetchone()
        assert result is not None
        assert str(result[0]) == str(settings["kdf_iterations"]), (
            f"Expected kdf_iter={settings['kdf_iterations']}, got {result[0]}"
        )

        conn.close()

    def test_cipher_default_pragmas_set_correct_defaults(
        self, sqlcipher_module, temp_db_path
    ):
        """
        Verify that cipher_default_* pragmas properly configure the database.

        This test confirms that using cipher_default_page_size (not cipher_page_size)
        correctly sets the page size for new databases in SQLCipher 4.6+.
        """
        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        # Use cipher_default_page_size (the correct way for SQLCipher 4.6+)
        cursor.execute("PRAGMA cipher_default_page_size = 16384")
        cursor.execute("PRAGMA key = 'testpassword'")
        cursor.close()

        # The page size should be 16384 as we set it
        result = conn.execute("PRAGMA cipher_page_size").fetchone()
        assert result[0] == "16384", (
            f"cipher_default_page_size should set page size to 16384, got {result[0]}"
        )

        # Create a table to ensure database is usable
        conn.execute("CREATE TABLE test (id INTEGER PRIMARY KEY)")
        conn.execute("INSERT INTO test VALUES (1)")
        result = conn.execute("SELECT * FROM test").fetchone()
        assert result == (1,), "Database should be functional"

        conn.close()

    def test_database_created_with_correct_settings(
        self, db_manager, auth_user
    ):
        """Verify database is created with all correct SQLCipher settings."""
        from sqlalchemy import text

        settings = get_sqlcipher_settings()
        engine = db_manager.create_user_database("testuser", "testpassword123")

        with engine.connect() as conn:
            # Check all cipher settings
            page_size = conn.execute(text("PRAGMA cipher_page_size")).scalar()
            hmac_algo = conn.execute(
                text("PRAGMA cipher_hmac_algorithm")
            ).scalar()
            kdf_algo = conn.execute(
                text("PRAGMA cipher_kdf_algorithm")
            ).scalar()
            kdf_iter = conn.execute(text("PRAGMA kdf_iter")).scalar()

            assert str(page_size) == str(settings["page_size"]), (
                f"cipher_page_size mismatch: expected {settings['page_size']}, got {page_size}"
            )
            assert hmac_algo == settings["hmac_algorithm"], (
                f"cipher_hmac_algorithm mismatch: expected {settings['hmac_algorithm']}, got {hmac_algo}"
            )
            assert kdf_algo == settings["kdf_algorithm"], (
                f"cipher_kdf_algorithm mismatch: expected {settings['kdf_algorithm']}, got {kdf_algo}"
            )
            assert str(kdf_iter) == str(settings["kdf_iterations"]), (
                f"kdf_iter mismatch: expected {settings['kdf_iterations']}, got {kdf_iter}"
            )

    def test_database_reopened_with_matching_settings(
        self, db_manager, auth_user
    ):
        """Verify database can be reopened and settings remain consistent."""
        from sqlalchemy import text

        settings = get_sqlcipher_settings()

        # Create database
        engine = db_manager.create_user_database("testuser", "testpassword123")

        # Create a test table and write some data
        with engine.connect() as conn:
            conn.execute(
                text(
                    "CREATE TABLE IF NOT EXISTS test_data (id INTEGER PRIMARY KEY, value TEXT)"
                )
            )
            conn.execute(
                text(
                    "INSERT INTO test_data (id, value) VALUES (1, 'test_value')"
                )
            )
            conn.commit()

        # Close the database
        db_manager.close_user_database("testuser")
        assert not db_manager.is_user_connected("testuser")

        # Reopen the database
        engine2 = db_manager.open_user_database("testuser", "testpassword123")
        assert engine2 is not None

        with engine2.connect() as conn:
            # Verify settings are still correct
            page_size = conn.execute(text("PRAGMA cipher_page_size")).scalar()
            hmac_algo = conn.execute(
                text("PRAGMA cipher_hmac_algorithm")
            ).scalar()
            kdf_algo = conn.execute(
                text("PRAGMA cipher_kdf_algorithm")
            ).scalar()

            assert str(page_size) == str(settings["page_size"])
            assert hmac_algo == settings["hmac_algorithm"]
            assert kdf_algo == settings["kdf_algorithm"]

            # Verify data is still there
            result = conn.execute(
                text("SELECT value FROM test_data WHERE id = 1")
            ).scalar()
            assert result == "test_value", "Data should persist after reopen"


class TestSQLCipherVersionDetection:
    """Tests for SQLCipher version detection and compatibility."""

    def test_sqlcipher_version_is_4x(self, sqlcipher_module, temp_db_path):
        """Verify we're testing against SQLCipher 4.x."""
        conn = sqlcipher_module.connect(str(temp_db_path))
        cursor = conn.cursor()

        # Set key first (required to query version in some setups)
        cursor.execute("PRAGMA key = 'test'")

        result = conn.execute("PRAGMA cipher_version").fetchone()
        assert result is not None

        version = result[0]
        # Should be 4.x.x (e.g., "4.6.1 community")
        assert version.startswith("4."), (
            f"Expected SQLCipher 4.x, got {version}"
        )

        cursor.close()
        conn.close()

    def test_cipher_settings_function_returns_valid_settings(self):
        """Verify get_sqlcipher_settings returns all required keys."""
        settings = get_sqlcipher_settings()

        required_keys = [
            "kdf_iterations",
            "page_size",
            "hmac_algorithm",
            "kdf_algorithm",
        ]
        for key in required_keys:
            assert key in settings, f"Missing required setting: {key}"

        # Verify types
        assert isinstance(settings["kdf_iterations"], int)
        assert isinstance(settings["page_size"], int)
        assert isinstance(settings["hmac_algorithm"], str)
        assert isinstance(settings["kdf_algorithm"], str)

        # Verify reasonable values
        assert settings["kdf_iterations"] >= 100000, (
            "KDF iterations should be >= 100000"
        )
        assert settings["page_size"] in [
            1024,
            4096,
            8192,
            16384,
            32768,
            65536,
        ], "Page size should be a valid SQLite page size"
