"""Integration tests for SqlAlchemyProjectRepository and SqlAlchemyProjectMembershipRepository."""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID, uuid4

import pytest

from noteflow.domain.entities.project import (
    ExportRules,
    Project,
    ProjectSettings,
    TriggerRules,
)
from noteflow.domain.errors import CannotArchiveDefaultProjectError
from noteflow.domain.identity.roles import ProjectRole
from noteflow.domain.value_objects import ExportFormat
from noteflow.infrastructure.persistence.models.identity import (
    UserModel,
    WorkspaceModel,
)
from noteflow.infrastructure.persistence.repositories.identity import (
    SqlAlchemyProjectMembershipRepository,
    SqlAlchemyProjectRepository,
)

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio import AsyncSession


async def _create_workspace(session: AsyncSession) -> WorkspaceModel:
    """Create a test workspace."""
    model = WorkspaceModel(
        id=uuid4(),
        name="Test Workspace",
        slug="test-workspace",
        metadata_={},
    )
    session.add(model)
    await session.flush()
    return model


async def _create_user(session: AsyncSession) -> UserModel:
    """Create a test user."""
    model = UserModel(
        id=uuid4(),
        display_name="Test User",
        email="test@example.com",
        metadata_={},
    )
    session.add(model)
    await session.flush()
    return model


@pytest.mark.integration
class TestProjectRepository:
    """Integration tests for SqlAlchemyProjectRepository."""

    async def test_create_and_get_project(self, session: AsyncSession) -> None:
        """Test creating and retrieving a project."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Test Project",
            slug="test-project",
            description="A test project",
        )
        await session.commit()

        retrieved = await repo.get(project.id)

        assert retrieved is not None, "project should exist after create"
        assert retrieved.id == project.id, "ID should match"
        assert retrieved.name == "Test Project", "name should match"
        assert retrieved.slug == "test-project", "slug should match"
        assert retrieved.description == "A test project", "description should match"

    async def test_get_project_not_found_repository(self, session: AsyncSession) -> None:
        """Test retrieving non-existent project returns None."""
        repo = SqlAlchemyProjectRepository(session)

        result = await repo.get(uuid4())

        assert result is None, "non-existent project should return None"

    async def test_create_project_with_settings_repository(self, session: AsyncSession) -> None:
        """Test creating project with full settings."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        template_id = uuid4()
        settings = ProjectSettings(
            export_rules=ExportRules(
                default_format=ExportFormat.PDF,
                include_audio=True,
                include_timestamps=True,
                template_id=template_id,
            ),
            trigger_rules=TriggerRules(
                auto_start_enabled=True,
                calendar_match_patterns=["*standup*"],
                app_match_patterns=["Zoom"],
            ),
            rag_enabled=True,
            default_summarization_template="professional",
        )

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Configured Project",
            settings=settings,
        )
        await session.commit()

        retrieved = await repo.get(project.id)

        assert retrieved is not None, "project should exist"
        assert retrieved.settings.rag_enabled is True, "rag_enabled should be preserved"
        assert retrieved.settings.default_summarization_template == "professional", "default_summarization_template should be 'professional'"
        assert retrieved.settings.export_rules is not None, "export_rules should not be None"
        assert retrieved.settings.export_rules.default_format == ExportFormat.PDF, "default_format should be PDF"
        assert retrieved.settings.export_rules.include_audio is True, "include_audio should be True"
        assert retrieved.settings.export_rules.template_id == template_id, f"template_id should be {template_id}"
        assert retrieved.settings.trigger_rules is not None, "trigger_rules should not be None"
        assert retrieved.settings.trigger_rules.auto_start_enabled is True, "auto_start_enabled should be True"
        assert retrieved.settings.trigger_rules.calendar_match_patterns == ["*standup*"], "calendar_match_patterns should be preserved"

    async def test_get_by_slug(self, session: AsyncSession) -> None:
        """Test retrieving project by workspace and slug."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Slugged Project",
            slug="my-slug",
        )
        await session.commit()

        result = await repo.get_by_slug(workspace.id, "my-slug")

        assert result is not None, "project should be found by slug"
        assert result.slug == "my-slug", "slug should match"
        assert result.name == "Slugged Project", "name should match"

    async def test_get_by_slug_not_found(self, session: AsyncSession) -> None:
        """Test get_by_slug returns None for non-existent slug."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        result = await repo.get_by_slug(workspace.id, "nonexistent")

        assert result is None, "non-existent project should return None"

    async def test_get_default_for_workspace(self, session: AsyncSession) -> None:
        """Test retrieving default project for workspace."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        # Create non-default and default projects
        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Regular Project",
            is_default=False,
        )
        default_project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Default Project",
            is_default=True,
        )
        await session.commit()

        result = await repo.get_default_for_workspace(workspace.id)

        assert result is not None, "default project should exist"
        assert result.id == default_project.id, "should return default project"
        assert result.is_default is True, "is_default should be True"

    async def test_get_default_for_workspace_none(self, session: AsyncSession) -> None:
        """Test get_default_for_workspace returns None when no default exists."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        result = await repo.get_default_for_workspace(workspace.id)

        assert result is None, "non-existent project should return None"

    async def test_update_project(self, session: AsyncSession) -> None:
        """Test updating a project."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Original Name",
        )
        await session.commit()

        # Update via domain entity
        retrieved = await repo.get(project.id)
        assert retrieved is not None, "project should exist for update"
        retrieved.update_name("Updated Name")
        retrieved.update_description("New description")
        retrieved.update_settings(ProjectSettings(rag_enabled=True))

        await repo.update(retrieved)
        await session.commit()

        final = await repo.get(project.id)
        assert final is not None, "updated project should exist"
        assert final.name == "Updated Name", "name should be updated to 'Updated Name'"
        assert final.description == "New description", "description should be updated"
        assert final.settings.rag_enabled is True, "rag_enabled setting should be True after update"

    async def test_update_project_not_found_raises(self, session: AsyncSession) -> None:
        """Test update raises when project does not exist."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = Project(
            id=uuid4(),
            workspace_id=workspace.id,
            name="Missing Project",
        )

        with pytest.raises(ValueError, match="not found"):
            await repo.update(project)

    async def test_archive_project(self, session: AsyncSession) -> None:
        """Test archiving a project."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="To Archive",
        )
        await session.commit()

        result = await repo.archive(project.id)
        await session.commit()

        assert result is not None, "archive should return project"
        assert result.archived_at is not None, "archived_at should be set"
        assert result.is_archived is True, "is_archived should be True"

    async def test_archive_default_project_raises_repository(self, session: AsyncSession) -> None:
        """Test archiving default project raises error."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Default Project",
            is_default=True,
        )
        await session.commit()

        # Get fresh reference
        default = await repo.get_default_for_workspace(workspace.id)
        assert default is not None, "default project should exist before archive attempt"

        with pytest.raises(CannotArchiveDefaultProjectError, match="Cannot archive"):
            await repo.archive(default.id)

    async def test_archive_project_not_found_repository(self, session: AsyncSession) -> None:
        """Test archiving non-existent project returns None."""
        repo = SqlAlchemyProjectRepository(session)

        result = await repo.archive(uuid4())

        assert result is None, "non-existent project should return None"

    async def test_restore_project(self, session: AsyncSession) -> None:
        """Test restoring an archived project."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="To Restore",
        )
        await session.commit()

        await repo.archive(project.id)
        await session.commit()

        result = await repo.restore(project.id)
        await session.commit()

        assert result is not None, "restore should return project"
        assert result.archived_at is None, "archived_at should be cleared"
        assert result.is_active is True, "is_active should be True"

    async def test_restore_project_not_found_repository(self, session: AsyncSession) -> None:
        """Test restoring non-existent project returns None."""
        repo = SqlAlchemyProjectRepository(session)

        result = await repo.restore(uuid4())

        assert result is None, "non-existent project should return None"

    async def test_delete_project(self, session: AsyncSession) -> None:
        """Test deleting a project."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="To Delete",
        )
        await session.commit()

        result = await repo.delete(project.id)
        await session.commit()

        assert result is True, "delete should return True for existing project"

        retrieved = await repo.get(project.id)
        assert retrieved is None, "deleted project should not be retrievable"

    async def test_delete_project_not_found(self, session: AsyncSession) -> None:
        """Test deleting non-existent project returns False."""
        repo = SqlAlchemyProjectRepository(session)

        result = await repo.delete(uuid4())

        assert result is False, "delete should return False for non-existent project"

    async def test_list_for_workspace(self, session: AsyncSession) -> None:
        """Test listing projects in a workspace."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        # Create multiple projects
        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Alpha Project",
        )
        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Zeta Project",
        )
        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Default Project",
            is_default=True,
        )
        await session.commit()

        result = await repo.list_for_workspace(workspace.id)

        assert len(result) == 3, f"expected 3 projects, got {len(result)}"
        # Default project should come first, then alphabetically
        assert result[0].is_default is True, "first project should be default"
        assert result[0].name == "Default Project", f"first project should be 'Default Project', got '{result[0].name}'"

    async def test_list_for_workspace_excludes_archived(self, session: AsyncSession) -> None:
        """Test list_for_workspace excludes archived by default."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        active = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Active Project",
        )
        archived = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Archived Project",
        )
        await session.commit()

        await repo.archive(archived.id)
        await session.commit()

        result = await repo.list_for_workspace(workspace.id, include_archived=False)

        assert len(result) == 1, f"expected 1 active project, got {len(result)}"
        assert result[0].id == active.id, f"expected active project ID {active.id}, got {result[0].id}"

    async def test_list_for_workspace_includes_archived(self, session: AsyncSession) -> None:
        """Test list_for_workspace can include archived projects."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Active Project",
        )
        archived = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Archived Project",
        )
        await session.commit()

        await repo.archive(archived.id)
        await session.commit()

        result = await repo.list_for_workspace(workspace.id, include_archived=True)

        assert len(result) == 2, f"expected 2 projects (including archived), got {len(result)}"

    async def test_list_for_workspace_pagination(self, session: AsyncSession) -> None:
        """Test list_for_workspace with pagination."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        # Create 5 projects
        for i in range(5):
            await repo.create(
                project_id=uuid4(),
                workspace_id=workspace.id,
                name=f"Project {i:02d}",
            )
        await session.commit()

        result = await repo.list_for_workspace(workspace.id, limit=2, offset=0)
        assert len(result) == 2, "first page should be full"

        result = await repo.list_for_workspace(workspace.id, limit=2, offset=2)
        assert len(result) == 2, "second page should be full"

        result = await repo.list_for_workspace(workspace.id, limit=2, offset=4)
        assert len(result) == 1, "last page has remainder"

    async def test_count_for_workspace(self, session: AsyncSession) -> None:
        """Test counting projects in a workspace."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        # Create 3 active + 1 archived
        for _ in range(3):
            await repo.create(
                project_id=uuid4(),
                workspace_id=workspace.id,
                name="Active",
            )
        archived = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Archived",
        )
        await session.commit()

        await repo.archive(archived.id)
        await session.commit()

        count_active = await repo.count_for_workspace(workspace.id, include_archived=False)
        count_all = await repo.count_for_workspace(workspace.id, include_archived=True)

        assert count_active == 3, f"expected 3 active projects, got {count_active}"
        assert count_all == 4, f"expected 4 total projects, got {count_all}"


@pytest.mark.integration
class TestProjectMembershipRepository:
    """Integration tests for SqlAlchemyProjectMembershipRepository."""

    async def _create_project(
        self,
        session: AsyncSession,
        workspace: WorkspaceModel,
    ) -> Project:
        """Create a test project."""
        repo = SqlAlchemyProjectRepository(session)
        return await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Test Project",
        )

    async def test_add_and_get_membership(self, session: AsyncSession) -> None:
        """Test adding and retrieving a project membership."""
        workspace = await _create_workspace(session)
        user = await _create_user(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        repo = SqlAlchemyProjectMembershipRepository(session)
        _membership = await repo.add(project.id, user.id, ProjectRole.EDITOR)
        await session.commit()

        retrieved = await repo.get(project.id, user.id)

        assert retrieved is not None, "membership should exist after add"
        assert retrieved.project_id == project.id, "project_id should match"
        assert retrieved.user_id == user.id, "user_id should match"
        assert retrieved.role == ProjectRole.EDITOR, "role should match"
        assert retrieved.joined_at is not None, "joined_at should be set"

    async def test_get_membership_not_found(self, session: AsyncSession) -> None:
        """Test get returns None for non-existent membership."""
        repo = SqlAlchemyProjectMembershipRepository(session)

        result = await repo.get(uuid4(), uuid4())

        assert result is None, "non-existent membership should return None"

    async def test_update_role(self, session: AsyncSession) -> None:
        """Test updating a member's role."""
        workspace = await _create_workspace(session)
        user = await _create_user(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        repo = SqlAlchemyProjectMembershipRepository(session)
        await repo.add(project.id, user.id, ProjectRole.VIEWER)
        await session.commit()

        result = await repo.update_role(project.id, user.id, ProjectRole.ADMIN)
        await session.commit()

        assert result is not None, "update_role should return membership"
        assert result.role == ProjectRole.ADMIN, "role should be updated"

        # Verify persistence
        retrieved = await repo.get(project.id, user.id)
        assert retrieved is not None, "membership should exist after update"
        assert retrieved.role == ProjectRole.ADMIN, "persisted role should be ADMIN"

    async def test_update_role_not_found(self, session: AsyncSession) -> None:
        """Test update_role returns None for non-existent membership."""
        repo = SqlAlchemyProjectMembershipRepository(session)

        result = await repo.update_role(uuid4(), uuid4(), ProjectRole.ADMIN)

        assert result is None, "non-existent membership should return None"

    async def test_remove_membership(self, session: AsyncSession) -> None:
        """Test removing a project membership."""
        workspace = await _create_workspace(session)
        user = await _create_user(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        repo = SqlAlchemyProjectMembershipRepository(session)
        await repo.add(project.id, user.id, ProjectRole.EDITOR)
        await session.commit()

        result = await repo.remove(project.id, user.id)
        await session.commit()

        assert result is True, "remove should return True for existing membership"

        retrieved = await repo.get(project.id, user.id)
        assert retrieved is None, "removed membership should not be retrievable"

    async def test_remove_membership_not_found(self, session: AsyncSession) -> None:
        """Test remove returns False for non-existent membership."""
        repo = SqlAlchemyProjectMembershipRepository(session)

        result = await repo.remove(uuid4(), uuid4())

        assert result is False, "remove should return False for non-existent membership"

    async def test_list_for_project(self, session: AsyncSession) -> None:
        """Test listing all members of a project."""
        workspace = await _create_workspace(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        # Create multiple users
        users: list[UserModel] = []
        for i in range(3):
            user = UserModel(
                id=uuid4(),
                display_name=f"User {i}",
                email=f"user{i}@example.com",
                metadata_={},
            )
            session.add(user)
            users.append(user)
        await session.flush()

        repo = SqlAlchemyProjectMembershipRepository(session)
        roles = [ProjectRole.ADMIN, ProjectRole.EDITOR, ProjectRole.VIEWER]
        for user, role in zip(users, roles, strict=False):
            await repo.add(project.id, user.id, role)
        await session.commit()

        result = await repo.list_for_project(project.id)

        assert len(result) == 3, f"expected 3 members, got {len(result)}"
        roles_found = {m.role for m in result}
        assert roles_found == {ProjectRole.ADMIN, ProjectRole.EDITOR, ProjectRole.VIEWER}, "all roles should be present"

    async def test_list_for_project_pagination(self, session: AsyncSession) -> None:
        """Test list_for_project with pagination."""
        workspace = await _create_workspace(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        # Create 5 users and memberships
        repo = SqlAlchemyProjectMembershipRepository(session)
        for i in range(5):
            user = UserModel(
                id=uuid4(),
                display_name=f"User {i}",
                email=f"user{i}@example.com",
                metadata_={},
            )
            session.add(user)
            await session.flush()
            await repo.add(project.id, user.id, ProjectRole.VIEWER)
        await session.commit()

        result = await repo.list_for_project(project.id, limit=2, offset=0)
        assert len(result) == 2, "first page should have 2 members"

        result = await repo.list_for_project(project.id, limit=2, offset=4)
        assert len(result) == 1, "last page should have 1 member"

    async def test_list_for_user(self, session: AsyncSession) -> None:
        """Test listing all projects a user is a member of."""
        workspace = await _create_workspace(session)
        user = await _create_user(session)
        await session.commit()

        # Create multiple projects with user membership
        project_repo = SqlAlchemyProjectRepository(session)
        membership_repo = SqlAlchemyProjectMembershipRepository(session)

        for i in range(3):
            project = await project_repo.create(
                project_id=uuid4(),
                workspace_id=workspace.id,
                name=f"Project {i}",
            )
            await membership_repo.add(project.id, user.id, ProjectRole.EDITOR)
        await session.commit()

        result = await membership_repo.list_for_user(user.id)

        assert len(result) == 3, f"expected 3 memberships, got {len(result)}"
        assert all(m.user_id == user.id for m in result), "all memberships should belong to user"

    async def test_list_for_user_filtered_by_workspace(self, session: AsyncSession) -> None:
        """Test list_for_user can filter by workspace."""
        # Create two workspaces
        workspace1 = await _create_workspace(session)
        workspace2 = WorkspaceModel(
            id=uuid4(),
            name="Second Workspace",
            slug="second-workspace",
            metadata_={},
        )
        session.add(workspace2)
        user = await _create_user(session)
        await session.commit()

        project_repo = SqlAlchemyProjectRepository(session)
        membership_repo = SqlAlchemyProjectMembershipRepository(session)

        # Projects in workspace1
        for i in range(2):
            project = await project_repo.create(
                project_id=uuid4(),
                workspace_id=workspace1.id,
                name=f"W1 Project {i}",
            )
            await membership_repo.add(project.id, user.id, ProjectRole.VIEWER)

        # Project in workspace2
        project = await project_repo.create(
            project_id=uuid4(),
            workspace_id=workspace2.id,
            name="W2 Project",
        )
        await membership_repo.add(project.id, user.id, ProjectRole.ADMIN)
        await session.commit()

        # Filter by workspace1
        result = await membership_repo.list_for_user(user.id, workspace_id=workspace1.id)
        assert len(result) == 2, f"expected 2 memberships in workspace1, got {len(result)}"

        # Filter by workspace2
        result = await membership_repo.list_for_user(user.id, workspace_id=workspace2.id)
        assert len(result) == 1, f"expected 1 membership in workspace2, got {len(result)}"
        assert result[0].role == ProjectRole.ADMIN, "workspace2 membership should have ADMIN role"

    async def test_bulk_add_memberships(self, session: AsyncSession) -> None:
        """Test bulk adding memberships."""
        workspace = await _create_workspace(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        # Create users
        users: list[UserModel] = []
        for i in range(3):
            user = UserModel(
                id=uuid4(),
                display_name=f"User {i}",
                email=f"user{i}@example.com",
                metadata_={},
            )
            session.add(user)
            users.append(user)
        await session.flush()

        repo = SqlAlchemyProjectMembershipRepository(session)
        memberships: list[tuple[UUID, ProjectRole]] = [
            (users[0].id, ProjectRole.ADMIN),
            (users[1].id, ProjectRole.EDITOR),
            (users[2].id, ProjectRole.VIEWER),
        ]
        result = await repo.bulk_add(project.id, memberships)
        await session.commit()

        assert len(result) == 3, f"expected 3 memberships created, got {len(result)}"
        roles = {m.role for m in result}
        assert roles == {ProjectRole.ADMIN, ProjectRole.EDITOR, ProjectRole.VIEWER}, "all roles should be present"

    async def test_count_for_project(self, session: AsyncSession) -> None:
        """Test counting members in a project."""
        workspace = await _create_workspace(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        # Create users and add memberships
        repo = SqlAlchemyProjectMembershipRepository(session)
        for i in range(5):
            user = UserModel(
                id=uuid4(),
                display_name=f"User {i}",
                email=f"user{i}@example.com",
                metadata_={},
            )
            session.add(user)
            await session.flush()
            await repo.add(project.id, user.id, ProjectRole.VIEWER)
        await session.commit()

        count = await repo.count_for_project(project.id)

        assert count == 5, "Should count all members in project"

    async def test_count_for_project_empty(self, session: AsyncSession) -> None:
        """Test counting members returns 0 for empty project."""
        workspace = await _create_workspace(session)
        project = await self._create_project(session, workspace)
        await session.commit()

        repo = SqlAlchemyProjectMembershipRepository(session)
        count = await repo.count_for_project(project.id)

        assert count == 0, "Should return 0 for project with no members"

    async def test_count_for_project_nonexistent(self, session: AsyncSession) -> None:
        """Test counting members returns 0 for non-existent project."""
        repo = SqlAlchemyProjectMembershipRepository(session)

        count = await repo.count_for_project(uuid4())

        assert count == 0, "Should return 0 for non-existent project"


@pytest.mark.integration
class TestProjectSettingsJsonbRoundTrip:
    """Tests for ProjectSettings JSONB serialization round-trip."""

    async def test_empty_settings_roundtrip(self, session: AsyncSession) -> None:
        """Test empty settings persist and restore correctly."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Empty Settings",
        )
        await session.commit()

        retrieved = await repo.get(project.id)
        assert retrieved is not None, "project should exist after creation"
        assert retrieved.settings.export_rules is None, "export_rules should be None for empty settings"
        assert retrieved.settings.trigger_rules is None, "trigger_rules should be None for empty settings"
        assert retrieved.settings.rag_enabled is None, "rag_enabled should be None for empty settings"

    async def test_partial_settings_roundtrip(self, session: AsyncSession) -> None:
        """Test partially populated settings persist correctly."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        settings = ProjectSettings(
            export_rules=ExportRules(default_format=ExportFormat.HTML),
            rag_enabled=False,
        )

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Partial Settings",
            settings=settings,
        )
        await session.commit()

        retrieved = await repo.get(project.id)
        assert retrieved is not None, "project should exist after creation"
        assert retrieved.settings.export_rules is not None, "export_rules should not be None"
        assert retrieved.settings.export_rules.default_format == ExportFormat.HTML, "default_format should be HTML"
        assert retrieved.settings.export_rules.include_audio is None, "include_audio should be None when not set"
        assert retrieved.settings.trigger_rules is None, "trigger_rules should be None when not set"
        assert retrieved.settings.rag_enabled is False, "rag_enabled should be False as configured"

    async def test_trigger_rules_with_empty_lists(self, session: AsyncSession) -> None:
        """Test empty lists in trigger rules are preserved (explicit clear)."""
        workspace = await _create_workspace(session)
        repo = SqlAlchemyProjectRepository(session)

        settings = ProjectSettings(
            trigger_rules=TriggerRules(
                auto_start_enabled=False,
                calendar_match_patterns=[],
                app_match_patterns=[],
            ),
        )

        project = await repo.create(
            project_id=uuid4(),
            workspace_id=workspace.id,
            name="Empty Lists",
            settings=settings,
        )
        await session.commit()

        retrieved = await repo.get(project.id)
        assert retrieved is not None, "project should exist after creation"
        assert retrieved.settings.trigger_rules is not None, "trigger_rules should not be None"
        assert retrieved.settings.trigger_rules.auto_start_enabled is False, "auto_start_enabled should be False"
        assert retrieved.settings.trigger_rules.calendar_match_patterns == [], "empty calendar_match_patterns should be preserved"
        assert retrieved.settings.trigger_rules.app_match_patterns == [], "empty app_match_patterns should be preserved"
