"""
InsightFlow - 协作与共享模块 (Phase 7 Task 4)
支持项目分享、评论批注、变更历史、团队空间
"""

import hashlib
import json
import uuid
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Any


class SharePermission(Enum):
    """分享权限级别"""

    READ_ONLY = "read_only"  # 只读
    COMMENT = "comment"  # 可评论
    EDIT = "edit"  # 可编辑
    ADMIN = "admin"  # 管理员


class CommentTargetType(Enum):
    """评论目标类型"""

    ENTITY = "entity"  # 实体评论
    RELATION = "relation"  # 关系评论
    TRANSCRIPT = "transcript"  # 转录文本评论
    PROJECT = "project"  # 项目级评论


class ChangeType(Enum):
    """变更类型"""

    CREATE = "create"  # 创建
    UPDATE = "update"  # 更新
    DELETE = "delete"  # 删除
    MERGE = "merge"  # 合并
    SPLIT = "split"  # 拆分


@dataclass
class ProjectShare:
    """项目分享链接"""

    id: str
    project_id: str
    token: str  # 分享令牌
    permission: str  # 权限级别
    created_by: str  # 创建者
    created_at: str
    expires_at: str | None  # 过期时间
    max_uses: int | None  # 最大使用次数
    use_count: int  # 已使用次数
    password_hash: str | None  # 密码保护
    is_active: bool  # 是否激活
    allow_download: bool  # 允许下载
    allow_export: bool  # 允许导出


@dataclass
class Comment:
    """评论/批注"""

    id: str
    project_id: str
    target_type: str  # 评论目标类型
    target_id: str  # 目标ID
    parent_id: str | None  # 父评论ID（支持回复）
    author: str  # 作者
    author_name: str  # 作者显示名
    content: str  # 评论内容
    created_at: str
    updated_at: str
    resolved: bool  # 是否已解决
    resolved_by: str | None  # 解决者
    resolved_at: str | None  # 解决时间
    mentions: list[str]  # 提及的用户
    attachments: list[dict]  # 附件


@dataclass
class ChangeRecord:
    """变更记录"""

    id: str
    project_id: str
    change_type: str  # 变更类型
    entity_type: str  # 实体类型 (entity/relation/transcript/project)
    entity_id: str  # 实体ID
    entity_name: str  # 实体名称（用于显示）
    changed_by: str  # 变更者
    changed_by_name: str  # 变更者显示名
    changed_at: str
    old_value: dict | None  # 旧值
    new_value: dict | None  # 新值
    description: str  # 变更描述
    session_id: str | None  # 会话ID（批量变更关联）
    reverted: bool  # 是否已回滚
    reverted_at: str | None  # 回滚时间
    reverted_by: str | None  # 回滚者


@dataclass
class TeamMember:
    """团队成员"""

    id: str
    project_id: str
    user_id: str  # 用户ID
    user_name: str  # 用户名
    user_email: str  # 用户邮箱
    role: str  # 角色 (owner/admin/editor/viewer)
    joined_at: str
    invited_by: str  # 邀请者
    last_active_at: str | None  # 最后活跃时间
    permissions: list[str]  # 具体权限列表


@dataclass
class TeamSpace:
    """团队空间"""

    id: str
    name: str
    description: str
    created_by: str
    created_at: str
    updated_at: str
    member_count: int
    project_count: int
    settings: dict[str, Any]  # 团队设置


class CollaborationManager:
    """协作管理主类"""

    def __init__(self, db_manager=None):
        self.db = db_manager
        self._shares_cache: dict[str, ProjectShare] = {}
        self._comments_cache: dict[str, list[Comment]] = {}

    # ============ 项目分享 ============

    def create_share_link(
        self,
        project_id: str,
        created_by: str,
        permission: str = "read_only",
        expires_in_days: int | None = None,
        max_uses: int | None = None,
        password: str | None = None,
        allow_download: bool = False,
        allow_export: bool = False,
    ) -> ProjectShare:
        """创建项目分享链接"""
        share_id = str(uuid.uuid4())
        token = self._generate_share_token(project_id)

        now = datetime.now().isoformat()
        expires_at = None
        if expires_in_days:
            expires_at = (datetime.now() + timedelta(days=expires_in_days)).isoformat()

        password_hash = None
        if password:
            password_hash = hashlib.sha256(password.encode()).hexdigest()

        share = ProjectShare(
            id=share_id,
            project_id=project_id,
            token=token,
            permission=permission,
            created_by=created_by,
            created_at=now,
            expires_at=expires_at,
            max_uses=max_uses,
            use_count=0,
            password_hash=password_hash,
            is_active=True,
            allow_download=allow_download,
            allow_export=allow_export,
        )

        # 保存到数据库
        if self.db:
            self._save_share_to_db(share)

        self._shares_cache[token] = share
        return share

    def _generate_share_token(self, project_id: str) -> str:
        """生成分享令牌"""
        data = f"{project_id}:{datetime.now().timestamp()}:{uuid.uuid4()}"
        return hashlib.sha256(data.encode()).hexdigest()[:32]

    def _save_share_to_db(self, share: ProjectShare) -> None:
        """保存分享记录到数据库"""
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            INSERT INTO project_shares
            (id, project_id, token, permission, created_by, created_at,
             expires_at, max_uses, use_count, password_hash, is_active,
             allow_download, allow_export)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
            (
                share.id,
                share.project_id,
                share.token,
                share.permission,
                share.created_by,
                share.created_at,
                share.expires_at,
                share.max_uses,
                share.use_count,
                share.password_hash,
                share.is_active,
                share.allow_download,
                share.allow_export,
            ),
        )
        self.db.conn.commit()

    def validate_share_token(self, token: str, password: str | None = None) -> ProjectShare | None:
        """验证分享令牌"""
        # 从缓存或数据库获取
        share = self._shares_cache.get(token)
        if not share and self.db:
            share = self._get_share_from_db(token)

        if not share:
            return None

        # 检查是否激活
        if not share.is_active:
            return None

        # 检查是否过期
        if share.expires_at and datetime.now().isoformat() > share.expires_at:
            return None

        # 检查使用次数
        if share.max_uses and share.use_count >= share.max_uses:
            return None

        # 验证密码
        if share.password_hash:
            if not password:
                return None
            password_hash = hashlib.sha256(password.encode()).hexdigest()
            if password_hash != share.password_hash:
                return None

        return share

    def _get_share_from_db(self, token: str) -> ProjectShare | None:
        """从数据库获取分享记录"""
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            SELECT * FROM project_shares WHERE token = ?
        """,
            (token,),
        )
        row = cursor.fetchone()

        if not row:
            return None

        return ProjectShare(
            id=row[0],
            project_id=row[1],
            token=row[2],
            permission=row[3],
            created_by=row[4],
            created_at=row[5],
            expires_at=row[6],
            max_uses=row[7],
            use_count=row[8],
            password_hash=row[9],
            is_active=bool(row[10]),
            allow_download=bool(row[11]),
            allow_export=bool(row[12]),
        )

    def increment_share_usage(self, token: str) -> None:
        """增加分享链接使用次数"""
        share = self._shares_cache.get(token)
        if share:
            share.use_count += 1

        if self.db:
            cursor = self.db.conn.cursor()
            cursor.execute(
                """
                UPDATE project_shares
                SET use_count = use_count + 1
                WHERE token = ?
            """,
                (token,),
            )
            self.db.conn.commit()

    def revoke_share_link(self, share_id: str, revoked_by: str) -> bool:
        """撤销分享链接"""
        if self.db:
            cursor = self.db.conn.cursor()
            cursor.execute(
                """
                UPDATE project_shares
                SET is_active = 0
                WHERE id = ?
            """,
                (share_id,),
            )
            self.db.conn.commit()
            return cursor.rowcount > 0
        return False

    def list_project_shares(self, project_id: str) -> list[ProjectShare]:
        """列出项目的所有分享链接"""
        if not self.db:
            return []

        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            SELECT * FROM project_shares
            WHERE project_id = ?
            ORDER BY created_at DESC
        """,
            (project_id,),
        )

        shares = []
        for row in cursor.fetchall():
            shares.append(
                ProjectShare(
                    id=row[0],
                    project_id=row[1],
                    token=row[2],
                    permission=row[3],
                    created_by=row[4],
                    created_at=row[5],
                    expires_at=row[6],
                    max_uses=row[7],
                    use_count=row[8],
                    password_hash=row[9],
                    is_active=bool(row[10]),
                    allow_download=bool(row[11]),
                    allow_export=bool(row[12]),
                )
            )
        return shares

    # ============ 评论和批注 ============

    def add_comment(
        self,
        project_id: str,
        target_type: str,
        target_id: str,
        author: str,
        author_name: str,
        content: str,
        parent_id: str | None = None,
        mentions: list[str] | None = None,
        attachments: list[dict] | None = None,
    ) -> Comment:
        """添加评论"""
        comment_id = str(uuid.uuid4())
        now = datetime.now().isoformat()

        comment = Comment(
            id=comment_id,
            project_id=project_id,
            target_type=target_type,
            target_id=target_id,
            parent_id=parent_id,
            author=author,
            author_name=author_name,
            content=content,
            created_at=now,
            updated_at=now,
            resolved=False,
            resolved_by=None,
            resolved_at=None,
            mentions=mentions or [],
            attachments=attachments or [],
        )

        if self.db:
            self._save_comment_to_db(comment)

        # 更新缓存
        key = f"{target_type}:{target_id}"
        if key not in self._comments_cache:
            self._comments_cache[key] = []
        self._comments_cache[key].append(comment)

        return comment

    def _save_comment_to_db(self, comment: Comment) -> None:
        """保存评论到数据库"""
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            INSERT INTO comments
            (id, project_id, target_type, target_id, parent_id, author, author_name,
             content, created_at, updated_at, resolved, resolved_by, resolved_at,
             mentions, attachments)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
            (
                comment.id,
                comment.project_id,
                comment.target_type,
                comment.target_id,
                comment.parent_id,
                comment.author,
                comment.author_name,
                comment.content,
                comment.created_at,
                comment.updated_at,
                comment.resolved,
                comment.resolved_by,
                comment.resolved_at,
                json.dumps(comment.mentions),
                json.dumps(comment.attachments),
            ),
        )
        self.db.conn.commit()

    def get_comments(
        self, target_type: str, target_id: str, include_resolved: bool = True
    ) -> list[Comment]:
        """获取评论列表"""
        if not self.db:
            return []

        cursor = self.db.conn.cursor()
        if include_resolved:
            cursor.execute(
                """
                SELECT * FROM comments
                WHERE target_type = ? AND target_id = ?
                ORDER BY created_at ASC
            """,
                (target_type, target_id),
            )
        else:
            cursor.execute(
                """
                SELECT * FROM comments
                WHERE target_type = ? AND target_id = ? AND resolved = 0
                ORDER BY created_at ASC
            """,
                (target_type, target_id),
            )

        comments = []
        for row in cursor.fetchall():
            comments.append(self._row_to_comment(row))
        return comments

    def _row_to_comment(self, row) -> Comment:
        """将数据库行转换为Comment对象"""
        return Comment(
            id=row[0],
            project_id=row[1],
            target_type=row[2],
            target_id=row[3],
            parent_id=row[4],
            author=row[5],
            author_name=row[6],
            content=row[7],
            created_at=row[8],
            updated_at=row[9],
            resolved=bool(row[10]),
            resolved_by=row[11],
            resolved_at=row[12],
            mentions=json.loads(row[13]) if row[13] else [],
            attachments=json.loads(row[14]) if row[14] else [],
        )

    def update_comment(self, comment_id: str, content: str, updated_by: str) -> Comment | None:
        """更新评论"""
        if not self.db:
            return None

        now = datetime.now().isoformat()
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            UPDATE comments
            SET content = ?, updated_at = ?
            WHERE id = ? AND author = ?
        """,
            (content, now, comment_id, updated_by),
        )
        self.db.conn.commit()

        if cursor.rowcount > 0:
            return self._get_comment_by_id(comment_id)
        return None

    def _get_comment_by_id(self, comment_id: str) -> Comment | None:
        """根据ID获取评论"""
        cursor = self.db.conn.cursor()
        cursor.execute("SELECT * FROM comments WHERE id = ?", (comment_id,))
        row = cursor.fetchone()
        if row:
            return self._row_to_comment(row)
        return None

    def resolve_comment(self, comment_id: str, resolved_by: str) -> bool:
        """标记评论为已解决"""
        if not self.db:
            return False

        now = datetime.now().isoformat()
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            UPDATE comments
            SET resolved = 1, resolved_by = ?, resolved_at = ?
            WHERE id = ?
        """,
            (resolved_by, now, comment_id),
        )
        self.db.conn.commit()
        return cursor.rowcount > 0

    def delete_comment(self, comment_id: str, deleted_by: str) -> bool:
        """删除评论"""
        if not self.db:
            return False

        cursor = self.db.conn.cursor()
        # 只允许作者或管理员删除
        cursor.execute(
            """
            DELETE FROM comments
            WHERE id = ? AND (author = ? OR ? IN (
                SELECT created_by FROM projects WHERE id = comments.project_id
            ))
        """,
            (comment_id, deleted_by, deleted_by),
        )
        self.db.conn.commit()
        return cursor.rowcount > 0

    def get_project_comments(
        self, project_id: str, limit: int = 50, offset: int = 0
    ) -> list[Comment]:
        """获取项目下的所有评论"""
        if not self.db:
            return []

        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            SELECT * FROM comments
            WHERE project_id = ?
            ORDER BY created_at DESC
            LIMIT ? OFFSET ?
        """,
            (project_id, limit, offset),
        )

        comments = []
        for row in cursor.fetchall():
            comments.append(self._row_to_comment(row))
        return comments

    # ============ 变更历史 ============

    def record_change(
        self,
        project_id: str,
        change_type: str,
        entity_type: str,
        entity_id: str,
        entity_name: str,
        changed_by: str,
        changed_by_name: str,
        old_value: dict | None = None,
        new_value: dict | None = None,
        description: str = "",
        session_id: str | None = None,
    ) -> ChangeRecord:
        """记录变更"""
        record_id = str(uuid.uuid4())
        now = datetime.now().isoformat()

        record = ChangeRecord(
            id=record_id,
            project_id=project_id,
            change_type=change_type,
            entity_type=entity_type,
            entity_id=entity_id,
            entity_name=entity_name,
            changed_by=changed_by,
            changed_by_name=changed_by_name,
            changed_at=now,
            old_value=old_value,
            new_value=new_value,
            description=description,
            session_id=session_id,
            reverted=False,
            reverted_at=None,
            reverted_by=None,
        )

        if self.db:
            self._save_change_to_db(record)

        return record

    def _save_change_to_db(self, record: ChangeRecord) -> None:
        """保存变更记录到数据库"""
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            INSERT INTO change_history
            (id, project_id, change_type, entity_type, entity_id, entity_name,
             changed_by, changed_by_name, changed_at, old_value, new_value,
             description, session_id, reverted, reverted_at, reverted_by)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
            (
                record.id,
                record.project_id,
                record.change_type,
                record.entity_type,
                record.entity_id,
                record.entity_name,
                record.changed_by,
                record.changed_by_name,
                record.changed_at,
                json.dumps(record.old_value) if record.old_value else None,
                json.dumps(record.new_value) if record.new_value else None,
                record.description,
                record.session_id,
                record.reverted,
                record.reverted_at,
                record.reverted_by,
            ),
        )
        self.db.conn.commit()

    def get_change_history(
        self,
        project_id: str,
        entity_type: str | None = None,
        entity_id: str | None = None,
        limit: int = 50,
        offset: int = 0,
    ) -> list[ChangeRecord]:
        """获取变更历史"""
        if not self.db:
            return []

        cursor = self.db.conn.cursor()

        if entity_type and entity_id:
            cursor.execute(
                """
                SELECT * FROM change_history
                WHERE project_id = ? AND entity_type = ? AND entity_id = ?
                ORDER BY changed_at DESC
                LIMIT ? OFFSET ?
            """,
                (project_id, entity_type, entity_id, limit, offset),
            )
        elif entity_type:
            cursor.execute(
                """
                SELECT * FROM change_history
                WHERE project_id = ? AND entity_type = ?
                ORDER BY changed_at DESC
                LIMIT ? OFFSET ?
            """,
                (project_id, entity_type, limit, offset),
            )
        else:
            cursor.execute(
                """
                SELECT * FROM change_history
                WHERE project_id = ?
                ORDER BY changed_at DESC
                LIMIT ? OFFSET ?
            """,
                (project_id, limit, offset),
            )

        records = []
        for row in cursor.fetchall():
            records.append(self._row_to_change_record(row))
        return records

    def _row_to_change_record(self, row) -> ChangeRecord:
        """将数据库行转换为ChangeRecord对象"""
        return ChangeRecord(
            id=row[0],
            project_id=row[1],
            change_type=row[2],
            entity_type=row[3],
            entity_id=row[4],
            entity_name=row[5],
            changed_by=row[6],
            changed_by_name=row[7],
            changed_at=row[8],
            old_value=json.loads(row[9]) if row[9] else None,
            new_value=json.loads(row[10]) if row[10] else None,
            description=row[11],
            session_id=row[12],
            reverted=bool(row[13]),
            reverted_at=row[14],
            reverted_by=row[15],
        )

    def get_entity_version_history(self, entity_type: str, entity_id: str) -> list[ChangeRecord]:
        """获取实体的版本历史（用于版本对比）"""
        if not self.db:
            return []

        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            SELECT * FROM change_history
            WHERE entity_type = ? AND entity_id = ?
            ORDER BY changed_at ASC
        """,
            (entity_type, entity_id),
        )

        records = []
        for row in cursor.fetchall():
            records.append(self._row_to_change_record(row))
        return records

    def revert_change(self, record_id: str, reverted_by: str) -> bool:
        """回滚变更"""
        if not self.db:
            return False

        now = datetime.now().isoformat()
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            UPDATE change_history
            SET reverted = 1, reverted_at = ?, reverted_by = ?
            WHERE id = ? AND reverted = 0
        """,
            (now, reverted_by, record_id),
        )
        self.db.conn.commit()
        return cursor.rowcount > 0

    def get_change_stats(self, project_id: str) -> dict[str, Any]:
        """获取变更统计"""
        if not self.db:
            return {}

        cursor = self.db.conn.cursor()

        # 总变更数
        cursor.execute(
            """
            SELECT COUNT(*) FROM change_history WHERE project_id = ?
        """,
            (project_id,),
        )
        total_changes = cursor.fetchone()[0]

        # 按类型统计
        cursor.execute(
            """
            SELECT change_type, COUNT(*) FROM change_history
            WHERE project_id = ? GROUP BY change_type
        """,
            (project_id,),
        )
        type_counts = {row[0]: row[1] for row in cursor.fetchall()}

        # 按实体类型统计
        cursor.execute(
            """
            SELECT entity_type, COUNT(*) FROM change_history
            WHERE project_id = ? GROUP BY entity_type
        """,
            (project_id,),
        )
        entity_type_counts = {row[0]: row[1] for row in cursor.fetchall()}

        # 最近活跃的用户
        cursor.execute(
            """
            SELECT changed_by_name, COUNT(*) as count FROM change_history
            WHERE project_id = ?
            GROUP BY changed_by_name
            ORDER BY count DESC
            LIMIT 5
        """,
            (project_id,),
        )
        top_contributors = [{"name": row[0], "changes": row[1]} for row in cursor.fetchall()]

        return {
            "total_changes": total_changes,
            "by_type": type_counts,
            "by_entity_type": entity_type_counts,
            "top_contributors": top_contributors,
        }

    # ============ 团队成员管理 ============

    def add_team_member(
        self,
        project_id: str,
        user_id: str,
        user_name: str,
        user_email: str,
        role: str,
        invited_by: str,
        permissions: list[str] | None = None,
    ) -> TeamMember:
        """添加团队成员"""
        member_id = str(uuid.uuid4())
        now = datetime.now().isoformat()

        # 根据角色设置默认权限
        if permissions is None:
            permissions = self._get_default_permissions(role)

        member = TeamMember(
            id=member_id,
            project_id=project_id,
            user_id=user_id,
            user_name=user_name,
            user_email=user_email,
            role=role,
            joined_at=now,
            invited_by=invited_by,
            last_active_at=None,
            permissions=permissions,
        )

        if self.db:
            self._save_member_to_db(member)

        return member

    def _get_default_permissions(self, role: str) -> list[str]:
        """获取角色的默认权限"""
        permissions_map = {
            "owner": ["read", "write", "delete", "share", "admin", "export"],
            "admin": ["read", "write", "delete", "share", "export"],
            "editor": ["read", "write", "export"],
            "viewer": ["read"],
            "commenter": ["read", "comment"],
        }
        return permissions_map.get(role, ["read"])

    def _save_member_to_db(self, member: TeamMember) -> None:
        """保存成员到数据库"""
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            INSERT INTO team_members
            (id, project_id, user_id, user_name, user_email, role, joined_at,
             invited_by, last_active_at, permissions)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
            (
                member.id,
                member.project_id,
                member.user_id,
                member.user_name,
                member.user_email,
                member.role,
                member.joined_at,
                member.invited_by,
                member.last_active_at,
                json.dumps(member.permissions),
            ),
        )
        self.db.conn.commit()

    def get_team_members(self, project_id: str) -> list[TeamMember]:
        """获取团队成员列表"""
        if not self.db:
            return []

        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            SELECT * FROM team_members WHERE project_id = ?
            ORDER BY joined_at ASC
        """,
            (project_id,),
        )

        members = []
        for row in cursor.fetchall():
            members.append(self._row_to_team_member(row))
        return members

    def _row_to_team_member(self, row) -> TeamMember:
        """将数据库行转换为TeamMember对象"""
        return TeamMember(
            id=row[0],
            project_id=row[1],
            user_id=row[2],
            user_name=row[3],
            user_email=row[4],
            role=row[5],
            joined_at=row[6],
            invited_by=row[7],
            last_active_at=row[8],
            permissions=json.loads(row[9]) if row[9] else [],
        )

    def update_member_role(self, member_id: str, new_role: str, updated_by: str) -> bool:
        """更新成员角色"""
        if not self.db:
            return False

        permissions = self._get_default_permissions(new_role)
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            UPDATE team_members
            SET role = ?, permissions = ?
            WHERE id = ?
        """,
            (new_role, json.dumps(permissions), member_id),
        )
        self.db.conn.commit()
        return cursor.rowcount > 0

    def remove_team_member(self, member_id: str, removed_by: str) -> bool:
        """移除团队成员"""
        if not self.db:
            return False

        cursor = self.db.conn.cursor()
        cursor.execute("DELETE FROM team_members WHERE id = ?", (member_id,))
        self.db.conn.commit()
        return cursor.rowcount > 0

    def check_permission(self, project_id: str, user_id: str, permission: str) -> bool:
        """检查用户权限"""
        if not self.db:
            return False

        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            SELECT permissions FROM team_members
            WHERE project_id = ? AND user_id = ?
        """,
            (project_id, user_id),
        )

        row = cursor.fetchone()
        if not row:
            return False

        permissions = json.loads(row[0]) if row[0] else []
        return permission in permissions or "admin" in permissions

    def update_last_active(self, project_id: str, user_id: str) -> None:
        """更新用户最后活跃时间"""
        if not self.db:
            return

        now = datetime.now().isoformat()
        cursor = self.db.conn.cursor()
        cursor.execute(
            """
            UPDATE team_members
            SET last_active_at = ?
            WHERE project_id = ? AND user_id = ?
        """,
            (now, project_id, user_id),
        )
        self.db.conn.commit()


# 全局协作管理器实例
_collaboration_manager = None


def get_collaboration_manager(db_manager=None) -> None:
    """获取协作管理器单例"""
    global _collaboration_manager
    if _collaboration_manager is None:
        _collaboration_manager = CollaborationManager(db_manager)
    return _collaboration_manager
