"""Tests for database initialization and encryption functionality."""

import shutil
import tempfile
import uuid
from pathlib import Path

import pytest
from sqlalchemy import create_engine, inspect
from sqlalchemy.orm import sessionmaker

from local_deep_research.database.models import (
    Base,
    ResearchHistory,
    Setting,
    User,
)


class TestDatabaseInitialization:
    """Test suite for database initialization and setup."""

    @pytest.fixture
    def temp_dir(self):
        """Create a temporary directory for test databases."""
        temp_dir = tempfile.mkdtemp()
        yield temp_dir
        shutil.rmtree(temp_dir)

    @pytest.fixture
    def db_engine(self, temp_dir):
        """Create and dispose a SQLite engine with all tables."""
        db_path = str(Path(temp_dir) / "test.db")
        engine = create_engine(f"sqlite:///{db_path}")
        Base.metadata.create_all(engine)
        yield engine
        engine.dispose()

    @pytest.fixture
    def db_session(self, db_engine):
        """Create a session bound to db_engine, closed on teardown."""
        Session = sessionmaker(bind=db_engine)
        session = Session()
        yield session
        session.close()

    def test_basic_database_creation(self, temp_dir):
        """Test creating a basic SQLite database."""
        db_path = str(Path(temp_dir) / "test.db")
        engine = create_engine(f"sqlite:///{db_path}")
        Base.metadata.create_all(engine)

        # Verify database file exists
        assert Path(db_path).exists()

        # Verify tables were created
        inspector = inspect(engine)
        tables = inspector.get_table_names()

        # Check for essential tables
        assert "users" in tables
        assert "research_history" in tables
        assert "settings" in tables
        assert "research_resources" in tables
        assert "token_usage" in tables

        engine.dispose()

    def test_database_creation_with_function(self, temp_dir):
        """Test database creation through standard SQLAlchemy."""
        db_path = str(Path(temp_dir) / "test_user.db")

        # Create engine and initialize database
        engine = create_engine(f"sqlite:///{db_path}")
        Base.metadata.create_all(engine)

        # Verify engine is created
        assert engine is not None

        # Verify database exists
        assert Path(db_path).exists()

        # Test connection
        from sqlalchemy import text

        with engine.connect() as conn:
            result = conn.execute(text("SELECT 1"))
            assert result.fetchone()[0] == 1

        engine.dispose()

    def test_encrypted_database_creation(self, temp_dir, monkeypatch):
        """A user database created via DatabaseManager is real SQLCipher-encrypted.

        Uses the production creation path (DatabaseManager + sqlcipher3), not
        the obsolete ``sqlite+pysqlcipher://`` dialect, and skips only when a
        functional SQLCipher backend is genuinely unavailable -- checked via
        ``DatabaseManager.has_encryption`` rather than guessing a package name.
        The previous version probed ``pysqlcipher3`` (which this project does
        not install -- it uses ``sqlcipher3``), so it silently skipped
        everywhere, including CI.
        """
        from sqlalchemy import text
        from sqlalchemy.exc import DatabaseError

        from local_deep_research.database.encrypted_db import DatabaseManager

        monkeypatch.setattr(
            "local_deep_research.database.encrypted_db.get_data_directory",
            lambda: Path(temp_dir),
        )
        manager = DatabaseManager()
        if not manager.has_encryption:
            pytest.skip("Functional SQLCipher backend not available")

        username, password = "encuser", "TestPassword123!"
        engine = manager.create_user_database(username, password)
        plain_engine = None
        try:
            # The encrypted engine is functional.
            with engine.connect() as conn:
                assert conn.execute(text("SELECT 1")).scalar() == 1

            db_path = manager._get_user_db_path(username)
            assert db_path.exists()

            # Opening the same file as PLAINTEXT SQLite must fail with "file is
            # not a database": the bytes are encrypted, so SQLite cannot parse
            # the header. DatabaseError (SQLAlchemy's OperationalError subclasses
            # it) is specific enough that the test can't pass for an unrelated
            # reason -- the actual proof that encryption was applied.
            plain_engine = create_engine(f"sqlite:///{db_path}")
            with pytest.raises(DatabaseError):
                with plain_engine.connect() as conn:
                    conn.execute(text("SELECT name FROM sqlite_master"))
        finally:
            if plain_engine is not None:
                plain_engine.dispose()
            manager.close_user_database(username)

    def test_database_schema_completeness(self, db_engine):
        """Test that all expected tables and columns are created."""
        inspector = inspect(db_engine)

        # Test ResearchHistory table schema
        research_columns = {
            col["name"] for col in inspector.get_columns("research_history")
        }
        expected_columns = {
            "id",
            "query",
            "mode",
            "status",
            "created_at",
            "completed_at",
            "duration_seconds",
            "progress",
            "report_path",
            "report_content",
            "title",
            "progress_log",
            "research_meta",
        }
        assert expected_columns.issubset(research_columns)

        # Test User table schema
        user_columns = {col["name"] for col in inspector.get_columns("users")}
        expected_user_columns = {
            "id",
            "username",
            "created_at",
            "last_login",
            "database_version",
        }
        assert expected_user_columns.issubset(user_columns), (
            f"Missing columns: {expected_user_columns - user_columns}"
        )

        # Test Settings table schema
        settings_columns = {
            col["name"] for col in inspector.get_columns("settings")
        }
        expected_settings_columns = {
            "id",
            "key",
            "value",
            "type",
            "category",
            "description",
            "name",
            "ui_element",
            "options",
            "min_value",
            "max_value",
            "step",
            "visible",
            "editable",
            "created_at",
            "updated_at",
        }
        assert expected_settings_columns.issubset(settings_columns)

    def test_database_indexes(self, db_engine):
        """Test that proper indexes are created."""
        inspector = inspect(db_engine)

        # Check indexes on research_history
        research_indexes = inspector.get_indexes("research_history")
        # Should have indexes on commonly queried fields
        index_columns = set()
        for idx in research_indexes:
            index_columns.update(idx["column_names"])

        # Status should be indexed for filtering
        # Created_at should be indexed for sorting
        # These might be part of composite indexes

        # Check unique constraints
        # Username and email should have unique constraints in users table

    def test_database_foreign_keys(self, db_engine, db_session):
        """Test that foreign key relationships work correctly."""
        # Create a research record
        research = ResearchHistory(
            id=str(uuid.uuid4()),
            query="Test query",
            mode="quick",
            status="completed",
            created_at="2024-01-01T00:00:00",
        )
        db_session.add(research)
        db_session.commit()

        # Create related records
        from local_deep_research.database.models import (
            ResearchResource,
            TokenUsage,
        )

        # Add a resource
        resource = ResearchResource(
            research_id=research.id,
            title="Test Resource",
            url="https://example.com",
            created_at="2024-01-01T00:01:00",
        )
        db_session.add(resource)

        # Add token usage
        usage = TokenUsage(
            research_id=str(research.id),
            model_provider="openai",
            model_name="gpt-4",
            prompt_tokens=80,
            completion_tokens=20,
            total_tokens=100,
        )
        db_session.add(usage)

        db_session.commit()

        # Verify relationships
        assert resource.research_id == research.id
        assert usage.research_id == str(research.id)

    def test_database_cascade_deletes(self, db_engine, db_session):
        """Test cascade delete behavior."""
        # Create a benchmark run with results
        from local_deep_research.database.models import (
            BenchmarkResult,
            BenchmarkRun,
            DatasetType,
        )

        run = BenchmarkRun(
            config_hash="test123",
            query_hash_list=[],
            search_config={},
            evaluation_config={},
            datasets_config={},
        )
        db_session.add(run)
        db_session.commit()

        # Add results
        for i in range(3):
            result = BenchmarkResult(
                benchmark_run_id=run.id,
                example_id=f"test_{i}",
                query_hash=f"hash_{i}",
                dataset_type=DatasetType.SIMPLEQA,
                question=f"Question {i}",
                correct_answer=f"Answer {i}",
            )
            db_session.add(result)

        db_session.commit()

        # Verify results exist
        result_count = (
            db_session.query(BenchmarkResult)
            .filter_by(benchmark_run_id=run.id)
            .count()
        )
        assert result_count == 3

        # Delete the run
        db_session.delete(run)
        db_session.commit()

        # Verify cascade delete worked
        result_count = (
            db_session.query(BenchmarkResult)
            .filter_by(benchmark_run_id=run.id)
            .count()
        )
        assert result_count == 0

    def test_database_transactions(self, db_engine, db_session):
        """Test transaction rollback behavior."""
        # Add a user
        user = User(username="testuser")
        db_session.add(user)
        db_session.commit()

        # Start a transaction that will fail
        try:
            # Add another user with duplicate username (should fail)
            user2 = User(username="testuser")
            db_session.add(user2)

            # Add a valid setting
            setting = Setting(
                key="test.setting",
                value="test_value",
                type="string",
                category="test",
            )
            db_session.add(setting)

            # This should fail due to unique constraint
            db_session.commit()

        except Exception:
            db_session.rollback()

        # Verify rollback worked - setting should not exist
        setting_count = (
            db_session.query(Setting).filter_by(key="test.setting").count()
        )
        assert setting_count == 0

        # Original user should still exist
        user_count = db_session.query(User).count()
        assert user_count == 1

    def test_database_performance_with_large_dataset(
        self, db_engine, db_session
    ):
        """Test database performance with larger datasets."""
        # Add many research records
        research_count = 1000
        for i in range(research_count):
            research = ResearchHistory(
                id=str(uuid.uuid4()),
                query=f"Test query {i}",
                mode="quick",
                status="completed" if i % 2 == 0 else "failed",
                created_at=f"2024-01-{(i % 28) + 1:02d}T00:00:00",
                duration_seconds=100 + i % 500,
                progress=100 if i % 2 == 0 else 50,
            )
            db_session.add(research)

            # Commit in batches
            if i % 100 == 0:
                db_session.commit()

        db_session.commit()

        # Test query performance
        import time

        # Query completed research
        start = time.time()
        completed = (
            db_session.query(ResearchHistory)
            .filter_by(status="completed")
            .count()
        )
        query_time = time.time() - start

        assert completed == 500
        assert query_time < 0.1  # Should be fast with indexes

        # Test ordering
        start = time.time()
        recent = (
            db_session.query(ResearchHistory)
            .order_by(ResearchHistory.created_at.desc())
            .limit(10)
            .all()
        )
        order_time = time.time() - start

        assert len(recent) == 10
        assert order_time < 0.1

    def test_user_specific_database_path(self, temp_dir):
        """Test user-specific database paths for multi-user support."""
        # Test database path generation for different users
        user1_path = str(Path(temp_dir) / "user1" / "user1_encrypted.db")
        user2_path = str(Path(temp_dir) / "user2" / "user2_encrypted.db")

        # Create directories
        Path(user1_path).parent.mkdir(parents=True, exist_ok=True)
        Path(user2_path).parent.mkdir(parents=True, exist_ok=True)

        # Create separate databases
        engine1 = create_engine(f"sqlite:///{user1_path}")
        engine2 = create_engine(f"sqlite:///{user2_path}")

        Base.metadata.create_all(engine1)
        Base.metadata.create_all(engine2)

        # Add data to each
        Session1 = sessionmaker(bind=engine1)
        Session2 = sessionmaker(bind=engine2)

        session1 = Session1()
        session2 = Session2()

        # User 1 research
        research1 = ResearchHistory(
            id=str(uuid.uuid4()),
            query="User 1 research",
            mode="quick",
            status="completed",
            created_at="2024-01-01T00:00:00",
        )
        session1.add(research1)
        session1.commit()

        # User 2 research
        research2 = ResearchHistory(
            id=str(uuid.uuid4()),
            query="User 2 research",
            mode="quick",
            status="completed",
            created_at="2024-01-01T00:00:00",
        )
        session2.add(research2)
        session2.commit()

        # Verify isolation
        user1_count = session1.query(ResearchHistory).count()
        user2_count = session2.query(ResearchHistory).count()

        assert user1_count == 1
        assert user2_count == 1

        # Verify different content
        user1_research = session1.query(ResearchHistory).first()
        user2_research = session2.query(ResearchHistory).first()

        assert user1_research.query == "User 1 research"
        assert user2_research.query == "User 2 research"

        session1.close()
        session2.close()
        engine1.dispose()
        engine2.dispose()
