"""
Team service — MongoDB operations for Team workspace.

Flow: Tạo Team → Ghi Nháp → Comment → Review → Merge vào Chính
Features: User profile resolution, comments, review flow, reactions.
"""

from __future__ import annotations

import logging
import secrets
import string
from datetime import datetime, timezone
from typing import Any

import httpx
from bson import ObjectId

from common.mongo_client import mongodb_client, utc_now

logger = logging.getLogger(__name__)

# Collection names
COLLECTION_TEAMS = "cuccu_teams"
COLLECTION_TEAM_MEMBERS = "cuccu_team_members"
COLLECTION_TEAM_MEMOS = "cuccu_team_memos"
COLLECTION_TEAM_COMMENTS = "cuccu_team_comments"
COLLECTION_TEAM_REACTIONS = "cuccu_team_reactions"
COLLECTION_USER_PROFILES = "cuccu_user_profiles"

# Valid reactions
VALID_REACTIONS = {"👍", "💡", "❓", "✅", "🔥", "❤️"}


class TeamService:
    """Team CRUD + Comments + Reviews + Reactions service."""

    @property
    def teams(self):
        return mongodb_client.db[COLLECTION_TEAMS]

    @property
    def members(self):
        return mongodb_client.db[COLLECTION_TEAM_MEMBERS]

    @property
    def team_memos(self):
        return mongodb_client.db[COLLECTION_TEAM_MEMOS]

    @property
    def comments(self):
        return mongodb_client.db[COLLECTION_TEAM_COMMENTS]

    @property
    def reactions(self):
        return mongodb_client.db[COLLECTION_TEAM_REACTIONS]

    @property
    def user_profiles(self):
        return mongodb_client.db[COLLECTION_USER_PROFILES]

    # ========================= USER PROFILE RESOLUTION =========================

    async def resolve_user_profiles(self, user_ids: list[str]) -> dict[str, dict]:
        """
        Resolve Clerk user IDs → display names + avatars.
        Uses MongoDB cache (TTL 24h) to avoid hammering Clerk API.
        Returns {user_id: {display_name, avatar_url, email}}.
        """
        if not user_ids:
            return {}

        unique_ids = list(set(user_ids))
        result: dict[str, dict] = {}

        # 1. Check cache first
        cached = await self.user_profiles.find(
            {"user_id": {"$in": unique_ids}}
        ).to_list(length=200)

        cached_map = {}
        for c in cached:
            cached_map[c["user_id"]] = {
                "display_name": c.get("display_name", ""),
                "avatar_url": c.get("avatar_url", ""),
                "email": c.get("email", ""),
            }

        result.update(cached_map)

        # 2. Find uncached IDs
        uncached = [uid for uid in unique_ids if uid not in cached_map]
        if not uncached:
            return result

        # 3. Fetch from Clerk Backend API
        try:
            from config import CLERK_SECRET_KEY
            if not CLERK_SECRET_KEY:
                logger.warning("⚠️ CLERK_SECRET_KEY not set, using fallback names")
                for uid in uncached:
                    result[uid] = {"display_name": uid[:12], "avatar_url": "", "email": ""}
                return result

            async with httpx.AsyncClient(timeout=10.0) as client:
                # Clerk API: GET /v1/users?user_id=id1&user_id=id2
                params = [("user_id", uid) for uid in uncached]
                resp = await client.get(
                    "https://api.clerk.com/v1/users",
                    params=params,
                    headers={"Authorization": f"Bearer {CLERK_SECRET_KEY}"},
                )

                if resp.status_code == 200:
                    users_data = resp.json()
                    # Handle both list and paginated response
                    if isinstance(users_data, dict) and "data" in users_data:
                        users_list = users_data["data"]
                    elif isinstance(users_data, list):
                        users_list = users_data
                    else:
                        users_list = []

                    now = utc_now()
                    for user in users_list:
                        uid = user.get("id", "")
                        first = user.get("first_name", "") or ""
                        last = user.get("last_name", "") or ""
                        display_name = f"{first} {last}".strip() or uid[:12]
                        avatar_url = user.get("image_url", "") or user.get("profile_image_url", "") or ""
                        email = ""
                        email_addresses = user.get("email_addresses", [])
                        if email_addresses:
                            email = email_addresses[0].get("email_address", "")

                        profile = {
                            "display_name": display_name,
                            "avatar_url": avatar_url,
                            "email": email,
                        }
                        result[uid] = profile

                        # Cache in MongoDB (upsert)
                        await self.user_profiles.update_one(
                            {"user_id": uid},
                            {"$set": {
                                "user_id": uid,
                                **profile,
                                "cached_at": now,
                            }},
                            upsert=True,
                        )
                else:
                    logger.error("❌ Clerk API error: %s %s", resp.status_code, resp.text[:200])
                    for uid in uncached:
                        result[uid] = {"display_name": uid[:12], "avatar_url": "", "email": ""}

        except Exception as e:
            logger.error("❌ Failed to resolve user profiles: %s", e)
            for uid in uncached:
                if uid not in result:
                    result[uid] = {"display_name": uid[:12], "avatar_url": "", "email": ""}

        return result

    def _get_profile(self, profiles: dict, user_id: str) -> tuple[str, str]:
        """Helper to get (display_name, avatar_url) from resolved profiles."""
        p = profiles.get(user_id, {})
        return p.get("display_name", user_id[:12] if user_id else ""), p.get("avatar_url", "")

    # ========================= TEAM CRUD =========================

    async def create_team(self, name: str, description: str, owner_id: str) -> dict:
        """Tạo team mới. Owner tự động thành member."""
        now = utc_now()
        # Use only unambiguous chars (no O/0/I/l/1)
        alphabet = string.ascii_uppercase.replace('O', '').replace('I', '') + string.digits.replace('0', '').replace('1', '')
        invite_code = ''.join(secrets.choice(alphabet) for _ in range(8))

        doc = {
            "name": name,
            "description": description,
            "owner_id": owner_id,
            "invite_code": invite_code,
            "created_at": now,
            "updated_at": now,
        }
        result = await self.teams.insert_one(doc)
        doc["_id"] = result.inserted_id

        # Add owner as member
        await self.members.insert_one({
            "team_id": str(result.inserted_id),
            "user_id": owner_id,
            "role": "owner",
            "joined_at": now,
        })

        logger.info("✅ Team created: %s by %s", name, owner_id)
        return self._team_to_response(doc, member_count=1)

    async def list_teams(self, user_id: str) -> list[dict]:
        """List all teams user belongs to. Uses aggregation to avoid N+1."""
        # Find team_ids user is member of
        member_docs = await self.members.find(
            {"user_id": user_id}
        ).to_list(length=100)

        team_ids = [m["team_id"] for m in member_docs]
        if not team_ids:
            return []

        # Use aggregation to get teams with member counts in one query
        object_ids = [ObjectId(tid) for tid in team_ids if ObjectId.is_valid(tid)]
        pipeline = [
            {"$match": {"_id": {"$in": object_ids}}},
            {"$lookup": {
                "from": COLLECTION_TEAM_MEMBERS,
                "let": {"team_id": {"$toString": "$_id"}},
                "pipeline": [
                    {"$match": {"$expr": {"$eq": ["$team_id", "$$team_id"]}}},
                    {"$count": "count"},
                ],
                "as": "member_info",
            }},
            {"$addFields": {
                "member_count": {
                    "$ifNull": [{"$arrayElemAt": ["$member_info.count", 0]}, 0]
                },
            }},
            {"$project": {"member_info": 0}},
            {"$sort": {"created_at": -1}},
        ]

        teams = await self.teams.aggregate(pipeline).to_list(length=100)

        results = []
        for team in teams:
            count = team.pop("member_count", 0)
            results.append(self._team_to_response(team, member_count=count))

        return results

    async def get_team(self, team_id: str, user_id: str) -> dict | None:
        """Get team detail with resolved member names. Only members can view."""
        if not await self._is_member(team_id, user_id):
            return None

        team = await self.teams.find_one({"_id": ObjectId(team_id)})
        if not team:
            return None

        # Get members
        member_docs = await self.members.find(
            {"team_id": team_id}
        ).to_list(length=100)

        # Resolve all member names
        member_ids = [m["user_id"] for m in member_docs]
        profiles = await self.resolve_user_profiles(member_ids)

        members = []
        for m in member_docs:
            name, avatar = self._get_profile(profiles, m["user_id"])
            members.append({
                "user_id": m["user_id"],
                "role": m["role"],
                "joined_at": str(m.get("joined_at", "")) if m.get("joined_at") else None,
                "display_name": name,
                "avatar_url": avatar,
            })

        response = self._team_to_response(team, member_count=len(members))
        response["members"] = members
        return response

    async def update_team(self, team_id: str, user_id: str, **fields) -> dict | None:
        """Update team. Only owner."""
        if not await self._is_owner(team_id, user_id):
            return None

        update = {"updated_at": utc_now()}
        for key in ("name", "description"):
            if key in fields and fields[key] is not None:
                update[key] = fields[key]

        await self.teams.update_one({"_id": ObjectId(team_id)}, {"$set": update})
        return await self.get_team(team_id, user_id)

    async def delete_team(self, team_id: str, user_id: str) -> bool:
        """Delete team + all members + all memos + comments + reactions. Only owner."""
        if not await self._is_owner(team_id, user_id):
            return False

        await self.teams.delete_one({"_id": ObjectId(team_id)})
        await self.members.delete_many({"team_id": team_id})
        await self.team_memos.delete_many({"team_id": team_id})
        await self.comments.delete_many({"team_id": team_id})
        await self.reactions.delete_many({"team_id": team_id})
        logger.info("🗑️ Team deleted: %s by %s", team_id, user_id)
        return True

    # ========================= MEMBERS =========================

    async def join_team(self, invite_code: str, user_id: str) -> dict | None:
        """Join team bằng invite code."""
        # Case-insensitive search to avoid O/0 confusion
        import re
        team = await self.teams.find_one({
            "invite_code": re.compile(f"^{re.escape(invite_code)}$", re.IGNORECASE)
        })
        if not team:
            return None

        team_id = str(team["_id"])

        # Check already member
        existing = await self.members.find_one({
            "team_id": team_id, "user_id": user_id
        })
        if existing:
            return self._team_to_response(team)

        await self.members.insert_one({
            "team_id": team_id,
            "user_id": user_id,
            "role": "member",
            "joined_at": utc_now(),
        })

        logger.info("✅ User %s joined team %s", user_id, team["name"])
        return self._team_to_response(team)

    async def remove_member(self, team_id: str, target_user_id: str, requester_id: str) -> bool:
        """Remove member. Owner only (can't remove self)."""
        if not await self._is_owner(team_id, requester_id):
            return False
        if target_user_id == requester_id:
            return False  # Can't remove yourself

        result = await self.members.delete_one({
            "team_id": team_id, "user_id": target_user_id
        })
        return result.deleted_count > 0

    async def leave_team(self, team_id: str, user_id: str) -> bool:
        """Leave team. Owner can't leave (must delete team)."""
        if await self._is_owner(team_id, user_id):
            return False

        result = await self.members.delete_one({
            "team_id": team_id, "user_id": user_id
        })
        return result.deleted_count > 0

    # ========================= TEAM MEMOS (Draft / Main) =========================

    async def create_draft(self, team_id: str, user_id: str, content: str, tags: list[str] = None) -> dict | None:
        """Tạo note nháp trong team."""
        if not await self._is_member(team_id, user_id):
            return None

        now = utc_now()
        doc = {
            "team_id": team_id,
            "creator_id": user_id,
            "content": content,
            "tags": tags or [],
            "space": "draft",
            "status": "draft",
            "pinned": False,
            "merged_at": None,
            "merged_by": None,
            "review_reason": None,
            "created_at": now,
            "updated_at": now,
        }
        result = await self.team_memos.insert_one(doc)
        doc["_id"] = result.inserted_id

        # Resolve creator name
        profiles = await self.resolve_user_profiles([user_id])
        return self._memo_to_response(doc, profiles=profiles)

    async def list_drafts(self, team_id: str, user_id: str) -> list[dict]:
        """List nháp của team with user profiles resolved."""
        if not await self._is_member(team_id, user_id):
            return []

        cursor = self.team_memos.find({
            "team_id": team_id,
            "space": "draft",
        }).sort("created_at", -1)

        docs = await cursor.to_list(length=200)
        if not docs:
            return []

        # Collect all user IDs to resolve
        user_ids = set()
        for doc in docs:
            user_ids.add(doc.get("creator_id", ""))
            if doc.get("merged_by"):
                user_ids.add(doc["merged_by"])
        user_ids.discard("")

        profiles = await self.resolve_user_profiles(list(user_ids))

        # Get comment counts + reactions in batch
        memo_ids = [str(doc["_id"]) for doc in docs]

        # Comment counts
        comment_pipeline = [
            {"$match": {"memo_id": {"$in": memo_ids}}},
            {"$group": {"_id": "$memo_id", "count": {"$sum": 1}}},
        ]
        comment_counts = {}
        async for item in self.comments.aggregate(comment_pipeline):
            comment_counts[item["_id"]] = item["count"]

        # Reaction counts
        reaction_pipeline = [
            {"$match": {"memo_id": {"$in": memo_ids}}},
            {"$group": {"_id": {"memo_id": "$memo_id", "emoji": "$emoji"}, "count": {"$sum": 1}}},
        ]
        reaction_counts: dict[str, dict] = {}  # {memo_id: {emoji: count}}
        async for item in self.reactions.aggregate(reaction_pipeline):
            mid = item["_id"]["memo_id"]
            emoji = item["_id"]["emoji"]
            reaction_counts.setdefault(mid, {})[emoji] = item["count"]

        # User's own reactions
        user_reactions_pipeline = [
            {"$match": {"memo_id": {"$in": memo_ids}, "user_id": user_id}},
            {"$group": {"_id": "$memo_id", "emojis": {"$push": "$emoji"}}},
        ]
        user_reactions: dict[str, list] = {}
        async for item in self.reactions.aggregate(user_reactions_pipeline):
            user_reactions[item["_id"]] = item["emojis"]

        results = []
        for doc in docs:
            mid = str(doc["_id"])
            resp = self._memo_to_response(doc, profiles=profiles)
            resp["comment_count"] = comment_counts.get(mid, 0)
            resp["reaction_counts"] = reaction_counts.get(mid, {})
            resp["user_reactions"] = user_reactions.get(mid, [])
            results.append(resp)

        return results

    async def list_main(self, team_id: str, user_id: str) -> list[dict]:
        """List bản chính của team with user profiles resolved."""
        if not await self._is_member(team_id, user_id):
            return []

        cursor = self.team_memos.find({
            "team_id": team_id,
            "space": "main",
        }).sort("merged_at", -1)

        docs = await cursor.to_list(length=200)
        if not docs:
            return []

        # Collect user IDs
        user_ids = set()
        for doc in docs:
            user_ids.add(doc.get("creator_id", ""))
            if doc.get("merged_by"):
                user_ids.add(doc["merged_by"])
        user_ids.discard("")

        profiles = await self.resolve_user_profiles(list(user_ids))

        memo_ids = [str(doc["_id"]) for doc in docs]

        # Comment counts
        comment_pipeline = [
            {"$match": {"memo_id": {"$in": memo_ids}}},
            {"$group": {"_id": "$memo_id", "count": {"$sum": 1}}},
        ]
        comment_counts = {}
        async for item in self.comments.aggregate(comment_pipeline):
            comment_counts[item["_id"]] = item["count"]

        # Reaction counts
        reaction_pipeline = [
            {"$match": {"memo_id": {"$in": memo_ids}}},
            {"$group": {"_id": {"memo_id": "$memo_id", "emoji": "$emoji"}, "count": {"$sum": 1}}},
        ]
        reaction_counts: dict[str, dict] = {}
        async for item in self.reactions.aggregate(reaction_pipeline):
            mid = item["_id"]["memo_id"]
            emoji = item["_id"]["emoji"]
            reaction_counts.setdefault(mid, {})[emoji] = item["count"]

        # User reactions
        user_reactions_pipeline = [
            {"$match": {"memo_id": {"$in": memo_ids}, "user_id": user_id}},
            {"$group": {"_id": "$memo_id", "emojis": {"$push": "$emoji"}}},
        ]
        user_reactions: dict[str, list] = {}
        async for item in self.reactions.aggregate(user_reactions_pipeline):
            user_reactions[item["_id"]] = item["emojis"]

        results = []
        for doc in docs:
            mid = str(doc["_id"])
            resp = self._memo_to_response(doc, profiles=profiles)
            resp["comment_count"] = comment_counts.get(mid, 0)
            resp["reaction_counts"] = reaction_counts.get(mid, {})
            resp["user_reactions"] = user_reactions.get(mid, [])
            results.append(resp)

        return results

    async def merge_to_main(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Merge note nháp → bản chính. Only owner can merge."""
        if not await self._is_owner(team_id, user_id):
            return None

        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
            "space": "draft",
        })
        if not doc:
            return None

        now = utc_now()
        await self.team_memos.update_one(
            {"_id": doc["_id"]},
            {"$set": {
                "space": "main",
                "status": "merged",
                "merged_at": now,
                "merged_by": user_id,
                "updated_at": now,
            }}
        )

        updated = await self.team_memos.find_one({"_id": doc["_id"]})
        profiles = await self.resolve_user_profiles([
            updated.get("creator_id", ""), user_id
        ])
        logger.info("✅ Merged draft → main: %s in team %s", memo_id, team_id)
        return self._memo_to_response(updated, profiles=profiles)

    async def unmerge_to_draft(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Đưa note từ chính về nháp. Chỉ owner."""
        if not await self._is_owner(team_id, user_id):
            return None

        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
            "space": "main",
        })
        if not doc:
            return None

        await self.team_memos.update_one(
            {"_id": doc["_id"]},
            {"$set": {
                "space": "draft",
                "status": "draft",
                "merged_at": None,
                "merged_by": None,
                "updated_at": utc_now(),
            }}
        )

        updated = await self.team_memos.find_one({"_id": doc["_id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        return self._memo_to_response(updated, profiles=profiles)

    async def update_memo(self, team_id: str, memo_id: str, user_id: str, content: str = None, tags: list[str] = None) -> dict | None:
        """Update team memo. Creator hoặc owner."""
        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
        })
        if not doc:
            return None

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return None

        # Can't edit if pending_review (unless owner)
        if doc.get("status") == "pending_review" and not is_owner:
            return None

        update: dict[str, Any] = {"updated_at": utc_now()}
        if content is not None:
            update["content"] = content
        if tags is not None:
            update["tags"] = tags

        await self.team_memos.update_one({"_id": doc["_id"]}, {"$set": update})
        updated = await self.team_memos.find_one({"_id": doc["_id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        return self._memo_to_response(updated, profiles=profiles)

    async def delete_memo(self, team_id: str, memo_id: str, user_id: str) -> bool:
        """Delete team memo. Creator hoặc owner. Must unmerge first if in main."""
        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
        })
        if not doc:
            return False

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return False

        # Must unmerge before deleting
        if doc.get("space") == "main" and not is_owner:
            return False

        await self.team_memos.delete_one({"_id": doc["_id"]})
        # Clean up comments and reactions
        await self.comments.delete_many({"memo_id": str(doc["_id"])})
        await self.reactions.delete_many({"memo_id": str(doc["_id"])})
        return True

    async def pin_memo(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Pin/unpin team memo. Owner hoặc creator."""
        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
        })
        if not doc:
            return None

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return None

        new_pinned = not doc.get("pinned", False)
        await self.team_memos.update_one(
            {"_id": doc["_id"]},
            {"$set": {"pinned": new_pinned, "updated_at": utc_now()}}
        )
        updated = await self.team_memos.find_one({"_id": doc["_id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        return self._memo_to_response(updated, profiles=profiles)

    # ========================= REVIEW FLOW =========================

    async def request_review(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Member requests review for their draft. Status: draft → pending_review."""
        if not await self._is_member(team_id, user_id):
            return None

        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
            "space": "draft",
        })
        if not doc:
            return None

        # Only creator or owner can request review
        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return None

        await self.team_memos.update_one(
            {"_id": doc["_id"]},
            {"$set": {"status": "pending_review", "updated_at": utc_now()}}
        )

        updated = await self.team_memos.find_one({"_id": doc["_id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        logger.info("📨 Review requested: %s by %s", memo_id, user_id)
        return self._memo_to_response(updated, profiles=profiles)

    async def submit_review(self, team_id: str, memo_id: str, user_id: str, action: str, reason: str = "") -> dict | None:
        """Owner approves or rejects a review. Only owner can review."""
        if not await self._is_owner(team_id, user_id):
            return None

        doc = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
        })
        if not doc:
            return None

        if action == "approve":
            new_status = "approved"
        elif action == "reject":
            new_status = "draft"
        else:
            return None

        update = {
            "status": new_status,
            "review_reason": reason,
            "updated_at": utc_now(),
        }

        await self.team_memos.update_one({"_id": doc["_id"]}, {"$set": update})
        updated = await self.team_memos.find_one({"_id": doc["_id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        logger.info("📋 Review %s: %s by %s (reason: %s)", action, memo_id, user_id, reason[:50])
        return self._memo_to_response(updated, profiles=profiles)

    # ========================= COMMENTS =========================

    async def create_comment(self, team_id: str, memo_id: str, user_id: str, content: str) -> dict | None:
        """Add comment to a team memo."""
        if not await self._is_member(team_id, user_id):
            return None

        # Verify memo exists in this team
        memo = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
        })
        if not memo:
            return None

        now = utc_now()
        doc = {
            "team_id": team_id,
            "memo_id": memo_id,
            "user_id": user_id,
            "content": content,
            "created_at": now,
        }
        result = await self.comments.insert_one(doc)
        doc["_id"] = result.inserted_id

        profiles = await self.resolve_user_profiles([user_id])
        name, avatar = self._get_profile(profiles, user_id)

        return {
            "id": str(doc["_id"]),
            "memo_id": memo_id,
            "user_id": user_id,
            "user_name": name,
            "user_avatar": avatar,
            "content": content,
            "created_at": str(now),
        }

    async def list_comments(self, team_id: str, memo_id: str, user_id: str) -> list[dict]:
        """List comments for a team memo."""
        if not await self._is_member(team_id, user_id):
            return []

        docs = await self.comments.find({
            "memo_id": memo_id,
            "team_id": team_id,
        }).sort("created_at", 1).to_list(length=100)

        if not docs:
            return []

        # Resolve all commenter names
        user_ids = list(set(d["user_id"] for d in docs))
        profiles = await self.resolve_user_profiles(user_ids)

        results = []
        for doc in docs:
            name, avatar = self._get_profile(profiles, doc["user_id"])
            results.append({
                "id": str(doc["_id"]),
                "memo_id": doc["memo_id"],
                "user_id": doc["user_id"],
                "user_name": name,
                "user_avatar": avatar,
                "content": doc["content"],
                "created_at": str(doc.get("created_at", "")),
            })

        return results

    async def delete_comment(self, team_id: str, comment_id: str, user_id: str) -> bool:
        """Delete a comment. Only comment author or team owner."""
        doc = await self.comments.find_one({"_id": ObjectId(comment_id)})
        if not doc or doc.get("team_id") != team_id:
            return False

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("user_id") != user_id:
            return False

        await self.comments.delete_one({"_id": doc["_id"]})
        return True

    # ========================= REACTIONS =========================

    async def toggle_reaction(self, team_id: str, memo_id: str, user_id: str, emoji: str) -> dict | None:
        """Toggle reaction on a memo. Returns updated reaction counts."""
        if emoji not in VALID_REACTIONS:
            return None

        if not await self._is_member(team_id, user_id):
            return None

        # Check memo exists
        memo = await self.team_memos.find_one({
            "_id": ObjectId(memo_id),
            "team_id": team_id,
        })
        if not memo:
            return None

        # Check if reaction already exists
        existing = await self.reactions.find_one({
            "memo_id": memo_id,
            "user_id": user_id,
            "emoji": emoji,
        })

        if existing:
            # Remove reaction
            await self.reactions.delete_one({"_id": existing["_id"]})
        else:
            # Add reaction
            await self.reactions.insert_one({
                "team_id": team_id,
                "memo_id": memo_id,
                "user_id": user_id,
                "emoji": emoji,
                "created_at": utc_now(),
            })

        # Return updated counts
        pipeline = [
            {"$match": {"memo_id": memo_id}},
            {"$group": {"_id": "$emoji", "count": {"$sum": 1}}},
        ]
        counts = {}
        async for item in self.reactions.aggregate(pipeline):
            counts[item["_id"]] = item["count"]

        # User's reactions for this memo
        user_emojis = await self.reactions.find(
            {"memo_id": memo_id, "user_id": user_id}
        ).to_list(length=20)
        user_reaction_list = [r["emoji"] for r in user_emojis]

        return {
            "memo_id": memo_id,
            "reaction_counts": counts,
            "user_reactions": user_reaction_list,
        }

    # ========================= HELPERS =========================

    async def _is_member(self, team_id: str, user_id: str) -> bool:
        return await self.members.find_one({
            "team_id": team_id, "user_id": user_id
        }) is not None

    async def _is_owner(self, team_id: str, user_id: str) -> bool:
        return await self.members.find_one({
            "team_id": team_id, "user_id": user_id, "role": "owner"
        }) is not None

    def _team_to_response(self, doc: dict, member_count: int = 0) -> dict:
        created = doc.get("created_at")
        return {
            "id": str(doc["_id"]),
            "name": doc.get("name", ""),
            "description": doc.get("description", ""),
            "owner_id": doc.get("owner_id", ""),
            "invite_code": doc.get("invite_code", ""),
            "members": [],
            "member_count": member_count,
            "created_at": str(created) if created else None,
        }

    def _memo_to_response(self, doc: dict, profiles: dict = None) -> dict:
        profiles = profiles or {}
        created = doc.get("created_at")
        updated = doc.get("updated_at")
        merged = doc.get("merged_at")

        creator_id = doc.get("creator_id", "")
        creator_name, creator_avatar = self._get_profile(profiles, creator_id)

        merged_by = doc.get("merged_by")
        merged_by_name = ""
        if merged_by:
            merged_by_name, _ = self._get_profile(profiles, merged_by)

        return {
            "id": str(doc["_id"]),
            "content": doc.get("content", ""),
            "tags": doc.get("tags", []),
            "creator_id": creator_id,
            "creator_name": creator_name,
            "creator_avatar": creator_avatar,
            "team_id": doc.get("team_id", ""),
            "space": doc.get("space", "draft"),
            "status": doc.get("status", "draft"),
            "pinned": doc.get("pinned", False),
            "merged_at": str(merged) if merged else None,
            "merged_by": merged_by,
            "merged_by_name": merged_by_name,
            "comment_count": 0,
            "reaction_counts": {},
            "user_reactions": [],
            "review_reason": doc.get("review_reason", ""),
            "created_at": str(created) if created else None,
            "updated_at": str(updated) if updated else None,
        }


# Singleton
team_service = TeamService()
